【发布时间】: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);
什么也没有用
我还尝试使用这些解决方案中概述的方法重新声明要使用可变参数修饰的方法类型,并通过汇编实现可变参数。他们没有工作。 (访问冲突)。
- How can a function with 'varargs' retrieve the contents of the stack?
- Delphi "array of const" to "varargs"
更多信息
目标平台是 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