【问题标题】:How can I call a Delphi function that returns a string using JNA?如何调用使用 JNA 返回字符串的 Delphi 函数?
【发布时间】:2012-12-12 23:51:00
【问题描述】:

我正在从 Java 程序中调用 Delphi 编译的 *.so 文件中的函数。经过一些研究,似乎JNA 是他要走的路。在深入研究一些复杂的 Delphi 代码之前,我正在尝试使用一些“Hello World”代码,但在获取 Delphi 函数返回的字符串时遇到了麻烦。

Delphi 代码 (helloworld.pp):

library HelloWorldLib;

function HelloWorld(const myString: string): string; stdcall;
begin
  WriteLn(myString);
  Result := myString;
end;

exports HelloWorld;

begin
end.

我从命令行使用“fpc -Mdelphi helloworld.pp”编译它,生成libhelloworld.so

现在我的 Java 类:

import com.sun.jna.Library;
import com.sun.jna.Native;

public class HelloWorld {
    public interface HelloWorldLibrary extends Library {
        HelloWorldLibrary INSTANCE = (HelloWorldLibrary) Native.loadLibrary("/full/path/to/libhelloworld.so", HelloWorldLibrary.class);

        String HelloWorld(String test);
    }

    public static void main(String[] args) {
        System.out.println(HelloWorldLibrary.INSTANCE.HelloWorld("QWERTYUIOP"));
    }
}

但是,当我运行这个 Java 代码时,我得到:

# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f810318add2, pid=4088, tid=140192489072384
#
# JRE version: 7.0_10-b18
# Java VM: Java HotSpot(TM) 64-Bit Server VM (23.6-b04 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [libhelloworld.so+0xbdd2]  HelloWorld+0x6fea

请注意,如果我更改我的 Delphi 方法(以及相关的 Java 接口)以返回一个硬编码的整数,那么一切都会很好:我传递的字符串被打印出来,并且我得到了预期的 int。

奇怪的是,如果 Delphi 方法返回一个字符,我必须将我的 JNA 代理编写为返回一个字节并手动将其转换为字符(如果我将我的接口声明为返回一个字符,它会打印出一个垃圾字符)。

知道这里出了什么问题吗?

仅供参考,我使用的是 Ubuntu 12.04,64 位,使用 Sun JDK 1.7.0_10-b18、JNA 3.5.1 和 Free Pascal 编译器版本 2.4.4-3.1。

【问题讨论】:

  • 你能从原生函数返回其他Strings而不是传入的那个吗?
  • @HannoBinder:如果我将 Delphi 代码更改为“Result := 'HELLO';”,我会得到同样的错误。 :(

标签: java string delphi jna freepascal


【解决方案1】:

Delphi 或 FreePascal string 是不能用作 JNA 类型的托管类型。 JNA documentation 解释说,Java String 被映射到一个指向以空字符结尾的 8 位字符数组的指针。在 Delphi 中是 PAnsiChar

因此,您可以将 Pascal 代码中的输入参数从 string 更改为 PAnsiChar

返回值比较成问题。您将需要决定谁分配内存。分配它的人也必须释放它。

如果本机代码负责分配它,那么您需要堆分配以空字符结尾的字符串。并返回一个指向它的指针。您还需要导出一个释放器,以便 Java 代码可以要求本机代码释放堆分配的内存块。

在 Java 代码中分配缓冲区通常更方便。然后将其传递给本机代码并让它填充缓冲区的内容。这个 Stack Overflow 问题以 Windows API 函数 GetWindowText 为例说明了该技术:How can I read the window title with JNI or JNA?

使用 Pascal 的示例如下:

function GetText(Text: PAnsiChar; Len: Integer): Integer; stdcall;
const
  S: AnsiString = 'Some text value';
begin
  Result := Length(S)+1;//include null-terminator
  if Len>0 then
    StrPLCopy(Text, S, Len-1);
end;

在 Java 方面,我猜代码应该是这样的,记住我对 Java 一无所知。

public interface MyLib extends StdCallLibrary {
    MyLib INSTANCE = (MyLib) Native.loadLibrary("MyLib", MyLib.class);
    int GetText(byte[] lpText, int len);
}

....

int len = User32.INSTANCE.GetText(null);
byte[] arr = new byte[len];
User32.INSTANCE.GetText(arr, len);
String Text = Native.toString(arr);

【讨论】:

  • 啊,你是对的。这带回了在多年使用 JVM 垃圾收集器的情况下消失的指针、malloc 等的记忆......顺便说一句,让 Delphi 方法返回一个 PAnsiChar 与一个字符串的 JNA 映射作为返回类型一起工作(但随后打开了这个问题内存分配)。
  • Java 类如下所示: public class HelloWorld { public interface HelloWorldLibrary extends Library { HelloWorldLibrary INSTANCE = (HelloWorldLibrary) Native.loadLibrary("/home/clevesque/Desktop/delphi/libhelloworld.so", HelloWorldLibrary.class); int GetText(字节[]缓冲区); } public static void main(String[] args) { byte[] buffer = new byte[512]; System.out.println(HelloWorldLibrary.INSTANCE.GetText(buffer)); System.out.println(Native.toString(buffer)); } }
  • 确实,返回PAnsiChar 是很好的模数释放内存。
  • 最好直接把分配的大小加到javapascal接口中,这样pascal代码就可以写成永远不会写太多的方式
  • @MarcovandeVoort 这允许调用者分配一个常量缓冲区,例如堆栈分配的固定长度数组。但是如果期望数组总是动态分配的,那么额外的参数就没有用了。
【解决方案2】:

此外,在 64 位 Linux 上使用 stdcall 也不完全合乎逻辑。它可能有效,因为在 64 位目标上通常只有一个调用约定,但正确的是,它不是。使用 cdecl;

【讨论】:

  • 对于 x64 目标来说,调用约定装饰器肯定会被忽略。如果曾经为 x86 编译过相同的代码,那么调用约定再次变得重要。
  • 嗯,这正是我要说的不是吗?碰巧工作与正确不同。
猜你喜欢
  • 2013-12-06
  • 2020-12-10
  • 1970-01-01
  • 2016-05-20
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 2012-06-08
  • 2014-06-30
相关资源
最近更新 更多