Как я могу вызвать функцию Delphi, которая возвращает строку, используя JNA?

Я работаю над вызовом функций из файла * .so, скомпилированного Delphi, из программы Java. После некоторого исследования кажется, что 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 возвращает char, я должен написать свой прокси JNA как возвращающий байт и вручную преобразовать его в char (если я объявляю свой интерфейс как возвращающий char, он распечатывает символ мусора).

Есть идеи, что здесь происходит не так?

К вашему сведению, я использую Ubuntu 12.04, 64 бита, использую Sun JDK 1.7.0_10-b18, JNA 3.5.1 и Free Pascal Compiler версии 2.4.4-3.1.


person Christophe L    schedule 27.12.2012    source источник
comment
Можете ли вы вернуть другие String из собственной функции, кроме той, которая была передана?   -  person JimmyB    schedule 27.12.2012
comment
@HannoBinder: я получаю ту же ошибку, если изменяю свой код Delphi для выполнения Result: = 'HELLO' ;. :(   -  person Christophe L    schedule 27.12.2012


Ответы (2)


Delphi или FreePascal string - это управляемый тип, который нельзя использовать в качестве типа JNA. В документации JNA объясняется, что Java String отображается на указатель на Массив 8-битных символов с завершающим нулем. В терминах Delphi это PAnsiChar.

Таким образом, вы можете изменить входной параметр в вашем коде Pascal с string на PAnsiChar.

Возвращаемое значение более проблематично. Вам нужно будет решить, кто выделяет память. И тот, кто его распределяет, должен также освободить его.

Если за его выделение отвечает собственный код, вам нужно будет выделить в куче строку с завершающим нулем. И верните на него указатель. Вам также потребуется экспортировать средство освобождения памяти, чтобы код Java мог попросить собственный код освободить выделенный в куче блок памяти.

Обычно в коде Java удобнее выделять буфер. Затем передайте это в собственный код и позвольте ему заполнить содержимое буфера. Этот вопрос о переполнении стека иллюстрирует эту технику на примере функции Windows API GetWindowText: Как я могу прочитать заголовок окна с помощью JNI или JNA? < / а>

Пример этого с использованием Паскаля будет таким:

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);
person David Heffernan    schedule 27.12.2012
comment
Ах ты прав. Это возвращает воспоминания об указателях, malloc и т. Д., Которые исчезли за годы работы со сборщиком мусора JVM ... Кстати, когда метод Delphi возвращает PAnsiChar, работает с сопоставлением JNA String как возвращаемого типа (но затем открывает проблему распределения памяти). - person Christophe L; 27.12.2012
comment
Класс Java выглядит следующим образом: открытый класс HelloWorld {открытый интерфейс HelloWorldLibrary расширяет библиотеку {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 (буфер)); System.out.println (Native.toString (буфер)); }} - person Christophe L; 27.12.2012
comment
Действительно, возврат PAnsiChar прекрасен по модулю освобождения памяти. - person David Heffernan; 27.12.2012
comment
Лучше напрямую добавить размер выделения к интерфейсу java ‹-› pascal, чтобы код паскаля мог быть написан таким образом, чтобы никогда не писать слишком много - person Marco van de Voort; 29.12.2012
comment
@MarcovandeVoort Это позволяет вызывающей стороне выделить постоянный буфер, например выделенный стеком массив фиксированной длины. Но если ожидается, что массив всегда выделяется динамически, дополнительный параметр не имеет смысла. - person David Heffernan; 29.12.2012
comment
Виноват. Я пропустил конструкцию с вызовом с nil. - person Marco van de Voort; 29.12.2012

Кроме того, использование stdcall в 64-битном Linux тоже не совсем логично. Вероятно, это работает, поскольку обычно для 64-битной цели существует только одно соглашение о вызовах, но правильно, это не так. Используйте cdecl;

person Marco van de Voort    schedule 28.12.2012
comment
Разумеется, декораторы соглашения о вызовах просто игнорируются для целей x64. Если тот же код когда-либо компилируется для x86, соглашение о вызовах снова становится важным. - person David Heffernan; 29.12.2012
comment
Ну, это именно то, что я говорю, не так ли? Бывает, что работа отличается от правильной. - person Marco van de Voort; 29.12.2012