VMS Tech Demo 15 - Groovy for applications

Content:

  1. Introduction
  2. Why Groovy?
  3. Integration with existing code:
  4. Groovy calling Java
  5. Java calling Groovy
    1. Simple call
    2. JSR 223
  6. Groovy calling native: (native being C, Pascal or Basic)
    1. JNI
    2. VMSCALL library
  7. Native calling Groovy (native being C, Pascal or Basic)
  8. Conclusion:

Introduction:

I am starting to really like Groovy.

This article will look at Groovy for applications.

VMS Tech Demo 13 - Groovy for scripting will look at Groovy for smaller scripting tasks.

Everything is about Groovy usage on VMS. Even though most of the Groovy code would run fine on Linux or Windows.

Why Groovy?

Integration with existing code:

Many potential Groovy use cases will just involve Groovy code.

But some potential Groovy use cases will involved integration Groovy code with existing code in another language.

The other language could be a JVM language (typical Java, but potentially also Scala or Kotlin) or a native language (on VMS that could be Cobol, Fortran, Pascal, Basic, C, C++).

All examples in this article will demo this simple API:

int add(int a, int b)
String conc(String a, String b)
Data modify(Data o)

Very simple but still covers the the most important stuff.

The general rule of using simple data types / data structures when calling between different languages also applies in this case.

Groovy calling Java:

Groovy is built on top of Java so any Java code can be directly called from Groovy.

JData.java:

public class JData {
    private int iv;
    private String sv;
    public JData() {
        this(0, "");
    }
    public JData(int iv, String sv) {
        this.iv = iv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
}

J.java:

public class J {
    public int add(int a, int b) {
        return a + b;
    }
    public String conc(String a, String b) {
        return a + b;
    }
    public JData modify(JData o) {
        return new JData(o.getIv() + 1, o.getSv() + "X");
    }
}

Gr.groovy:

J j = new J()
v1 = 123
v2 = 456
v3 = j.add(v1,v2)
println("$v1 + $v2 = $v3")
s1 = "ABC"
s2  = "DEF"
s3 = j.conc(s1, s2)
println("$s1 + $s2 = $s3")
o = new JData(123, "ABC")
println("iv=${o.iv} sv=${o.sv}")
o2 = j.modify(o)
println("iv=${o2.iv} sv=${o2.sv}")
$ javac J.java JData.java
$ groovy Gr.groovy

Java calling Groovy:

Simple call:

Groovy code that get compiled to Java byte code with groovyc command and then it can be directly called from Java. With a few caveats due to the dynamic nature of Groovy.

Gr.groovy:

class GrData {
    int iv
    String sv
}

class Gr {
    int add(int a, int b) {
        return a + b
    }
    String conc(String a, String b) {
        return a + b
    }
    GrData modify(GrData o) {
        return new GrData(iv: o.iv + 1, sv: o.sv + "X")
    }
}

J.java:

public class J {
    public static void main(String[] args) throws Exception {
        Gr gr = new Gr();
        int v1 = 123;
        int v2 = 456;
        int v3 = gr.add(v1, v2);
        System.out.printf("%d + %d = %d\n", v1, v2, v3);
        String s1 = "ABC";
        String s2 = "DEF";
        String s3 = gr.conc(s1, s2);
        System.out.printf("%s + %s = %s\n", s1, s2, s3);
        GrData o = new GrData();
        o.setIv(123);
        o.setSv("ABC");
        System.out.printf("iv=%d sv=%s\n", o.getIv(), o.getSv());
        GrData o2 = gr.modify(o);
        System.out.printf("iv=%d sv=%s\n", o2.getIv(), o2.getSv());
    }
}
$ groovyc Gr.groovy
$ javac -cp .:/disk0/net/groovy/groovy-4.0.12/lib/* J.java
$ java -cp .:/disk0/net/groovy/groovy-4.0.12/lib/* "J"

JSR 223:

But alternatively Java can also execute Groovy source code using the JSR 223 ScriptEngine.

This obviously makes most sense for relative small code fragments. But it can be really cool for making frequently changing but complex rules configuration instead of code: just save the Groovy code in a file or in the database.

JData.java:

public class JData {
    private int iv;
    private String sv;
    public JData() {
        this(0, "");
    }
    public JData(int iv, String sv) {
        this.iv = iv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
}

JDyn.java:

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

public class JDyn {
    public static void testAdd() throws ScriptException {
        String source = "int add(int a, int b) {\r\n" +
                        "    return a + b\r\n" +
                        "}\r\n" +
                        "v3 = add(v1, v2)\r\n";
        ScriptEngineManager sem = new ScriptEngineManager();
        ScriptEngine se = sem.getEngineByName("groovy");
        ScriptContext ctx = new SimpleScriptContext();
        int v1 = 123;
        int v2 = 456;
        ctx.setAttribute("v1", v1, ScriptContext.ENGINE_SCOPE);
        ctx.setAttribute("v2", v2, ScriptContext.ENGINE_SCOPE);
        se.eval(source, ctx);
        int v3 = (Integer)ctx.getAttribute("v3");
        System.out.printf("%d + %d = %d\n", v1, v2, v3);
    }
    public static void testConc() throws ScriptException {
        String source = "String conc(String a, String b) {\r\n" +
                        "    return a + b\r\n" +
                        "}\r\n" +
                        "s3 = conc(s1, s2)\r\n";
        ScriptEngineManager sem = new ScriptEngineManager();
        ScriptEngine se = sem.getEngineByName("groovy");
        ScriptContext ctx = new SimpleScriptContext();
        String s1 = "ABC";
        String s2 = "DEF";
        ctx.setAttribute("s1", s1, ScriptContext.ENGINE_SCOPE);
        ctx.setAttribute("s2", s2, ScriptContext.ENGINE_SCOPE);
        se.eval(source, ctx);
        String s3 = (String)ctx.getAttribute("s3");
        System.out.printf("%s + %s = %s\n", s1, s2, s3);
    }
    public static void testModify() throws ScriptException {
        String source = "JData modify(JData o) {\r\n" +
                        "    return new JData(iv: o.iv + 1, sv: o.sv + 'X')\r\n" +
                        "}\r\n" +
                        "o2 = modify(o)\r\n";
        ScriptEngineManager sem = new ScriptEngineManager();
        ScriptEngine se = sem.getEngineByName("groovy");
        ScriptContext ctx = new SimpleScriptContext();
        JData o = new JData(123, "ABC");
        System.out.printf("iv=%d sv=%s\n", o.getIv(), o.getSv());
        ctx.setAttribute("o", o, ScriptContext.ENGINE_SCOPE);
        se.eval(source, ctx);
        JData o2 = (JData)ctx.getAttribute("o2");
        System.out.printf("iv=%d sv=%s\n", o2.getIv(), o2.getSv());
    }
    public static void main(String[] args) throws Exception {
        testAdd();
        testConc();
        testModify();
    }
}

dodyn.com:

$ javac -cp . JDyn.java JData.java
$ java -cp .:/disk0/net/groovy/groovy-4.0.12/lib/* "JDyn"

ScriptEngine was added to Java in version 6, so this features is not available in Java 5 on VMS Alpha.

Groovy calling native:

Let us see some examples of how Groovy can call C, Pascal and Basic code.

The classic way to to call native code from a JVM language is using JNI (Java Native Interface).

It has existed since Java 1.0 and it works.

But it requires writing wrapper code in C and JNI wrapper code can be a bit tricky.

And on VMS we have the additional problem that Java 8 put all data in P2 space while a lot of old native code expects data to be in P0 space.

But knowing JNI is still useful.

Note that the call stack is:

Groovy calling native using JNI

Alternatives to JNI has been developed.

JNA is a common open source generic wrapper.

Java 16 introduced a Foreign Function API (still in preview).

To assist with calling VMS SYS$ and LIB$ functions I developed the VMSCALL library - for details see here.

It can also be used to call custom native code.

The wrapper code becomes trivial (just need to be sure that everything are functions returning a status code and that values being returned is an out parameter) and can be done in any language.

The Groovy code becomes responsible for setting up the call properly.

At first that call setup may look a bit complex, but it is actually pretty simple if one know VMS calling convention!

Note that the call stack is:

Groovy calling native using VMSCALL library

Comparison:

Technology Impact application code Required wrapper code
JNI transparent API simple Java code not requiring any specific skills
very complex C code requiring knowledge of both JNI API and VMS calling convention
VMSCALL library glue code requiring knowlede of VMS calling convention simple code in whatever language to normalize API to be functions returning status value

JNI:

data.h:

#ifndef DATA_H
#define DATA_H

struct data
{
    int iv;
    char sv[256];
};

#endif

C.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "data.h"

int add(int a, int b)
{
    return a + b;
}

char *conc(char *a, char *b)
{
    char *res = malloc(strlen(a) + strlen(b) + 1);
    strcpy(res, a);
    strcat(res, b);
    return res;
}

struct data *modify(struct data *o)
{
    struct data *res = malloc(sizeof(struct data));
    res->iv = o->iv + 1;
    strcpy(res->sv, o->sv);
    strcat(res->sv, "X");
    return res;
}

Data.java:

public class Data {
    private int iv;
    private String sv;
    public Data() {
        this(0, "");
    }
    public Data(int iv, String sv) {
        super();
        this.iv = iv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
}

NC.java:

public class NC {
    static {
        System.loadLibrary("ncshr");
    }
    public static native int add(int a, int b);
    public static native String conc(String a, String b);
    public static native Data modify(Data o);
}

NC.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "NC.h"                                                          

#include "data.h"

int ADD(int a, int b);
char *CONC(const char *a, const char *b);
struct data *MODIFY(const struct data *o);

JNIEXPORT jint JNICALL Java_NC_add(JNIEnv *ctx, jclass obj, jint a, jint b)
{
    return ADD(a, b);
}

JNIEXPORT jstring JNICALL Java_NC_conc(JNIEnv *ctx, jclass obj, jstring a, jstring b)
{
    const char *na = (*ctx)->GetStringUTFChars(ctx, a, 0);
    const char *nb = (*ctx)->GetStringUTFChars(ctx, b, 0);
    char *na2 = _malloc32(strlen(na) + 1);
    strcpy(na2, na);
    char *nb2 = _malloc32(strlen(nb) + 1);
    strcpy(nb2, nb);
    const char *res = CONC(na2, nb2);
    free(na2);
    free(nb2);
    (*ctx)->ReleaseStringUTFChars(ctx, a, na);
    (*ctx)->ReleaseStringUTFChars(ctx, b, nb);
    return (*ctx)->NewStringUTF(ctx, res);
}

JNIEXPORT jobject JNICALL Java_NC_modify(JNIEnv *ctx, jclass obj , jobject o)
{
    jclass dataclz = (*ctx)->FindClass(ctx, "Data");
    jfieldID ivfld = (*ctx)->GetFieldID(ctx, dataclz, "iv", "I");
    jfieldID svfld = (*ctx)->GetFieldID(ctx, dataclz, "sv", "Ljava/lang/String;");
    jmethodID ctor = (*ctx)->GetMethodID(ctx, dataclz, "<init>", "()V");
    struct data no;
    no.iv = (*ctx)->GetIntField(ctx, o, ivfld);
    jstring temp = (*ctx)->GetObjectField(ctx, o, svfld);
    const char *ntemp = (*ctx)->GetStringUTFChars(ctx, temp, 0);
    strcpy(no.sv, ntemp);
    (*ctx)->ReleaseStringUTFChars(ctx, temp, ntemp);
    struct data *no2 = MODIFY(&no);
    jint iv = no2->iv;
    jstring sv = (*ctx)->NewStringUTF(ctx, no2->sv);
    free(no2);
    jobject o2 = (*ctx)->NewObject(ctx, dataclz, ctor);
    (*ctx)->SetIntField(ctx, o2, ivfld, iv);
    (*ctx)->SetObjectField(ctx, o2, svfld, sv);
    return o2;
}

GrNC.groovy:

v1 = 123
v2 = 456
v3 = NC.add(v1, v2)
println("$v1 + $v2 = $v3")
s1 = "ABC"
s2 = "DEF"
s3 = NC.conc(s1, s2)
println("$s1 + $s2 = $s3")
o = new Data(iv: 123, sv: "ABC")
println("iv=${o.iv} sv=${o.sv}")
o2 = NC.modify(o)
println("iv=${o2.iv} sv=${o2.sv}")

jcc.com:

$ javaver = "unknown"
$ osnam = "unknown"
$ ptrsiz = 0
$ if f$getsyi("arch_name") .eqs. "Alpha"
$ then
$    javaver = "java$150"
$    osnam ="vms"
$    ptrsiz = 32
$ endif
$ if f$getsyi("arch_name") .eqs. "IA64" .or. f$getsyi("arch_name") .eqs. "x86_64"
$ then
$    javaver = "openjdk$80"
$    osnam = "openvms"
$    ptrsiz = 64
$ endif
$ jcc :== cc/pointer='ptrsiz'/name=(as_is,shortened)/reent=multi/float=ieee/ieee=denorm/include=(sys$common:['javaver'.include],sys$common:['javaver'.include.'osnam'])
$ exit

nc.com:

$ @jcc
$ cc c
$ javac NC.java Data.java
$ javah -jni "NC"
$ jcc nc
$ link/share nc + c + sys$input/opt
case_sensitive=YES
SYMBOL_VECTOR=(Java_NC_add=PROCEDURE,-
               Java_NC_conc=PROCEDURE,-
               Java_NC_modify=PROCEDURE)
case_sensitive=NO
java$java_shr/share
$
$ define/nolog ncshr "''f$parse("nc.exe")'"
$ exit
$ @nc
$ groovy GrNC.groovy

P.pas:

module P(input, output);

type
   varstr = varying [256] of char;
   data = record
             iv : integer;
             sv : varstr;
          end;

[global]
function add(a, b : integer) : integer;

begin
   add := a + b;
end;

[global]
function conc(a, b : varstr) : varstr;

begin
   conc := a + b;
end;

[global]
function modify(o : data) : data;

var
   temp : data;

begin
   temp.iv := o.iv + 1;
   temp.sv := o.sv + 'X';
   modify := temp;
end;

end.

Data.java:

public class Data {
    private int iv;
    private String sv;
    public Data() {
        this(0, "");
    }
    public Data(int iv, String sv) {
        super();
        this.iv = iv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
}

NP.java:

public class NP {
    static {
        System.loadLibrary("npshr");
    }
    public static native int add(int a, int b);
    public static native String conc(String a, String b);
    public static native Data modify(Data o);
}

NP.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "NP.h"                                                          

struct data
{
    int iv;
    char sv[2 + 256];
};

int ADD(int *a, int *b);
void CONC(char *res, const char *a, const char *b);
void MODIFY(struct data *o2, const struct data *o);

JNIEXPORT jint JNICALL Java_NP_add(JNIEnv *ctx, jclass obj, jint a, jint b)
{
    return ADD(&a, &b);
}

JNIEXPORT jstring JNICALL Java_NP_conc(JNIEnv *ctx, jclass obj, jstring a, jstring b)
{
    const char *na = (*ctx)->GetStringUTFChars(ctx, a, 0);
    const char *nb = (*ctx)->GetStringUTFChars(ctx, b, 0);
    char *na2 = _malloc32(258);
    *((short *)na2) = strlen(na);
    memcpy(na2 + 2, na, strlen(na));
    char *nb2 = _malloc32(258);
    memcpy(nb2 + 2, nb, strlen(nb));
    *((short *)nb2) = strlen(nb);
    char *res = malloc(258);
    CONC(res, na2, nb2);
    short reslen = *((short *)res);
    char *res2 = malloc(reslen + 1);
    memcpy(res2, res + 2, reslen);
    res2[reslen] = 0;
    free(na2);
    free(nb2);
    (*ctx)->ReleaseStringUTFChars(ctx, a, na);
    (*ctx)->ReleaseStringUTFChars(ctx, b, nb);
    return (*ctx)->NewStringUTF(ctx, res2);
}

JNIEXPORT jobject JNICALL Java_NP_modify(JNIEnv *ctx, jclass obj , jobject o)
{
    jclass dataclz = (*ctx)->FindClass(ctx, "Data");
    jfieldID ivfld = (*ctx)->GetFieldID(ctx, dataclz, "iv", "I");
    jfieldID svfld = (*ctx)->GetFieldID(ctx, dataclz, "sv", "Ljava/lang/String;");
    jmethodID ctor = (*ctx)->GetMethodID(ctx, dataclz, "<init>", "()V");
    struct data no;
    no.iv = (*ctx)->GetIntField(ctx, o, ivfld);
    jstring temp = (*ctx)->GetObjectField(ctx, o, svfld);
    const char *ntemp = (*ctx)->GetStringUTFChars(ctx, temp, 0);
    *((short *)no.sv) = strlen(ntemp);
    strcpy(no.sv + 2, ntemp);
    (*ctx)->ReleaseStringUTFChars(ctx, temp, ntemp);
    struct data no2;
    MODIFY(&no2, &no);
    jint iv = no2.iv;
    char *nsv = malloc(257);
    short nsvlen = *((short *)no2.sv);
    memcpy(nsv, no2.sv + 2, nsvlen);
    nsv[nsvlen] = 0;
    jstring sv = (*ctx)->NewStringUTF(ctx, nsv);
    jobject o2 = (*ctx)->NewObject(ctx, dataclz, ctor);
    (*ctx)->SetIntField(ctx, o2, ivfld, iv);
    (*ctx)->SetObjectField(ctx, o2, svfld, sv);
    return o2;
}

GrNP.groovy:

v1 = 123
v2 = 456
v3 = NP.add(v1, v2)
println("$v1 + $v2 = $v3")
s1 = "ABC"
s2 = "DEF"
s3 = NP.conc(s1, s2)
println("$s1 + $s2 = $s3")
o = new Data(iv: 123, sv: "ABC")
println("iv=${o.iv} sv=${o.sv}")
o2 = NP.modify(o)
println("iv=${o2.iv} sv=${o2.sv}")

jcc.com:

$ javaver = "unknown"
$ osnam = "unknown"
$ ptrsiz = 0
$ if f$getsyi("arch_name") .eqs. "Alpha"
$ then
$    javaver = "java$150"
$    osnam ="vms"
$    ptrsiz = 32
$ endif
$ if f$getsyi("arch_name") .eqs. "IA64" .or. f$getsyi("arch_name") .eqs. "x86_64"
$ then
$    javaver = "openjdk$80"
$    osnam = "openvms"
$    ptrsiz = 64
$ endif
$ jcc :== cc/pointer='ptrsiz'/name=(as_is,shortened)/reent=multi/float=ieee/ieee=denorm/include=(sys$common:['javaver'.include],sys$common:['javaver'.include.'osnam'])
$ exit

np.com:

$ @jcc
$ pas p
$ javac NP.java Data.java
$ javah -jni "NP"
$ jcc np
$ link/share np + p + sys$input/opt
case_sensitive=YES
SYMBOL_VECTOR=(Java_NP_add=PROCEDURE,-
               Java_NP_conc=PROCEDURE,-
               Java_NP_modify=PROCEDURE)
case_sensitive=NO
java$java_shr/share
$
$ define/nolog npshr "''f$parse("np.exe")'"
$ exit
$ @np
$ groovy GrNP.groovy

B.bas:

function integer add(integer a, integer b)
    add = a + b
end function
!
function string conc(string a, string b)
    conc = a + b
end function
!
function xdata xmodify(xdata o)
    %include "data.bas"
    declare xdata temp
    declare integer ix
    ix = len(o::sv)
    while ix > 1 and (mid$(o::sv, ix, 1) = chr$(32) or mid$(o::sv, ix, 1) = chr$(0))
        ix = ix - 1
    next
    temp::iv = o::iv + 1
    temp::sv = mid$(o::sv, 1, ix) + "X"
    xmodify = temp
end function

Data.java:

public class Data {
    private int iv;
    private String sv;
    public Data() {
        this(0, "");
    }
    public Data(int iv, String sv) {
        super();
        this.iv = iv;
        this.sv = sv;
    }
    public int getIv() {
        return iv;
    }
    public void setIv(int iv) {
        this.iv = iv;
    }
    public String getSv() {
        return sv;
    }
    public void setSv(String sv) {
        this.sv = sv;
    }
}

NB.java:

public class NB {
    static {
        System.loadLibrary("nbshr");
    }
    public static native int add(int a, int b);
    public static native String conc(String a, String b);
    public static native Data modify(Data o);
}

NB.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <descrip.h>

#include "NB.h"                                                          

struct data
{
    int iv;
    char sv[256];
};

int ADD(int *a, int *b);
void CONC(struct dsc$descriptor_d *res, const struct dsc$descriptor_s *a, const struct dsc$descriptor_s *b);
void XMODIFY(struct data *o2, const struct data *o);

JNIEXPORT jint JNICALL Java_NB_add(JNIEnv *ctx, jclass obj, jint a, jint b)
{
    return ADD(&a, &b);
}

JNIEXPORT jstring JNICALL Java_NB_conc(JNIEnv *ctx, jclass obj, jstring a, jstring b)
{
    const char *na = (*ctx)->GetStringUTFChars(ctx, a, 0);
    const char *nb = (*ctx)->GetStringUTFChars(ctx, b, 0);
    char *na2 = _malloc32(strlen(na));
    memcpy(na2, na, strlen(na));
    char *nb2 = _malloc32(strlen(nb));
    memcpy(nb2, nb, strlen(nb));
    struct dsc$descriptor_s na2desc;
    na2desc.dsc$b_class = DSC$K_CLASS_S;
    na2desc.dsc$b_dtype = DSC$K_DTYPE_T;
    na2desc.dsc$w_length = strlen(na);
    na2desc.dsc$a_pointer = na2;
    struct dsc$descriptor_s nb2desc;
    nb2desc.dsc$b_class = DSC$K_CLASS_S;
    nb2desc.dsc$b_dtype = DSC$K_DTYPE_T;
    nb2desc.dsc$w_length = strlen(nb);
    nb2desc.dsc$a_pointer = nb2;
    struct dsc$descriptor_d resdesc;
    resdesc.dsc$b_class = DSC$K_CLASS_D;
    resdesc.dsc$b_dtype = DSC$K_DTYPE_T;
    resdesc.dsc$w_length = 0;
    resdesc.dsc$a_pointer = NULL;
    CONC(&resdesc, &na2desc, &nb2desc);
    short reslen = resdesc.dsc$w_length;
    char *res = malloc(reslen + 1);
    memcpy(res, resdesc.dsc$a_pointer, reslen);
    res[reslen] = 0;
    free(na2);
    free(nb2);
    (*ctx)->ReleaseStringUTFChars(ctx, a, na);
    (*ctx)->ReleaseStringUTFChars(ctx, b, nb);
    return (*ctx)->NewStringUTF(ctx, res);
}

JNIEXPORT jobject JNICALL Java_NB_modify(JNIEnv *ctx, jclass obj , jobject o)
{
    jclass dataclz = (*ctx)->FindClass(ctx, "Data");
    jfieldID ivfld = (*ctx)->GetFieldID(ctx, dataclz, "iv", "I");
    jfieldID svfld = (*ctx)->GetFieldID(ctx, dataclz, "sv", "Ljava/lang/String;");
    jmethodID ctor = (*ctx)->GetMethodID(ctx, dataclz, "<init>", "()V");
    struct data no;
    memset(&no, 0, sizeof(no));
    no.iv = (*ctx)->GetIntField(ctx, o, ivfld);
    jstring temp = (*ctx)->GetObjectField(ctx, o, svfld);
    const char *ntemp = (*ctx)->GetStringUTFChars(ctx, temp, 0);
    strcpy(no.sv, ntemp);
    (*ctx)->ReleaseStringUTFChars(ctx, temp, ntemp);
    struct data no2;
    memset(&no2, 0, sizeof(no2));
    XMODIFY(&no2, &no);
    jint iv = no2.iv;
    char *nsv = malloc(257);
    memcpy(nsv, no2.sv, 256);
    nsv[256] = 0;
    int ix = 256;
    while(ix > 0 && (nsv[ix-1] == ' ' || nsv[ix-1] == 0)) ix--;
    nsv[ix] = 0;
    jstring sv = (*ctx)->NewStringUTF(ctx, nsv);
    jobject o2 = (*ctx)->NewObject(ctx, dataclz, ctor);
    (*ctx)->SetIntField(ctx, o2, ivfld, iv);
    (*ctx)->SetObjectField(ctx, o2, svfld, sv);
    return o2;
}

GrNB.groovy:

v1 = 123
v2 = 456
v3 = NB.add(v1, v2)
println("$v1 + $v2 = $v3")
s1 = "ABC"
s2 = "DEF"
s3 = NB.conc(s1, s2)
println("$s1 + $s2 = $s3")
o = new Data(iv: 123, sv: "ABC")
println("iv=${o.iv} sv=${o.sv}")
o2 = NB.modify(o)
println("iv=${o2.iv} sv=${o2.sv}")

jcc.com:

$ javaver = "unknown"
$ osnam = "unknown"
$ ptrsiz = 0
$ if f$getsyi("arch_name") .eqs. "Alpha"
$ then
$    javaver = "java$150"
$    osnam ="vms"
$    ptrsiz = 32
$ endif
$ if f$getsyi("arch_name") .eqs. "IA64" .or. f$getsyi("arch_name") .eqs. "x86_64"
$ then
$    javaver = "openjdk$80"
$    osnam = "openvms"
$    ptrsiz = 64
$ endif
$ jcc :== cc/pointer='ptrsiz'/name=(as_is,shortened)/reent=multi/float=ieee/ieee=denorm/include=(sys$common:['javaver'.include],sys$common:['javaver'.include.'osnam'])
$ exit

nb.com:

$ @jcc
$ bas b
$ javac NB.java Data.java
$ javah -jni "NB"
$ jcc nb
$ link/share nb + b + sys$input/opt
case_sensitive=YES
SYMBOL_VECTOR=(Java_NB_add=PROCEDURE,-
               Java_NB_conc=PROCEDURE,-
               Java_NB_modify=PROCEDURE)
case_sensitive=NO
java$java_shr/share
$
$ define/nolog nbshr "''f$parse("nb.exe")'"
$ exit
$ @nb
$ groovy GrNB.groovy

VMSCALL library:

data.h:

#ifndef DATA_H
#define DATA_H

struct data
{
    int iv;
    char sv[256];
};

#endif

C.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "data.h"

int add(int a, int b)
{
    return a + b;
}

char *conc(char *a, char *b)
{
    char *res = malloc(strlen(a) + strlen(b) + 1);
    strcpy(res, a);
    strcat(res, b);
    return res;
}

struct data *modify(struct data *o)
{
    struct data *res = malloc(sizeof(struct data));
    res->iv = o->iv + 1;
    strcpy(res->sv, o->sv);
    strcat(res->sv, "X");
    return res;
}

CWrap.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "data.h"

int add(int a, int b);
char *conc(char *a, char *b);
struct data *modify(struct data *o);

int c_add(int a, int b, int *c)
{
    *c = add(a, b);
    return 1;
}

int c_conc(char *a, char *b, char *c)
{
    char *temp = conc(a, b); 
    strcpy(c, temp);
    free(temp);
    return 1;
}

int c_modify(struct data *o, struct data *o2)
{
    struct data *temp = modify(o);
    memcpy(o2, temp, sizeof(struct data));
    free(temp);
    return 1;
}

GrCallC.groovy:

import dk.vajhoej.record.*
import dk.vajhoej.vms.call.*
import static dk.vajhoej.vms.call.VMS.*

@Struct
class Data {
    @StructField(n=0,type=FieldType.INT4)
    int iv;
    @StructField(n=1,type=FieldType.FIXSTRNULTERM,length=256)
    String sv
    static Data empty() {
        return new Data(iv: 0, sv: "".padRight(256))
    }
}

v1 = 123
v2 = 456
v1wrap = new LongWord(v1)
v2wrap = new LongWord(v2)
v3wrap = new LongWord()
call("cwrap", "c_add", pass(v1wrap).byValue().readOnly(),
                       pass(v2wrap).byValue().readOnly(),
                       pass(v3wrap).byReference().writeOnly())
v3 = v3wrap.value
println("$v1 + $v2 = $v3")
s1 = "ABC"
s2 = "DEF"
s1wrap = new NullTermCharacterString(s1)
s2wrap = new NullTermCharacterString(s2)
s3wrap = new NullTermCharacterString(256)
call("cwrap", "c_conc", pass(s1wrap).byReference().readOnly(),
                        pass(s2wrap).byReference().readOnly(),
                        pass(s3wrap).byReference().writeOnly())
s3 = s3wrap.toString()
println("$s1 + $s2 = $s3")
o = new Data(iv: 123, sv: "ABC")
println("iv=${o.iv} sv=${o.sv}")
owrap = new Block(o)
o2wrap =  new Block(Data.empty())
call("cwrap", "c_modify", pass(owrap).byReference().readOnly(),
                          pass(o2wrap).byReference().writeOnly())
o2 = o2wrap.getObject(Data.class)
println("iv=${o2.iv} sv=${o2.sv}")

cwrap.com:

$ cc c
$ cc cwrap
$ link/share cwrap + c + sys$input/opt
SYMBOL_VECTOR=(c_add=PROCEDURE,-
               c_conc=PROCEDURE,-
               c_modify=PROCEDURE)
$
$ define/nolog cwrap sys$disk:[]cwrap
$ exit
$ groovy_cp = "/vmsjavacallpath/vmscall.jar:/vmsjavacallpath/record.jar"
$ @cwrap
$ groovy GrCallC.groovy

P.pas:

module P(input, output);

type
   varstr = varying [256] of char;
   data = record
             iv : integer;
             sv : varstr;
          end;

function add(a, b : integer) : integer;

begin
   add := a + b;
end;

function conc(a, b : varstr) : varstr;

begin
   conc := a + b;
end;

function modify(o : data) : data;

var
   temp : data;

begin
   temp.iv := o.iv + 1;
   temp.sv := o.sv + 'X';
   modify := temp;
end;

end.

PWrap.pas:

[inherit('P')]
module PWrap(input, output);

[global]
function p_add(a, b : integer; var c : integer) : integer; 

begin
   c := add(a, b);
   p_add := 1;
end;

[global]
function p_conc(a, b : varstr; var c : varstr) : integer;

begin
   c := conc(a, b);
   p_conc := 1;
end;

[global]
function p_modify(o : Data; var o2 : Data) : integer;

begin
   o2 := modify(o);
   p_modify := 1;
end;

end.

GrCallP.groovy:

import dk.vajhoej.record.*
import dk.vajhoej.vms.call.*
import static dk.vajhoej.vms.call.VMS.*

@Struct
class Data {
    @StructField(n=0,type=FieldType.INT4)
    int iv;
    @StructField(n=1,type=FieldType.VARSTR)
    String sv
    static Data empty() {
        return new Data(iv: 0, sv: "".padRight(256))
    }
}

v1 = 123
v2 = 456
v1wrap = new LongWord(v1)
v2wrap = new LongWord(v2)
v3wrap = new LongWord()
call("pwrap", "p_add", pass(v1wrap).byReference().readOnly(),
                       pass(v2wrap).byReference().readOnly(),
                       pass(v3wrap).byReference().writeOnly())
v3 = v3wrap.value
println("$v1 + $v2 = $v3")
s1 = "ABC"
s2 = "DEF"
s1wrap = new VariableCharacterString(s1)
s2wrap = new VariableCharacterString(s2)
s3wrap = new VariableCharacterString(256)
call("pwrap", "p_conc", pass(s1wrap).byReference().readOnly(),
                        pass(s2wrap).byReference().readOnly(),
                        pass(s3wrap).byReference().writeOnly())
s3 = s3wrap.toString()
println("$s1 + $s2 = $s3")
o = new Data(iv: 123, sv: "ABC")
println("iv=${o.iv} sv=${o.sv}")
owrap = new Block(o)
o2wrap =  new Block(Data.empty())
call("pwrap", "p_modify", pass(owrap).byReference().readOnly(),
                          pass(o2wrap).byReference().writeOnly())
o2 = o2wrap.getObject(Data.class)
println("iv=${o2.iv} sv=${o2.sv}")

pwrap.com:

$ pas/env p
$ pas pwrap
$ link/share pwrap + p + sys$input/opt
SYMBOL_VECTOR=(p_add=PROCEDURE,-
               p_conc=PROCEDURE,-
               p_modify=PROCEDURE)
$
$ define/nolog pwrap sys$disk:[]pwrap
$ exit
$ groovy_cp = "/vmsjavacallpath/vmscall.jar:/vmsjavacallpath/record.jar"
$ @pwrap
$ groovy GrCallP.groovy

data.bas:

record xdata
    integer iv
    string sv = 256
end record

B.bas:

function integer add(integer a, integer b)
    add = a + b
end function
!
function string conc(string a, string b)
    conc = a + b
end function
!
function xdata xmodify(xdata o)
    %include "data.bas"
    declare xdata temp
    declare integer ix
    ix = len(o::sv)
    while ix > 1 and (mid$(o::sv, ix, 1) = chr$(32) or mid$(o::sv, ix, 1) = chr$(0))
        ix = ix - 1
    next
    temp::iv = o::iv + 1
    temp::sv = mid$(o::sv, 1, ix) + "X"
    xmodify = temp
end function

BWrap.bas:

function integer b_add(integer a, integer b, integer c)
    external integer function add(integer, integer)
    c = add(a, b)
    b_add = 1
end function
!
function integer b_conc(string a, string b, string c)
    external string function conc(string, string)
    c = conc(a, b)
    b_conc = 1
end function
!
function integer b_modify(xdata o, xdata o2)
    %include "data.bas"
    external xdata function xmodify(xdata)
    o2 = xmodify(o)
    b_modify = 1
end function

GrCallB.groovy:

import dk.vajhoej.record.*
import dk.vajhoej.vms.call.*
import static dk.vajhoej.vms.call.VMS.*

@Struct
class Data {
    @StructField(n=0,type=FieldType.INT4)
    int iv;
    @StructField(n=1,type=FieldType.FIXSTR,length=256,pad=true)
    String sv
    static Data empty() {
        return new Data(iv: 0, sv: "")
    }
}

v1 = 123
v2 = 456
v1wrap = new LongWord(v1)
v2wrap = new LongWord(v2)
v3wrap = new LongWord()
call("bwrap", "b_add", pass(v1wrap).byReference().readOnly(),
                       pass(v2wrap).byReference().readOnly(),
                       pass(v3wrap).byReference().writeOnly())
v3 = v3wrap.value
println("$v1 + $v2 = $v3")
s1 = "ABC"
s2 = "DEF"
s1wrap = new CharacterString(s1)
s2wrap = new CharacterString(s2)
s3wrap = new CharacterString(256)
call("bwrap", "b_conc", pass(s1wrap).byDescriptor().readOnly(),
                        pass(s2wrap).byDescriptor().readOnly(),
                        pass(s3wrap).byDescriptor().writeOnly())
s3 = s3wrap.toString()
println("$s1 + $s2 = $s3")
o = new Data(iv: 123, sv: "ABC")
println("iv=${o.iv} sv=${o.sv}")
owrap = new Block(o)
o2wrap = new Block(Data.empty())
call("bwrap", "b_modify", pass(owrap).byReference().readOnly(),
                          pass(o2wrap).byReference().writeOnly())
o2 = o2wrap.getObject(Data.class)
println("iv=${o2.iv} sv=${o2.sv}")

bwrap.com:

$ bas b
$ bas bwrap
$ link/share bwrap + b + sys$input/opt
SYMBOL_VECTOR=(b_add=PROCEDURE,-
               b_conc=PROCEDURE,-
               b_modify=PROCEDURE)
$
$ define/nolog bwrap sys$disk:[]bwrap
$ exit
$ groovy_cp = "/vmsjavacallpath/vmscall.jar:/vmsjavacallpath/record.jar"
$ @bwrap
$ groovy GrCallB.groovy

Native calling Groovy:

JNI can also be used to load a JVM and execute Java byte code in it.

Again C code is required - non-trivial C code.

Note that the call stack is:

Native calling Groovy

Note that creating and starting a JVM is a very heavy operation.

Gr.groovy:

class GrData {
    int iv
    String sv
}

class Gr {
    static int add(int a, int b) {
        return a + b
    }
    static String conc(String a, String b) {
        return a + b
    }
    static GrData modify(GrData o) {
        return new GrData(iv: o.iv + 1, sv: o.sv + "X")
    }
}

data.h:

#ifndef DATA_H
#define DATA_H

struct data
{
    int iv;
    char sv[256];
};

#endif

CWrap.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <signal.h>
#include <ssdef.h>

#include <jni.h>

#include "data.h"

static JNIEnv *ctx;
static JavaVM *jvm;
static jclass clz;
static jmethodID add_method;
static jmethodID conc_method;
static jmethodID modify_method;
static jclass dataclz;
static jfieldID iv_field;
static jfieldID sv_field;
static jmethodID ctor;

void C_INIT_JVM()
{
    DECC$CRTL_INIT();
    JavaVMOption options[2];
    options[0].optionString = "-Xmx256m";
#ifndef __ALPHA
    options[1].optionString = "-Djava.class.path=.:/disk0/net/groovy/groovy-4.0.12/lib/groovy-4.0.12.jar";
#else
    options[1].optionString = "-Djava.class.path=.:/disk2/arne/groovy/groovy-2_2_2/groovy-all-2_2_2.jar";
#endif
    JavaVMInitArgs vm_args;
#ifndef __ALPHA
    vm_args.version = JNI_VERSION_1_8;
#else
    vm_args.version = JNI_VERSION_1_4;
#endif
    vm_args.nOptions = 2;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = JNI_FALSE;
    jint stat = JNI_CreateJavaVM(&jvm, (void **)&ctx, &vm_args);
    if(stat < 0)
    {
        fprintf(stderr, "Error creating JVM\n");
        exit(SS$_ABORT);
    }
}

void C_LOAD_CLASSES()
{
    clz = (*ctx)->FindClass(ctx, "Gr");
    if(clz == NULL)
    {
        fprintf(stderr, "Error loading Gr class\n");
        exit(SS$_ABORT);
    }
    add_method = (*ctx)->GetStaticMethodID(ctx, clz, "add", "(II)I");
    if(add_method == NULL)
    {
        fprintf(stderr, "Error getting add method\n");
        exit(SS$_ABORT);
    }
    conc_method = (*ctx)->GetStaticMethodID(ctx, clz, "conc", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
    if(conc_method == NULL)
    {
        fprintf(stderr, "Error getting conc method\n");
        exit(SS$_ABORT);
    }
    modify_method = (*ctx)->GetStaticMethodID(ctx, clz, "modify", "(LGrData;)LGrData;");
    if(modify_method == NULL)
    {
        fprintf(stderr, "Error getting modify method\n");
        exit(SS$_ABORT);
    }
    dataclz = (*ctx)->FindClass(ctx, "GrData");
    if(dataclz == NULL)
    {
        fprintf(stderr, "Error loading GrData class\n");
        exit(SS$_ABORT);
    }
    iv_field = (*ctx)->GetFieldID(ctx, dataclz, "iv", "I");
    if(iv_field == NULL)
    {
        fprintf(stderr, "Error getting iv field\n");
        exit(SS$_ABORT);
    }
    sv_field = (*ctx)->GetFieldID(ctx, dataclz, "sv", "Ljava/lang/String;");
    if(sv_field == NULL)
    {
        fprintf(stderr, "Error getting sv field\n");
        exit(SS$_ABORT);
    }
    ctor = (*ctx)->GetMethodID(ctx, dataclz, "<init>", "()V");
    if(ctor == NULL)
    {
        fprintf(stderr, "Error getting constructor\n");
        exit(SS$_ABORT);
    }
}

int C_ADD(int a, int b)
{
    jint ja = a;
    jint jb = b;
    jint res = (*ctx)->CallStaticIntMethod(ctx, clz, add_method, ja, jb);
    return res;
}

char *C_CONC(char *a, char *b)
{
    // char* a -> char* a2 -> jstring ja
    char *a2 = malloc(strlen(a) + 1);
    strcpy(a2, a);
    jstring ja = (*ctx)->NewStringUTF(ctx, a2);
    // char* b -> char* b2 -> jstring jb
    char *b2 = malloc(strlen(b) + 1);
    strcpy(b2, b);
    jstring jb = (*ctx)->NewStringUTF(ctx, b2);
    // call and free in args
    jstring jres = (*ctx)->CallStaticObjectMethod(ctx, clz, conc_method, ja, jb);
    (*ctx)->ReleaseStringUTFChars(ctx, ja, a2);
    (*ctx)->ReleaseStringUTFChars(ctx, jb, b2);
    //jstring jres -> char* res -> 32 bit char* res2
    const char *res = (*ctx)->GetStringUTFChars(ctx, jres, 0);
    char *res2 = _malloc32(strlen(res) + 1);
    strcpy(res2, res);
    (*ctx)->ReleaseStringUTFChars(ctx, jres, res);
    return res2;
}

struct data *C_MODIFY(struct data *o)
{
    // jobject jo = new GrData(o->iv, o->sv)
    jint iv = o->iv;
    jstring sv = (*ctx)->NewStringUTF(ctx, o->sv);
    jobject jo = (*ctx)->NewObject(ctx, dataclz, ctor);
    (*ctx)->SetIntField(ctx, jo, iv_field, iv);
    (*ctx)->SetObjectField(ctx, jo, sv_field, sv);
    // call and free input arg
    jobject jres = (*ctx)->CallStaticObjectMethod(ctx, clz, modify_method, jo);
    (*ctx)->ReleaseStringUTFChars(ctx, sv, o->sv);
    // res->iv = jres->iv
    // res->sv = jres->sv
    struct data *res = _malloc32(sizeof(struct data));
    memset(res, 0, sizeof(struct data));
    res->iv = (*ctx)->GetIntField(ctx, jres, iv_field);
    jstring temp = (*ctx)->GetObjectField(ctx, jres, sv_field);
    const char *ntemp = (*ctx)->GetStringUTFChars(ctx, temp, 0);
    strcpy(res->sv, ntemp);
    (*ctx)->ReleaseStringUTFChars(ctx, temp, ntemp);
    return res;
}

void C_DESTROY_JVM()
{
    (*jvm)->DestroyJavaVM(jvm);
}

CWrap.h:

#ifndef CWRAP_H
#define CWRAP_H

#include "data.h"

void c_init_jvm();
void c_load_classes();
int c_add(int a, int b);
char *c_conc(char *a, char *b);
struct data *c_modify(struct data *o);
void c_destroy_jvm();

#endif

C.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "data.h"
#include "CWrap.h"

int main()
{
    c_init_jvm();
    c_load_classes();
    int v1 = 123;
    int v2 = 456;
    int v3 = c_add(v1, v2);
    printf("%d + %d = %d\n", v1, v2, v3);
    char *s1 = "ABC";
    char *s2 = "DEF";
    char *s3 = c_conc(s1, s2);
    printf("%s + %s = %s\n", s1, s2, s3);
    struct data o;
    o.iv = 123;
    strcpy(o.sv, "ABC");
    printf("iv=%d sv=%s\n", o.iv, o.sv);
    struct data *o2 = c_modify(&o);
    printf("iv=%d sv=%s\n", o2->iv, o2->sv);
    c_destroy_jvm();
    return 0;
}

jcc.com:

$ javaver = "unknown"
$ osnam = "unknown"
$ ptrsiz = 0
$ if f$getsyi("arch_name") .eqs. "Alpha"
$ then
$    javaver = "java$150"
$    osnam ="vms"
$    ptrsiz = 32
$ endif
$ if f$getsyi("arch_name") .eqs. "IA64" .or. f$getsyi("arch_name") .eqs. "x86_64"
$ then
$    javaver = "openjdk$80"
$    osnam = "openvms"
$    ptrsiz = 64
$ endif
$ jcc :== cc/pointer='ptrsiz'/name=(as_is,shortened)/reent=multi/float=ieee/ieee=denorm/include=(sys$common:['javaver'.include],sys$common:['javaver'.include.'osnam'])
$ exit
$ @jcc
$ groovyc Gr.groovy
$ jcc CWrap
$ cc c
$ link c + cwrap + sys$input/opt
java$java_shr/share
java$jvm_shr/share
$
$ run c

Gr.groovy:

class GrData {
    int iv
    String sv
}

class Gr {
    static int add(int a, int b) {
        return a + b
    }
    static String conc(String a, String b) {
        return a + b
    }
    static GrData modify(GrData o) {
        return new GrData(iv: o.iv + 1, sv: o.sv + "X")
    }
}

data.h:

#ifndef DATA_H
#define DATA_H

struct data
{
    int iv;
    char sv[256];
};

#endif

CWrap.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <signal.h>
#include <ssdef.h>

#include <jni.h>

#include "data.h"

static JNIEnv *ctx;
static JavaVM *jvm;
static jclass clz;
static jmethodID add_method;
static jmethodID conc_method;
static jmethodID modify_method;
static jclass dataclz;
static jfieldID iv_field;
static jfieldID sv_field;
static jmethodID ctor;

void C_INIT_JVM()
{
    DECC$CRTL_INIT();
    JavaVMOption options[2];
    options[0].optionString = "-Xmx256m";
#ifndef __ALPHA
    options[1].optionString = "-Djava.class.path=.:/disk0/net/groovy/groovy-4.0.12/lib/groovy-4.0.12.jar";
#else
    options[1].optionString = "-Djava.class.path=.:/disk2/arne/groovy/groovy-2_2_2/groovy-all-2_2_2.jar";
#endif
    JavaVMInitArgs vm_args;
#ifndef __ALPHA
    vm_args.version = JNI_VERSION_1_8;
#else
    vm_args.version = JNI_VERSION_1_4;
#endif
    vm_args.nOptions = 2;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = JNI_FALSE;
    jint stat = JNI_CreateJavaVM(&jvm, (void **)&ctx, &vm_args);
    if(stat < 0)
    {
        fprintf(stderr, "Error creating JVM\n");
        exit(SS$_ABORT);
    }
}

void C_LOAD_CLASSES()
{
    clz = (*ctx)->FindClass(ctx, "Gr");
    if(clz == NULL)
    {
        fprintf(stderr, "Error loading Gr class\n");
        exit(SS$_ABORT);
    }
    add_method = (*ctx)->GetStaticMethodID(ctx, clz, "add", "(II)I");
    if(add_method == NULL)
    {
        fprintf(stderr, "Error getting add method\n");
        exit(SS$_ABORT);
    }
    conc_method = (*ctx)->GetStaticMethodID(ctx, clz, "conc", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
    if(conc_method == NULL)
    {
        fprintf(stderr, "Error getting conc method\n");
        exit(SS$_ABORT);
    }
    modify_method = (*ctx)->GetStaticMethodID(ctx, clz, "modify", "(LGrData;)LGrData;");
    if(modify_method == NULL)
    {
        fprintf(stderr, "Error getting modify method\n");
        exit(SS$_ABORT);
    }
    dataclz = (*ctx)->FindClass(ctx, "GrData");
    if(dataclz == NULL)
    {
        fprintf(stderr, "Error loading GrData class\n");
        exit(SS$_ABORT);
    }
    iv_field = (*ctx)->GetFieldID(ctx, dataclz, "iv", "I");
    if(iv_field == NULL)
    {
        fprintf(stderr, "Error getting iv field\n");
        exit(SS$_ABORT);
    }
    sv_field = (*ctx)->GetFieldID(ctx, dataclz, "sv", "Ljava/lang/String;");
    if(sv_field == NULL)
    {
        fprintf(stderr, "Error getting sv field\n");
        exit(SS$_ABORT);
    }
    ctor = (*ctx)->GetMethodID(ctx, dataclz, "<init>", "()V");
    if(ctor == NULL)
    {
        fprintf(stderr, "Error getting constructor\n");
        exit(SS$_ABORT);
    }
}

int C_ADD(int a, int b)
{
    jint ja = a;
    jint jb = b;
    jint res = (*ctx)->CallStaticIntMethod(ctx, clz, add_method, ja, jb);
    return res;
}

char *C_CONC(char *a, char *b)
{
    // char* a -> char* a2 -> jstring ja
    char *a2 = malloc(strlen(a) + 1);
    strcpy(a2, a);
    jstring ja = (*ctx)->NewStringUTF(ctx, a2);
    // char* b -> char* b2 -> jstring jb
    char *b2 = malloc(strlen(b) + 1);
    strcpy(b2, b);
    jstring jb = (*ctx)->NewStringUTF(ctx, b2);
    // call and free in args
    jstring jres = (*ctx)->CallStaticObjectMethod(ctx, clz, conc_method, ja, jb);
    (*ctx)->ReleaseStringUTFChars(ctx, ja, a2);
    (*ctx)->ReleaseStringUTFChars(ctx, jb, b2);
    //jstring jres -> char* res -> 32 bit char* res2
    const char *res = (*ctx)->GetStringUTFChars(ctx, jres, 0);
    char *res2 = _malloc32(strlen(res) + 1);
    strcpy(res2, res);
    (*ctx)->ReleaseStringUTFChars(ctx, jres, res);
    return res2;
}

struct data *C_MODIFY(struct data *o)
{
    // jobject jo = new GrData(o->iv, o->sv)
    jint iv = o->iv;
    jstring sv = (*ctx)->NewStringUTF(ctx, o->sv);
    jobject jo = (*ctx)->NewObject(ctx, dataclz, ctor);
    (*ctx)->SetIntField(ctx, jo, iv_field, iv);
    (*ctx)->SetObjectField(ctx, jo, sv_field, sv);
    // call and free input arg
    jobject jres = (*ctx)->CallStaticObjectMethod(ctx, clz, modify_method, jo);
    (*ctx)->ReleaseStringUTFChars(ctx, sv, o->sv);
    // res->iv = jres->iv
    // res->sv = jres->sv
    struct data *res = _malloc32(sizeof(struct data));
    memset(res, 0, sizeof(struct data));
    res->iv = (*ctx)->GetIntField(ctx, jres, iv_field);
    jstring temp = (*ctx)->GetObjectField(ctx, jres, sv_field);
    const char *ntemp = (*ctx)->GetStringUTFChars(ctx, temp, 0);
    strcpy(res->sv, ntemp);
    (*ctx)->ReleaseStringUTFChars(ctx, temp, ntemp);
    return res;
}

void C_DESTROY_JVM()
{
    (*jvm)->DestroyJavaVM(jvm);
}

PWrap.pas:

module PWrap(input, output);

[external('decc$free')]
procedure free_c_str(ptr : c_str_t); external;

type
   string = varying [256] of char;
   data = record
             iv : integer;
             sv : string;
          end;
   cdata = record
              iv : integer;
              sv : packed array [1..256] of char;
           end;
   ptr_cdata = ^cdata;

[external('C_INIT_JVM')]
procedure c_init_jvm; external;

[external('C_LOAD_CLASSES')]
procedure c_load_classes; external;

[external('C_ADD')]
function c_add(%IMMED a : integer; %IMMED b : integer) : integer; external;

[external('C_CONC')]
function c_conc(%IMMED a : c_str_t; %IMMED b : c_str_t) : c_str_t; external;

[external('C_MODIFY')]
function c_modify(%REF o : cdata) : ptr_cdata; external;

[external('C_DESTROY_JVM')]
procedure c_destroy_jvm; external;

procedure p_init_jvm;

begin
   c_init_jvm;
end;

procedure p_load_classes;

begin
   c_load_classes;
end;

function p_add(a, b : integer) : integer;

begin
   p_add := c_add(a, b);
end;

function p_conc(a, b : string) : string;

var
   a2, b2, res : c_str_t;

begin
   a2 := malloc_c_str(a);
   b2 := malloc_c_str(b);
   res := c_conc(a2, b2);
   free_c_str(a2);
   free_c_str(b2);
   p_conc := pas_str(res);
   free_c_str(res);
end;

function p_modify(o : data) : data;

var
   o2 : cdata;
   cres : ptr_cdata;
   res : data;

begin
   o2.iv := o.iv;
   o2.sv := o.sv.body;
   cres := c_modify(o2);
   res.iv := cres^.iv;
   res.sv.body := cres^.sv;
   res.sv.length := index(res.sv.body, chr(0));
   p_modify := res;
end;

procedure p_destroy_jvm;

begin
   c_destroy_jvm;
end;

end.

P.pas:

[inherit('PWrap')]
program p(input,output);

var
   v1, v2, v3 : integer;
   s1, s2, s3 : string;
   o, o2 : data;

begin
   p_init_jvm;
   p_load_classes;
   v1 := 123;
   v2 := 456;
   v3 := p_add(v1, v2);
   writeln(v1:1,' + ',v2:1, ' = ',v3:1);
   s1 := 'ABC';
   s2 := 'DEF';
   s3 := p_conc(s1, s2);
   writeln(s1,' + ',s2, ' = ',s3);
   o.iv := 123;
   o.sv := 'ABC';
   writeln('iv=',o.iv:1,' sv=',o.sv);
   o2 := p_modify(o);
   writeln('iv=',o2.iv:1,' sv=',o2.sv);
   p_destroy_jvm;
end.

jcc.com:

$ javaver = "unknown"
$ osnam = "unknown"
$ ptrsiz = 0
$ if f$getsyi("arch_name") .eqs. "Alpha"
$ then
$    javaver = "java$150"
$    osnam ="vms"
$    ptrsiz = 32
$ endif
$ if f$getsyi("arch_name") .eqs. "IA64" .or. f$getsyi("arch_name") .eqs. "x86_64"
$ then
$    javaver = "openjdk$80"
$    osnam = "openvms"
$    ptrsiz = 64
$ endif
$ jcc :== cc/pointer='ptrsiz'/name=(as_is,shortened)/reent=multi/float=ieee/ieee=denorm/include=(sys$common:['javaver'.include],sys$common:['javaver'.include.'osnam'])
$ exit
$ @jcc
$ groovyc Gr.groovy
$ jcc CWrap
$ pas/env pwrap
$ pas p
$ link p + pwrap + cwrap + sys$input/opt
java$java_shr/share
java$jvm_shr/share
$
$ run p

Gr.groovy:

class GrData {
    int iv
    String sv
}

class Gr {
    static int add(int a, int b) {
        return a + b
    }
    static String conc(String a, String b) {
        return a + b
    }
    static GrData modify(GrData o) {
        return new GrData(iv: o.iv + 1, sv: o.sv + "X")
    }
}

data.h:

#ifndef DATA_H
#define DATA_H

struct data
{
    int iv;
    char sv[256];
};

#endif

CWrap.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <signal.h>
#include <ssdef.h>

#include <jni.h>

#include "data.h"

static JNIEnv *ctx;
static JavaVM *jvm;
static jclass clz;
static jmethodID add_method;
static jmethodID conc_method;
static jmethodID modify_method;
static jclass dataclz;
static jfieldID iv_field;
static jfieldID sv_field;
static jmethodID ctor;

void C_INIT_JVM()
{
    DECC$CRTL_INIT();
    JavaVMOption options[2];
    options[0].optionString = "-Xmx256m";
#ifndef __ALPHA
    options[1].optionString = "-Djava.class.path=.:/disk0/net/groovy/groovy-4.0.12/lib/groovy-4.0.12.jar";
#else
    options[1].optionString = "-Djava.class.path=.:/disk2/arne/groovy/groovy-2_2_2/groovy-all-2_2_2.jar";
#endif
    JavaVMInitArgs vm_args;
#ifndef __ALPHA
    vm_args.version = JNI_VERSION_1_8;
#else
    vm_args.version = JNI_VERSION_1_4;
#endif
    vm_args.nOptions = 2;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = JNI_FALSE;
    jint stat = JNI_CreateJavaVM(&jvm, (void **)&ctx, &vm_args);
    if(stat < 0)
    {
        fprintf(stderr, "Error creating JVM\n");
        exit(SS$_ABORT);
    }
}

void C_LOAD_CLASSES()
{
    clz = (*ctx)->FindClass(ctx, "Gr");
    if(clz == NULL)
    {
        fprintf(stderr, "Error loading Gr class\n");
        exit(SS$_ABORT);
    }
    add_method = (*ctx)->GetStaticMethodID(ctx, clz, "add", "(II)I");
    if(add_method == NULL)
    {
        fprintf(stderr, "Error getting add method\n");
        exit(SS$_ABORT);
    }
    conc_method = (*ctx)->GetStaticMethodID(ctx, clz, "conc", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
    if(conc_method == NULL)
    {
        fprintf(stderr, "Error getting conc method\n");
        exit(SS$_ABORT);
    }
    modify_method = (*ctx)->GetStaticMethodID(ctx, clz, "modify", "(LGrData;)LGrData;");
    if(modify_method == NULL)
    {
        fprintf(stderr, "Error getting modify method\n");
        exit(SS$_ABORT);
    }
    dataclz = (*ctx)->FindClass(ctx, "GrData");
    if(dataclz == NULL)
    {
        fprintf(stderr, "Error loading GrData class\n");
        exit(SS$_ABORT);
    }
    iv_field = (*ctx)->GetFieldID(ctx, dataclz, "iv", "I");
    if(iv_field == NULL)
    {
        fprintf(stderr, "Error getting iv field\n");
        exit(SS$_ABORT);
    }
    sv_field = (*ctx)->GetFieldID(ctx, dataclz, "sv", "Ljava/lang/String;");
    if(sv_field == NULL)
    {
        fprintf(stderr, "Error getting sv field\n");
        exit(SS$_ABORT);
    }
    ctor = (*ctx)->GetMethodID(ctx, dataclz, "<init>", "()V");
    if(ctor == NULL)
    {
        fprintf(stderr, "Error getting constructor\n");
        exit(SS$_ABORT);
    }
}

int C_ADD(int a, int b)
{
    jint ja = a;
    jint jb = b;
    jint res = (*ctx)->CallStaticIntMethod(ctx, clz, add_method, ja, jb);
    return res;
}

char *C_CONC(char *a, char *b)
{
    // char* a -> char* a2 -> jstring ja
    char *a2 = malloc(strlen(a) + 1);
    strcpy(a2, a);
    jstring ja = (*ctx)->NewStringUTF(ctx, a2);
    // char* b -> char* b2 -> jstring jb
    char *b2 = malloc(strlen(b) + 1);
    strcpy(b2, b);
    jstring jb = (*ctx)->NewStringUTF(ctx, b2);
    // call and free in args
    jstring jres = (*ctx)->CallStaticObjectMethod(ctx, clz, conc_method, ja, jb);
    (*ctx)->ReleaseStringUTFChars(ctx, ja, a2);
    (*ctx)->ReleaseStringUTFChars(ctx, jb, b2);
    //jstring jres -> char* res -> 32 bit char* res2
    const char *res = (*ctx)->GetStringUTFChars(ctx, jres, 0);
    char *res2 = _malloc32(strlen(res) + 1);
    strcpy(res2, res);
    (*ctx)->ReleaseStringUTFChars(ctx, jres, res);
    return res2;
}

struct data *C_MODIFY(struct data *o)
{
    // jobject jo = new GrData(o->iv, o->sv)
    jint iv = o->iv;
    jstring sv = (*ctx)->NewStringUTF(ctx, o->sv);
    jobject jo = (*ctx)->NewObject(ctx, dataclz, ctor);
    (*ctx)->SetIntField(ctx, jo, iv_field, iv);
    (*ctx)->SetObjectField(ctx, jo, sv_field, sv);
    // call and free input arg
    jobject jres = (*ctx)->CallStaticObjectMethod(ctx, clz, modify_method, jo);
    (*ctx)->ReleaseStringUTFChars(ctx, sv, o->sv);
    // res->iv = jres->iv
    // res->sv = jres->sv
    struct data *res = _malloc32(sizeof(struct data));
    memset(res, 0, sizeof(struct data));
    res->iv = (*ctx)->GetIntField(ctx, jres, iv_field);
    jstring temp = (*ctx)->GetObjectField(ctx, jres, sv_field);
    const char *ntemp = (*ctx)->GetStringUTFChars(ctx, temp, 0);
    strcpy(res->sv, ntemp);
    (*ctx)->ReleaseStringUTFChars(ctx, temp, ntemp);
    return res;
}

void C_DESTROY_JVM()
{
    (*jvm)->DestroyJavaVM(jvm);
}

data.bas:

record xdata
    integer iv
    string sv = 256
end record

BWrap.bas:

sub b_init_jvm()
    external sub c_init_jvm()
    call c_init_jvm
end sub
!
sub b_load_classes()
    external sub c_load_classes()
    call c_load_classes
end sub
!
function integer b_add(integer a, integer b)
    external integer function c_add(integer by value, integer by value)
    b_add = c_add(a, b)
end function
!
sub b_conc(string a, string b, string c)
    external integer function c_conc(string by ref, string by ref)
    external integer function decc$strlen(integer by value)
    external sub str$copy_r(string by desc, integer by ref, integer by value)
    declare integer strptr
    strptr = c_conc(a + chr$(0), b + chr$(0))
    call str$copy_r(c, decc$strlen(strptr), strptr)
end sub
!
sub b_modify(xdata o, xdata o2)
    %include "data.bas"
    external integer function c_modify(xdata by ref)
    external sub decc$memcpy(xdata by ref, integer by value, integer by value)
    declare integer dataptr, ix
    declare xdata temp
    temp = o
    ! C expects null padded not space padded
    ix = 256
    while mid$(temp::sv, ix, 1) = " "
        mid$(temp::sv, ix, 1) = chr$(0)
        ix = ix - 1
    next
    dataptr = c_modify(temp)
    call decc$memcpy(o2, dataptr, 260)
    ! Basic expects space padded not null padded
    ix = 256
    while mid$(o2::sv, ix, 1) = chr$(0)
        mid$(o2::sv, ix, 1) = " "
        ix = ix - 1
    next
end sub
!
sub b_destroy_jvm()
    external sub c_destroy_jvm
    call c_destroy_jvm
end sub

B.bas:

program b
    %include "data.bas"
    external sub b_init_jvm()
    external sub b_load_classes()
    external integer function b_add(integer, integer)
    external sub b_conc(string, string, string)
    external sub b_modify(xdata, xdata)
    external sub b_destroy_jvm()
    external string function trim(string)
    declare integer v1, v2, v3
    declare string s1, s2, s3
    declare xdata o, o2
    call b_init_jvm
    call b_load_classes
    v1 = 123
    v2 = 456
    v3 = b_add(v1, v2)
    print using "### + ### = ###", v1, v2, v3
    s1 = "ABC"
    s2 = "DEF"
    call b_conc(s1, s2, s3)
    print s1 + " + " + s2 + " = " + s3
    o::iv = 123
    o::sv = "ABC"
    print using "iv=### sv='E", o::iv, trim(o::sv)
    call b_modify(o, o2)
    print using "iv=### sv='E", o2::iv, trim(o2::sv)
    call b_destroy_jvm
end program
!
function string trim(string s)
    declare integer ix
    ix = len(s)
    while ix > 1 and mid$(s, ix, 1) = " "
        ix = ix - 1
    next
    trim = mid$(s, 1, ix)
end function

jcc.com:

$ javaver = "unknown"
$ osnam = "unknown"
$ ptrsiz = 0
$ if f$getsyi("arch_name") .eqs. "Alpha"
$ then
$    javaver = "java$150"
$    osnam ="vms"
$    ptrsiz = 32
$ endif
$ if f$getsyi("arch_name") .eqs. "IA64" .or. f$getsyi("arch_name") .eqs. "x86_64"
$ then
$    javaver = "openjdk$80"
$    osnam = "openvms"
$    ptrsiz = 64
$ endif
$ jcc :== cc/pointer='ptrsiz'/name=(as_is,shortened)/reent=multi/float=ieee/ieee=denorm/include=(sys$common:['javaver'.include],sys$common:['javaver'.include.'osnam'])
$ exit
$ @jcc
$ groovyc Gr.groovy
$ jcc CWrap
$ bas bwrap
$ bas b
$ link b + bwrap + cwrap + sys$input/opt
java$java_shr/share
java$jvm_shr/share
$
$ run b

Conclusion:

Groovy integrate nicely with other JVM languages.

Calling native code from Groovy using VMS CALL library is OK.

Using JNI for either calling native code from Groovy or calling Groovy from native code should be avoided unless significant benefits. JNI code is notoriously tricky to to get right and hard to debug.

Article history:

Version Date Description
1.0 March 2nd 2024 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj