Import biblioteki DLL napisanej w C# do Delphi XE2

0

Napisałem prostą dll c#, która wyświetla okienko i coś tam robi.
Inicjalizacja okienka odbywa się za pomocą metody:
public Response Inicjalizuj(string p, string p_2)
która wyświelta okienko i wpisuje parametry p i p2 do okienka. Następnie zwraca obiekt Response, który składa się z kilku stringów.
Jak najprościej i poprawnie użyć teraz tej dll w programi w Delphi. Czy konieczne są jakieś obostrzenia ?
Próbowałem już :

DLLHandle :=LoadLibrary('DaneNip.DLL');
@DLLFunc := GetProcAddress (DLLHandle,'Inicjalizuj');

i o ile dllhandle zawiera coś jakąs liczbę to @DLLFunc już nic nie zwraca.

Próba z :

function Inicjalizuj (nip: String; regon: String):Response; external 'DaneNip.DLL' delayed;

resp :=Inicjalizuj('11','22');

powoduje zawieszenie się z błędem External exception c06d007f
Proszę o pomoc bo jesli chodzi o delphi jestem zielony.

dodanie znaczników <code class="delphi"> - furious programming

0

Z tego co mi wiadomo tak tego nie zrobisz. Najprościej z poziomu Visual Studio zrobić taką DLL-kę jako COM, a później w takiej postaci zaimportować ją do Delphi. Obostrzenia są - wynikają z modelu COM.

0

Dlaczego nie napiszesz tej DLL-ki w Delphi?

0

Dlaczego nie napiszesz tej DLL-ki w Delphi?

Bo takie założenia dostałem w pracy - będę pisał różne dll-ki w c# , które będą podpinane pod programy w napisane w delphi. Poza tym nie znam delphi tylko c#, a muszę dostarczyć rozwiązanie jak to zrobić.

Dodałem do VS2010Express tego UnmanagedExportLibrary.zip pojawił mi się nowy rodzaj projektu utworzyłem go, zapisałem, zbudowałem (kod który tam się znajduje poniżej), wyskoczył błąd:


   internal static class UnmanagedExports
   {
      [DllExport("adddays", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
      static double AddDays(double dateValue, int days)
      {
         return DateTime.FromOADate(dateValue).AddDays(days).ToOADate();
      }
   }

"Error	1	The imported project "C:\Grzegorz\Projekty\UnmanagedExportLibrary2\UnmanagedExportLibrary2\DllExport\RGiesecke.DllExport.targets" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.	C:\Grzegorz\Projekty\UnmanagedExportLibrary2\UnmanagedExportLibrary2\UnmanagedExportLibrary2.csproj	66	3	UnmanagedExportLibrary2
"

Jednak jakimś cudem dllka się pojawiła w katalogu z projektem.
Przekopiowałem ją do projektu w delphi i niestety przy próbie

function adddays (d: double; i:Int32):double; external 'Test1.dll' delayed;
 d:=adddays(123,213);

oraz

  DLLHandle :=LoadLibrary('Test1.DLL');
@DLLFunc := GetProcAddress (DLLHandle,'adddays');
     if Assigned(DLLFunc) then
        EditNip.Text := 'funkcja jest'
     else
        EditNip.Text := 'niema'
 

Dalej dostaję błąd lub w drugim wypadku brak znalezienia funkcji.
Co robię nie tak ?

0

Bo takie założenia dostałem w pracy - będę pisał różne dll-ki w c#

Niepraktyczne to założenie, wymagające kombinowania.
Nie pomogę ci z UnmanagedExportLibrary, bo nie miałem z tym do czynienia.

Ale widzę ewidentny błąd:

 static double AddDays(double dateValue, int days)
function adddays (d: double; i:Int32):double;
0

Wyjaśnij proszę bo nie widzę błędu. Delphi dopiero poznaję.

0

Niestety zmiana adddays na AddDays w niczym nie pomaga :(

1
function AddDays(d: double; i:Int32):double; stdcall; external 'Test1.dll' name 'AddDays';

Teraz pokombinuj z tym name 'AddDays': spróbuj '_AddDays', '_AddDays@12'

A jak nie, to coś nie tak z tym UnmanagedExportLibrary, albo źle go używasz.

Generalnie C# do tego nie służy. Eksportować funkcje widzialne przez natywne programy można łatwo pod C++/CLI.

using namespace System;

extern "C" __declspec(dllexport) double __stdcall AddDays(double dateValue, int days)
{
         return DateTime::FromOADate(dateValue).AddDays(days).ToOADate();
}

To jest ta sama funkcja co twoja w C#. __declspec(dllexport) sprawia, że pod Delphi będzie widziana - pod warunkiem że ją zaimportujesz jako name '_AddDays@12', gdzie 12 to ilość bajtów zajmowanych przez parametry (double 8 + int 4 = 12).

0
Azarien napisał(a):

Teraz pokombinuj z tym name 'AddDays': spróbuj '_AddDays', '_AddDays@12'

Niestety dalej nic różne warianty niczego nie zmieniają.

Próbowałem dla pewności stworzyć dll w delphi wg http://delphi.about.com/od/windowsshellapi/a/dll_basics.htm i na obydwa podane sposoby udało się odpalić taką dll, więc problem ewidentnie leży po stronie C# i/lub tego jak jest przygotowywana/importowana dll. Niestety czas leci, a ja dalej jestem w lesie. :(

0

Nie mam pod reka C#
Sprobuj tak

   internal static class UnmanagedExports
   {
      [DllExport("AddDays", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
      static double AddDays(double dateValue, int days)
      {
         return DateTime.FromOADate(dateValue).AddDays(days).ToOADate();
      }
   }

i w delphi

type
  TAddDays = function (dateValue: double; days Integer): double; stdcall;

dodanie znacznika <code class="delphi"> - furious programming

0

Niestety dalej to samo.
Dostałem od kolegi taki programik
http://www.nirsoft.net/utils/dll_export_viewer.html
Jeśli wrzucę do niego dll napisaną w delphi lub inną, z których kolega korzystał w delphi i mu działa, to w programie
zwracane są funkcje, które da się wywołać bez problemu z tych dll.
Kiedy natomiast podaję mu moją dll lub każdą napisaną w c# program zwraca pustą listę !
A więc jest to raczej kwestia przygotowania dll niż sposób wywoływania albo nie wiem co innego jeszcze. Czyli tak jak pisał Azarien
coś nie tak z tym UnmanagedExportLibrary tylko co ? Jak przygotować dll by te funkcje były widoczne ?

0

https://www.nuget.org/packages/UnmanagedExports

  • You have to set your platform target to either x86, ia64 or x64. AnyCPU assemblies cannot export functions.
  • The export name defaults to the method name and the calling convention to stdcall. If that's all what you want, you can just use [DllExport] without parameters.
  • You cannot put your exports in generic types or export generic methods. (The CLR wouldn't know what type parameters to use)
0

Dziękuję za pomoc. Jest postęp - zainstalowałem vs 2013 gdzie można bez problemu doinstalować UnmanagedExports w Package Manager Console (w vs 2010 trzeba by było mieć pełną wersję a nie express). I ruszyło !
Teraz problem jest z przekazywaniem parametrów. W C# jest :

[DllExport("Inicjalizuj", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
public static Response Inicjalizuj(string p, string p_2)

w delphi

TNipRegon = function(dbl: String; i:String): Response; StdCall;
...
@DLL := GetProcAddress (DLLHandle,'Inicjalizuj');
resp:=DLL ('123','456');

Wywołanie z parametrami '123' i 'abc' powoduje przekazanie tylko pierwszego znaku z każdego parametru. Efektem tego jest wywołane okienko z '1' zamiast 123 oraz 'a' zamiast abc.
Co trzeba zmienić ? By parametry były przekazywane w całości poprawnie ?
Próbowałem jeszcze drugim sposobem załadować bibliotekę (z external) ale wtedy dostawałem zamiast przekazywanych parametrów krzaki.

0

Nie wiem jak to się ma do bibliotek z C# ale przekazywanie parametrów typu string w bibliotekach to zawsze było źródłem wszelkiego zła. Jak już to kombinuj coś z pointerami PChar (lub jeżeli UNICODE PWideChar) czy coś.

0

Dzieki udało się za pomocą AnsiString
Pozostała ostatnia kwestia - zwracany obiekt "Response":


public class Response
        {
            public string Regon;
            public string NIP;
            public string Nazwa;
...
}

Czy taki obiekt jestem w stanie odczytać w delhpi za pomocą:

	TNipRegon = function(dbl: AnsiString; i:AnsiString): Response; StdCall;
type
  Response = Class(TObject)
     private

     protected

     public
        Regon : AnsiString ;
        NIP: AnsiString ;
        Nazwa : AnsiString ;
...

Czy tak się w ogóle da czy raczej trzeba zwrócić jeden prosty obiekt taki jak np string porozdzielany jakimś separatorem i na piechotę poprzepisywać wszystkie parametry do klasy parsując stringa i wpisując elementy jeden po drugim ? Czy klasa Response z C# będzie analogicznie widziana pod Delphi czy raczej to zbyt karkołomna próba powiązania ze sobą dwóch zbyt różnych obiektów.Pytam bo może ktoś już przekazywał takie złożone obiekty między dllkami czy raczej nikomu się to jeszcze nie udało robił to zawsze jak najprościej się dało.

0

To co chcesz zrobić już nie jest takie proste , bo przekazanie obiektu w jednego języka programowania do drugiego nie jest możliwa.

Masz dwa wyjścia , albo zrobisz to w technologi COM
albo będziesz musiał napisać trochę wiecej kodu który połączy warstwę DELPHI z C#.

Proponuję abyś zobaczył sobie jak wygląda to dla obiektów Qt dla Lazarus-a
http://users.telenet.be/Jan.Van.hijfte/qtforfpc/fpcqt4.html
http://users.telenet.be/Jan.Van.hijfte/qtforfpc/V2.6/splitbuild-qt5pas-V2.6Alpha_Qt5.1.1.tar.gz

0

Po stronie Delphi używaj pchar, a po stronie C# standardowego marshallingu (słowo-klucz) stringów.
Pogóglaj jak przekazać natywnego c-stringa do C#.

To wszystko się da zrobić.

Ale osobę, która decydowała o tym

Bo takie założenia dostałem w pracy - będę pisał różne dll-ki w c# , które będą podpinane pod programy w napisane w delphi.
należy oskarżyć albo o głupotę albo o sabotaż.

0

A nie mozesz eksportowac interface? (np IInvokable). I zamiast jakichkolwiek stringow to OleVariant?

0

Zetknąłem się z podobnym problemem - w moim przypadku mamy napisać plugin do aplikacji napisanej w Delphi (aplikację dostarcza zewnętrzna firma - nie mamy na nią wpływu). Osobiście w Delphi pisałem dawno, a całą technologię w firmie mamy przestawioną na C# -> nie ma szans na zrobienie tego co potrzebujemy w Delphi.

Podobnie jak u kolegi - dostaję błąd "~Zewnętrzny wyjątek E0434352"

Podobnie jak On - w VS2017 używam modułu UnmanagedExports. W assembly information projektu ustawiłem "Make assembly COM-Visible". Projekt jest typu Class Library. Aplikacja Delphi jest x86 - kompiluję również do platformy x86

Kręcę się trochę w kółko. Może walczyliście z tym tematem i potraficie przynajmniej wskazać kierunek.

EDIT: OK. Zrobiłem prostą aplikację testującą moją bibliotekę napisaną w C# i osiągnąłem u siebie dokładnie ten sam błąd, co w aplikacji zewnętrznej). Dobrnąłem przynajmniej do takiego punktu, że Delphi widzi moje funkcje. Na ta chwilę ustaliłem, że problemem są parametry wejściowe i wyjściowe. Delphi przekazuje jako parametr "const p : PChar", a oczekuje zwrotki ze wskaźnikiem do tablicy obiektów "packed records".

zostawiając na chwilę ten wątek - napisałem mały kawałek kodu, którego zadaniem jest przekazanie PChara i oczekiwanie na wynik w takiej samej postaci:

procedure TForm1.Button1Click(Sender: TObject);
var
  DLL : THandle; 
  test1 : function(a:PChar) : PChar;
begin
    DLL := LoadLibrary('blbioteka.dll'); 
    try
        @test1 := GetProcAddress(DLL, 'test1'); 
        ShowMessage(string(test1('test2')));
    finally
        FreeLibrary(DLL); 
    end;
end;

metoda po stronie C# wygląda tak:

        [ComVisible(true)]
        [DllExport("test1", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static string test1([MarshalAs(UnmanagedType.LPStr)]string s)
        {
            return s+" "+s;
        }

kombinowałem z LPWStr, BStr i chyba wszystkimi innymi typami stringopodobnymi - dostaję albo błędy, albo krzaki w ShowMessage ...

0

Możesz spróbować zrobić dll w C++/CLI. Eksporty robisz normalnie tak jak w C++ zwykłym, a jednocześnie możesz używać .net w projekcie. Możesz nawet wszystko napisać w C#, a potem dll - pomost, który będzie korzystał z assembly napisanych w C# (dodanych jako referencje) i eksportował funkcie w stylu C. W Delphi wtedy nie korzystasz z assembly C#, tylko z tej jednej cll C++/CLI i używasz jej tak jak normalnej natywnej biblioteki.

0

Doszedłem dzisiaj do takiego punktu, gdzie uważam, że problem wynika z kodowania znaków, a właściwie rozbieżności pomiędzy Delphi, a C#. Nawet nie typu jako takiego, a Encodingu.

Czy jesteście w stanie podpowiedzieć mi jaki jest defaultowy encoding dla typu PChar i ew. (jeżeli to nie kłopot, bo nie potrafię znaleźć zrozumiałego dla mnie przykładu kodu w Delphi) - chciałbym zweryfikować moją teorię: Gdyby udało mi się wysłać stringa (PChar=PWideChar) do C# w znanym nam kodowaniu - wtedy mógłbym po drugiej stronie spróbować wykonać zmianę kodowania na coś, co rozumie C# (a właściwie mają znane kodowanie - posługiwać się tym konkretnym). Chodzi o to, żebyście pomogli mi stworzyć kawałek kodu, który zagwarantuje, że to co przekażę do metody w DLL jest przekazywane w znanym mi encodingu.

Innymi słowy - wydaje mi się, że Delphiowy PChar=PWideChar to w C# UnmanagedType.LPWStr, natomiast dodatkowo pojawia się drugi wymiar - Encoding, który muszę opanować.

Za tą teorią przemawia kilka poszlak: po stronie C# dostajemy efekt w postaci MessageBoxa z "krzakami" - czyli metoda jest wykonywana prawidłowo (przeczy to konieczności pisania jakichś pośrednich wrapperów), tylko dane kodowania się nie zgadzają. Druga poszlaka jest taka, że oryginalna aplikacja wykonuje import pewnej biblioteki napisanej w .NET, z której wykorzystuje metody przyjmujące argumenty typu string, a wewnątrz dane z przyjętego parametru poddają właśnie konwersji encodingów - czyli jest coś na rzeczy.

1

Jeżeli to nowe Delphi 2009+ to z tym przykładowym kodem działa:

procedure TForm4.Button1Click(Sender: TObject);
var
  DLL : THandle;
  test1 : function(a:PChar) : PChar; stdcall;
begin
    DLL := LoadLibrary('biblioteka2.dll');
    try
        @test1 := GetProcAddress(DLL, 'test1');

        ShowMessage(string(test1('test2')));
    finally
        FreeLibrary(DLL);
    end;
end;
    public class Class1
    {
        [DllExport("test1", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.LPWStr)]
        public static string test1([MarshalAs(UnmanagedType.LPWStr)]string s)
        {
            return s + " " + s;
        }
    }

PChar to w nowych Delphi PWideChar więc w C# LPWStr i zapomniałeś o stdcall.

0

Cudownie !!! Działa. Dziękuję.

Kolejny krok do przodu. Teraz musze zwalczyć temat przekazywania struct do packed record.

0
toyman napisał(a):

Doszedłem dzisiaj do takiego punktu, gdzie uważam, że problem wynika z kodowania znaków, a właściwie rozbieżności pomiędzy Delphi, a C#. Nawet nie typu jako takiego, a Encodingu.

Delphi (nowe) używa UTF-16 i C# używa UTF-16.

0

Tak, tak. Okazało się po po prostu, że sknociłem CallingConvention - po stronie C# źle zdefiniowałem, a po stronie Delphi nie określiłem.

Teraz walczę z przekazaniem tablicy (jakiejkolwiek), a celem przekazanie wskaźnika do tablicy ze structem, który ma napełnić tablicę z packed record … nie ukrywam, że idzie mi jak po grudzie (tym bardziej, że nie robiłem tego nigdy i nie do końca wiem co robię).

Możecie nadać mi jakiś kierunek ? Po stronie C# jest LPArray lub SafeArray - pierwszy teoretycznie przekazuje wskaźnik, drugi przekazuje komplet informacji ale po stronie Delphi … kombinuję na różne sposoby i cały czas osiągam dziwne efekty

0

Nie znam na tyle C# aby tu coś konkretnie doradzić ale ten przykład może (nie jestem pewien) naprowadzić na rozwiązanie:
https://stackoverflow.com/questions/30569111/how-can-i-marshal-a-delphi-short-string-using-p-invoke

0

Dobraaa. Wygląda obiecująco. Dziękuję.

0

Kurczę. Zaczyna mi już brakować RAMu i mózg mi zaczyna kisnąć. Walczę z przekazaniem tablicy stringów.

Po stronie C# mam tak:

        [ComVisible(true)]
        [DllExport("Function_2", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.SafeArray,SafeArraySubType = VarEnum.VT_LPWSTR)]
        //[return: MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex =2)]
        public static string[] Function_2([MarshalAs(UnmanagedType.LPWStr)]string pParams)
        {
            MessageBox.Show(pParams);
            string[] pp_str = new string[] { "a", " b" };
            return pp_str;
        }

Przy czym wariant z SafeArray daje jak na razie najlepszy efekt, bo prawidłowo wyświetla stringa przekazanego w pParams, natomiast wywala się dalej (prawdopodobnie przy returnie.

Po stronie Delphi mam tak:

  TStr = array[0..1] of PChar;
  PStr = ^TStr;

(…)

procedure TForm1.Button4Click(Sender: TObject);
var
  DLL : THandle;
  Function2 : function(pParams: PChar): PStr; stdcall;
  p : PStr;
begin
    DLL := LoadLibrary('biblioteka.dll');
    try
        @Function2 := GetProcAddress(DLL, 'Function_2');
        if @Function2=nil then raise Exception.Create('Bład - nie mogę znaleźć proceudry w bibliotece!');
        p:=Function2(PChar('test'));
    finally
        FreeLibrary(DLL);
    end;
end;

Próbowałem wszystkich kombinacji LPArray, SafeArray z PStr i TStr w miejscu parametru zwracanego przez metodę. W każdym wariancie poza SafeArray + PStr otrzymuje "External error E0434352" na dzień dobry. W tym jednym błąd pojawia się dopiero po wyświetleniu MessageBoxa w metodzie C#

Przyznam, że kopię cały dzień i niewiele mądrego jestem w stanie ustalić w zakresie. Niby na logikę wszystko wygląda ok, a nie działa ...

0

Tak chyba nie zwrócisz. Raczej powinieneś spróbować przekazać tablicę jako parametr zwrotny to jakoś powinno się udać, możliwe że będzie też trzeba w kolejnym parametrze podać wielkość przekazanej tablicy.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.