Delphi 使用可变参数列表调用 JNI 方法
Delphi call JNI methods with variable argument list
使用 Embarcadero 的 Jni api 单元,如何向需要它的 JNI 方法提供变量参数列表?例如,JNINativeInterface 的 CallStaticObjectMethodV() 方法(清单 1)有一个 va_list 类型的最后一个参数,它应该封装一个可变参数列表。在调用此方法的 C++ 代码(清单 2)中,方法签名被标记为可变参数,这令人惊讶,因为 Delphi 的 AndroidApi.Jni 单元中没有可变参数修饰。
您应该如何构造 Args 参数以在 Delphi 中实现相同的目的?我的尝试(如清单 3 所示)不起作用。
清单 1:从单元 Androidapi.Jni 中提取,针对 Windows 平台稍作调整(针对 stdcall 更改了 cdecl)
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);
什么也没有用
我还尝试使用这些解决方案中概述的方法重新声明要用可变参数装饰的方法类型,并使用 assember 实现可变参数传递。他们没有工作。 (访问冲突)。
- 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;
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
的方法没问题,但失败是由其他地方的错误引起的。
使用 Embarcadero 的 Jni api 单元,如何向需要它的 JNI 方法提供变量参数列表?例如,JNINativeInterface 的 CallStaticObjectMethodV() 方法(清单 1)有一个 va_list 类型的最后一个参数,它应该封装一个可变参数列表。在调用此方法的 C++ 代码(清单 2)中,方法签名被标记为可变参数,这令人惊讶,因为 Delphi 的 AndroidApi.Jni 单元中没有可变参数修饰。
您应该如何构造 Args 参数以在 Delphi 中实现相同的目的?我的尝试(如清单 3 所示)不起作用。
清单 1:从单元 Androidapi.Jni 中提取,针对 Windows 平台稍作调整(针对 stdcall 更改了 cdecl)
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);
什么也没有用
我还尝试使用这些解决方案中概述的方法重新声明要用可变参数装饰的方法类型,并使用 assember 实现可变参数传递。他们没有工作。 (访问冲突)。
- 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;
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
的方法没问题,但失败是由其他地方的错误引起的。