Search code examples
delphijava-native-interface

Delphi call JNI methods with variable argument list


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.

Listing 1: Extract from unit Androidapi.Jni, slightly adapted for Windows platform (changed cdecl for stdcall)

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: An example of how it is called from C++

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;
}

Listing 3: My attempt at translating listing 2 into Delphi

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);

What also hasn't worked

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).


A bit more information

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?


Update: The final solution

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;

Solution

  • 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.