I am starting to really like Groovy.
This article will look at Groovy for applications.
VMS Tech Demo 14 - Groovy for scripting will look at Groovy for smaller scripting tasks.
VMS Tech Demo 16 - Groovy for web will look at Groovy for web: web services and web application.
Everything is about Groovy usage on VMS. Even though most of the Groovy code would run fine on Linux or Windows.
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 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
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"
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.
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:
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:
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 |
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
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
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:
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
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.
Version | Date | Description |
---|---|---|
1.0 | March 2nd 2024 | Initial version |
See list of all articles here
Please send comments to Arne Vajhøj