2012-12-27 8 views
5

Pracuję nad wywoływaniem funkcji z skompilowanego pliku Delphi * .so z programu Java. Po pewnych badaniach wydaje się, że jest on na dobrej drodze. Przed zagłębieniem się w skomplikowany kod Delphi, próbuję grać z jakimś kodem "Hello World", ale mam problemy z uzyskaniem ciągu zwracanego przez funkcję Delphi.Jak mogę wywołać funkcję Delphi, która zwraca ciąg znaków przy użyciu JNA?

Kod Delphi (helloworld.pp):

library HelloWorldLib; 

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

exports HelloWorld; 

begin 
end. 

skompilować go z linii poleceń z "FPC -Mdelphi helloworld.pp", która produkuje libhelloworld.so.

Teraz moja klasa 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")); 
    } 
} 

Jednak gdy uruchamiam ten kod Java uzyskać:

# 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 

Zauważ, że jeśli mogę zmienić metodę Delphi (i związanego z interfejsu Java), aby powrócić zakodowana liczba całkowita, wszystko działa świetnie: ciąg, który przekazuję, zostaje wydrukowany i otrzymuję int z powrotem zgodnie z oczekiwaniami.

O dziwo, jeśli metoda Delphi zwróci znak, muszę napisać mój serwer proxy JNA jako zwracający bajt i ręcznie wysłać go do char (jeśli deklaruję, że mój interfejs zwraca zwęglę, drukuje znak śmieci).

Masz pomysł, co tu jest nie tak?

FYI, jestem na Ubuntu 12.04, 64 bitów, używając Sun JDK 1.7.0_10-b18, JNA 3.5.1 i darmowego kompilatora Pascal w wersji 2.4.4-3.1.

+0

Czy możesz zwrócić inny 'String's z funkcji natywnej niż ten, który został przekazany? – JimmyB

+0

@HnonoBinder: Otrzymuję ten sam błąd, jeśli zmienię kod Delphi na "Result: =" HELLO ";". :( –

Odpowiedz

8

Delphi lub FreePascal string to typ zarządzany, który nie może być używany jako typ JNA. W dokumencie JNA documentation wyjaśniono, że Java String jest mapowana na wskaźnik do zakończonej znakiem Null tablicy 8-bitowych znaków. W terminach Delphi jest to PAnsiChar.

Możesz zmienić parametr wejściowy w swoim kodzie Pascal z string na PAnsiChar.

Wartość zwracana jest bardziej problematyczna. Musisz zdecydować, kto przydzieli pamięć. I ktokolwiek go przydzieli, musi go uwolnić.

Jeśli za przydzielenie go odpowiada kod natywny, musisz zlikwidować łańcuch zakończony znakiem NUL. I zwróć na nią wskaźnik. Musisz także wyeksportować dealokator, aby kod Java mógł poprosić natywny kod o zwolnienie przydzielonego bloku pamięci.

Zazwyczaj wygodniej jest przydzielić bufor w kodzie Java. Następnie przekaż to do kodu natywnego i pozwól mu wypełnić zawartość bufora. To pytanie przepełnienie stosu przedstawia techniki, za pomocą funkcji Windows API GetWindowText jako przykład: How can I read the window title with JNI or JNA?

Przykładem tego używając Pascal byłoby tak:

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; 

Na stronie Java, myślę kod wyglądałby tak, mając na uwadze, że nie znam absolutnie niczego na temat Javy.

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); 
+0

Ahhh masz rację, to przywraca wspomnienia wskaźników, malloc, itp., Które zniknęły po latach pracy z śmieciarzem JVM ... BTW mając metodę Delphi zwraca PAnsiChar działa z mapowaniem JNA z a. String jako typ zwracany (ale potem otwiera kwestię alokacji pamięci) –

+0

Klasa Java wygląda następująco: public class HelloWorld { \t public interface HelloWorldLibrary rozszerza bibliotekę { \t \t HelloWorldLibrary ZDARZEŃ = (HelloWorldLibrary) Native.loadLibrary ("/home/clevesque/Desktop/delphi/libhelloworld.so", HelloWorldLibrary.class); \t \t int GetText (bufor bajtowy []); \t} \t publiczne statyczne pustych główne łańcuchowe ([] arg) { \t \t bajt [] = bufor nowy bajt [512]; \t System.out.println (HelloWorldLibrary.INSTANCE.GetText (bufor)); \t \t System.out.println (Native.toString (bufor)); \t} } –

+0

Rzeczywiście, zwracanie 'PAnsiChar' jest w porządku modulo deallocating pamięć. –

0

Poza tym używanie stdcall w 64-bitowym systemie Linux nie jest całkowicie logiczne. Prawdopodobnie działa, ponieważ zazwyczaj istnieje tylko jedna konwencja wywoływania na 64-bitowym celu, ale jest poprawna, nie jest. Użyj cdecl;

+0

Na pewno wywoływanie dekoratorów konwencji są po prostu ignorowane dla celów x64. Jeśli ten sam kod jest kiedykolwiek kompilowany dla x86, wówczas ważna staje się kolejność wywoływania. –

+0

Cóż, dokładnie to mówię, nieprawdaż? Zdarza się, że działa, różni się od poprawnego. –

+0

Można się zastanawiać, co oznacza StdCallLibrary w systemie Linux –

Powiązane problemy