【问题标题】:Delphi call JNI methods with variable argument listDelphi 使用可变参数列表调用 JNI 方法
【发布时间】:2015-10-09 04:51:23
【问题描述】:

使用 Embarcadero 的 Jni api 单元,如何为需要它的 JNI 方法提供变量参数列表?例如,JNINativeInterface(清单 1)的 CallStaticObjectMethodV() 方法有一个 va_list 类型的最后一个参数,它应该封装一个变量的参数列表。在调用此方法的 C++ 代码(清单 2)中,方法签名被标记为可变参数,这令人惊讶,因为在 Delphi 的 AndroidApi.Jni 单元中没有可变参数修饰。

您应该如何构造 Args 参数以在 Delphi 中实现相同的功能?我的尝试(如清单 3 所示)不起作用。

清单 1:Androidapi.Jni 单元的摘录,稍微适应 Windows 平台(将 cdecl 更改为 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;

清单 2:如何从 C++ 调用它的示例

清单 2 是从 Saxon/C 库中提取的。

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

清单 3:我将清单 2 翻译成 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);

什么也没有用

我还尝试使用这些解决方案中概述的方法重新声明要使用可变参数修饰的方法类型,并通过汇编实现可变参数。他们没有工作。 (访问冲突)。


更多信息

目标平台是 Win32。我为 windows (WinApi.jni.pas) 制作了 AndroidApi.jni.pas 的副本。我刚刚更改了 stdcall 的 cdecl 装饰。 stdcall 是正确的,我可以使用该单元启动 JavaVM 并执行其他 JNI 工作。 Embaracedero 没有将 CallStaticObjectMethodV() 标记为可变参数,但也许这是一个错误?


更新:最终解决方案

感谢Jonathan Revusky's JNI Wrapper,我制定了一个可行的解决方案...

有效的代码是..

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;

【问题讨论】:

  • 我的目标平台是windows,所以调用约定是stdcall,不是cdecl。我已经测试过了,它是正确的。
  • 问题底部的两个链接答案解释了为什么可变参数需要汇编。
  • CallStaticObjectMethodV: function(Env: PJNIEnv; AClass: JNIClass; MethodID: JNIMethodID ): JNIObject; cdecl; varargs; 不起作用,因为编译器不会自动构建 va_list 对象。我在汇编视图中对此进行了测试。
  • 如果这是一个 Windows 问题,提到 Androidapi.Jni 会让人感到困惑。也许你可以设置更多的场景。实现 varargs 函数需要 Asm,但不需要调用它们。
  • C 中函数的确切声明是什么?而varargs(或任何传递不同数量参数的方式)只能与cdecl一起使用。只有在cdecl 中,调用者 才会清理堆栈,这是绝对必要的,因为只有调用者知道传递了多少参数。在函数内部,这是未知的。它可以在运行时发现,但在编译时完全未知。

标签: delphi java-native-interface


【解决方案1】:

va_list 需要指向一个内存块,该内存块与您调用可变参数函数时将被压入堆栈的内容相匹配。

va_start 的通常实现只是简单地产生堆栈中可变参数被压入的位置的地址。

#define va_start(ap, parmN) ((void)((ap) = (va_list)((char _FAR *)(&parmN)+__size(parmN)))) 

因此,您尝试创建一个包含参数的数组,并将其用作您的 va_list 应该可以工作。也许你已经放弃它太仓促了?也许代替:

Move( Str1, Data[0], Sz);
Move( Str1, Data[Sz], Sz);

你的意思是

Move( Str1, Data[0], Sz);
Move( Str2, Data[Sz], Sz);

虽然我个人会选择JNIString 的数组而不是字节数组。

因此,也许您创建va_list 的方法很好,但失败是由其他地方的错误引起的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-04
    • 2015-09-03
    • 1970-01-01
    • 1970-01-01
    • 2018-12-24
    • 2012-04-12
    • 1970-01-01
    • 2022-01-24
    相关资源
    最近更新 更多