简体   繁体   English

如何调用使用JNA返回字符串的Delphi函数?

[英]How can I call a Delphi function that returns a string using JNA?

I'm working on calling functions from a Delphi compiled *.so file from a Java program. 我正在从Java程序中调用来自Delphi编译的* .so文件的函数。 After some research it seems like JNA is he way to go. 经过一些研究,似乎JNA是他的出路。 Before diving into some complex Delphi code, I'm trying to play with some "Hello World" code but am having trouble getting a string returned by a Delphi function. 在深入研究一些复杂的Delphi代码之前,我正在尝试使用一些“Hello World”代码,但是在获取Delphi函数返回的字符串时遇到了问题。

The Delphi code (helloworld.pp): Delphi代码(helloworld.pp):

library HelloWorldLib;

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

exports HelloWorld;

begin
end.

I compile it from the command line with " fpc -Mdelphi helloworld.pp ", which produces libhelloworld.so . 我使用“ fpc -Mdelphi helloworld.pp ”从命令行编译它,生成libhelloworld.so

Now my Java class: 现在我的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"));
    }
}

However when I run this Java code I get: 但是当我运行这个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

Note that if I change my Delphi method (and the associated Java interface) to return a hardcoded integer, everything works great: the string I pass gets printed and I get the int back as expected. 请注意,如果我更改我的Delphi方法(和相关的Java接口)以返回硬编码整数,一切都很好:我传递的字符串被打印,我按预期返回int。

Strangely enough, if the Delphi method returns a char, I have to write my JNA proxy as returning a byte and cast it to char manually (if I declare my interface as returning a char it prints out a garbage character). 奇怪的是,如果Delphi方法返回一个char,我必须编写我的JNA代理作为返回一个字节并手动将其转换为char(如果我声明我的接口返回一个char它打印出一个垃圾字符)。

Any idea what is going wrong here? 知道这里出了什么问题吗?

FYI, I'm on Ubuntu 12.04, 64bits, using Sun JDK 1.7.0_10-b18, JNA 3.5.1 and Free Pascal Compiler version 2.4.4-3.1. 仅供参考,我在Ubuntu 12.04,64bits,使用Sun JDK 1.7.0_10-b18,JNA 3.5.1和Free Pascal Compiler版本2.4.4-3.1。

A Delphi or FreePascal string is a managed type that cannot be used as a JNA type. Delphi或FreePascal string是不能用作JNA类型的托管类型。 The JNA documentation explains that Java String is mapped to a pointer to a null-terminated array of 8 bit characters. JNA文档解释了Java String被映射到指向以8位字符为空的终止数组的指针。 In Delphi terms that is PAnsiChar . 在Delphi中,这是PAnsiChar

So you can change the input parameter in your Pascal code from string to PAnsiChar . 因此,您可以将Pascal代码中的输入参数从string更改为PAnsiChar

The return value is more problematic. 返回值更成问题。 You will need to decide who allocates the memory. 您需要决定谁分配内存。 And whoever allocates it must also free it. 分配它的人也必须释放它。

If the native code is responsible for allocating it then you'd need to heap allocate the null-terminated string. 如果本机代码负责分配它,那么您需要堆分配以null结尾的字符串。 And return a pointer to it. 并返回指向它的指针。 You'd also need to export a deallocator so that the Java code can ask the native code to deallocate the heap allocated block of memory. 您还需要导出解除分配器,以便Java代码可以要求本机代码释放堆分配的内存块。

It is usually more convenient to allocate a buffer in the Java code. 在Java代码中分配缓冲区通常更方便。 Then pass that to the native code and let it fill out the content of the buffer. 然后将其传递给本机代码,让它填写缓冲区的内容。 This Stack Overflow question illustrates the technique, using the Windows API function GetWindowText as its example: How can I read the window title with JNI or JNA? 此Stack Overflow问题说明了该技术,使用Windows API函数GetWindowText作为示例: 如何使用JNI或JNA读取窗口标题?

An example of this using Pascal would be like so: 使用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;

On the Java side, I guess the code would look like this, bearing in mind that I know absolutely nothing about Java. 在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);

Besides that, using stdcall on 64-bit Linux is not entirely logical either. 除此之外,在64位Linux上使用stdcall也不完全符合逻辑。 It probably works, since there usually is only one calling convention on a 64-bit target, but correct, it isn't. 它可能有效,因为在64位目标上通常只有一个调用约定,但是正确的,它不是。 Use cdecl; 使用cdecl;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM