Using Embarcadero's Jni api unit, how does one supply variable argument lists to the JNI methods that require it? For example, the CallStaticObjectMethodV() method of the JNINativeInterface (listing 1) has a last parameter of type va_list, which is supposed to encapsulate a variable list of arguments. In C++ code (listing 2) which calls this method, the method signature is marked as varargs, which is surprising because there is no varargs decoration in Delphi's AndroidApi.Jni unit.
How are you supposed to construct the Args parameter to achieve the same thing in Delphi? My attempt, shown in listing 3 does not work.
JNINativeInterface = packed record
...
CallStaticObjectMethod : function(Env: PJNIEnv; AClass: JNIClass; MethodID: JNIMethodID): JNIObject; stdcall;
CallStaticObjectMethodV: function(Env: PJNIEnv; AClass: JNIClass; MethodID: JNIMethodID; Args: va_list ): JNIObject; stdcall;
CallStaticObjectMethodA: function(Env: PJNIEnv; AClass: JNIClass; MethodID: JNIMethodID; Args: PJNIValue): JNIObject; stdcall;
Listing 2 was extracted from the Saxon/C library.
XdmValue * SaxonProcessor::parseFile(const char* source){
jmethodID mID = (jmethodID)env->GetStaticMethodID(saxonCAPIClass, "xmlParseFile", "(Lnet/sf/saxon/s9api/Processor;Ljava/lang/String;Ljava/lang/String;)Lnet/sf/saxon/s9api/XdmNode;");
if (!mID) {
cerr<<"\nError: MyClassInDll "<<"xmlParseFile()"<<" not found"<<endl;
return NULL;
}
jobject xdmNodei = env->CallStaticObjectMethod(saxonCAPIClass, mID, proc, env->NewStringUTF(cwd.c_str()), env->NewStringUTF(source));
if(exceptionOccurred()) {
exception= checkForException(env, saxonCAPIClass, NULL);
} else {
XdmValue * value = new XdmValue(xdmNodei);
value->setProcessor(this);
return value;
}
return NULL;
}
var
mID: JNIMethodID;
xdmNodei: JNIObject;
Str1, Str2: JNIString;
Hold1, Hold2: TBytes;
ArgsAsList: va_list;
Data: TBytes;
Sz: integer;
begin
mID := FJNIEnv.GetStaticMethodID( Fpenv, FsaxonCAPIClass, 'xmlParseFile',
'(Lnet/sf/saxon/s9api/Processor;Ljava/lang/String;Ljava/lang/String;)Lnet/sf/saxon/s9api/XdmNode;');
Str1 := FJNIEnv.NewStringUTF( Fpenv, String_to_MarshaledAString( Fcwd , Hold1));
Str2 := FJNIEnv.NewStringUTF( Fpenv, String_to_MarshaledAString( Source, Hold2));
Sz := SizeOf( JNIString);
SetLength( Data, 3 * Sz);
FillChar( Data[0], Length( Data), 0);
Move( Str1, Data[0], Sz);
Move( Str1, Data[Sz], Sz);
ArgsAsList := va_list( @Data[0]);
xdmNodei := FJNIEnv.CallStaticObjectMethodV( Fpenv, FsaxonCAPIClass, mID, ArgsAsList);
I have also tried redeclaring the method type to be decorated with varargs, and implementing the varargs passing with assember, using the method outlined in these solutions. They did not work. (Access violation).
The target platform is Win32. I made a copy of AndroidApi.jni.pas for windows (WinApi.jni.pas
). I just changed the cdecl decorations for stdcall. stdcall is correct, and I can use the unit to start the JavaVM and do other JNI stuff. Embaracedero does not mark the CallStaticObjectMethodV() as varargs, but maybe this is an error?
Thanks to Jonathan Revusky's JNI Wrapper, I worked out a working solution ...
The code that works is ..
function TSaxonProcessor.parseFile( const Source: string): TXdmValue;
var
mID: JNIMethodID;
xdmNodei: JNIObject;
Str1, Str2: JNIString;
Hold1, Hold2: TBytes;
Data: TArray<JNIString>;
begin
mID := FJNIEnv.GetStaticMethodID( Fpenv, FsaxonCAPIClass, 'xmlParseFile',
'(Lnet/sf/saxon/s9api/Processor;Ljava/lang/String;Ljava/lang/String;)Lnet/sf/saxon/s9api/XdmNode;');
Str1 := FJNIEnv.NewStringUTF( Fpenv, String_to_MarshaledAString( Fcwd , Hold1));
Str2 := FJNIEnv.NewStringUTF( Fpenv, String_to_MarshaledAString( Source, Hold2));
SetLength( Data, 3);
Data[0] := FProc;
Data[1] := Str1;
Data[2] := Str2;
xdmNodei := FJNIEnv.CallStaticObjectMethodV( Fpenv, FsaxonCAPIClass, mID, @Data[0]);
end;
The va_list
needs to point to a block of memory that matches what would be pushed onto the stack if you had instead called a variadic function.
The usual implementation of va_start
simply yields the address of the location on the stack where the variadic arguments were pushed.
#define va_start(ap, parmN) ((void)((ap) = (va_list)((char _FAR *)(&parmN)+__size(parmN))))
So your attempt to make an array containing the arguments, and use that as your va_list
ought to work. Perhaps you've given up on it too hastily? Perhaps instead of:
Move( Str1, Data[0], Sz);
Move( Str1, Data[Sz], Sz);
you meant
Move( Str1, Data[0], Sz);
Move( Str2, Data[Sz], Sz);
Although personally I'd opt for a array of JNIString
rather than a byte array.
So perhaps your approach to create the va_list
is fine, but the failure is caused by an error elsewhere.