2010-11-12 18 views
24

EDYCJA: Poniżej zamieściłem lepszą implementację tego. Zostawiłem to tutaj, więc odpowiedzi miałyby sens.Wywołanie biblioteki DLL Delphi z aplikacji C# .NET

Zrobiłem wiele poszukiwań dla prawidłowej metody pisania DLL w Delphi, i jest w stanie wywołać go z C#, przekazując i zwracając ciągi. Wiele informacji było niepełnych lub niepoprawnych. Po wielu próbach i błędach znalazłem rozwiązanie.

Został skompilowany przy użyciu Delphi 2007 i VS 2010. Podejrzewam, że będzie dobrze działać także w innych wersjach.

Oto kod Delphi. Pamiętaj o dołączeniu informacji o wersji do projektu.

library DelphiLibrary; 

uses SysUtils; 

// Compiled using Delphi 2007. 

// NOTE: If your project doesn't have version information included, you may 
// receive the error "The "ResolveManifestFiles" task failed unexpectedly" 
// when compiling the C# application. 

{$R *.res} 

// Example function takes an input integer and input string, and returns 
// inputInt + 1, and inputString + ' ' + IntToStr(outputInt) as output 
// parameters. If successful, the return result is nil (null), otherwise it is 
// the exception message string. 


// NOTE: I've posted a better version of this below. You should use that instead. 

function DelphiFunction(inputInt : integer; inputString : PAnsiChar; 
         out outputInt : integer; out outputString : PAnsiChar) 
         : PAnsiChar; stdcall; export; 
var s : string; 
begin 
    outputInt := 0; 
    outputString := nil; 
    try 
    outputInt := inputInt + 1; 
    s := inputString + ' ' + IntToStr(outputInt); 
    outputString := PAnsiChar(s); 
    Result := nil; 
    except 
    on e : exception do Result := PAnsiChar(e.Message); 
    end; 
end; 

// I would have thought having "export" at the end of the function declartion 
// (above) would have been enough to export the function, but I couldn't get it 
// to work without this line also. 
exports DelphiFunction; 

begin 
end. 

Oto kod C#:

using System; 
using System.Runtime.InteropServices; 

namespace CsharpApp 
{ 
    class Program 
    { 
     // I added DelphiLibrary.dll to my project (NOT in References, but 
     // "Add existing file"). In Properties for the dll, I set "BuildAction" 
     // to None, and "Copy to Output Directory" to "Copy always". 
     // Make sure your Delphi dll has version information included. 

     [DllImport("DelphiLibrary.dll", 
        CallingConvention = CallingConvention.StdCall, 
        CharSet = CharSet.Ansi)] 
     public static extern 
      string DelphiFunction(int inputInt, string inputString, 
            out int outputInt, out string outputString); 

     static void Main(string[] args) 
     { 
      int inputInt = 1; 
      string inputString = "This is a test"; 
      int outputInt; 
      string outputString; 


// NOTE: I've posted a better version of this below. You should use that instead. 


      Console.WriteLine("inputInt = {0}, intputString = \"{1}\"", 
           inputInt, inputString); 
      var errorString = DelphiFunction(inputInt, inputString, 
              out outputInt, out outputString); 
      if (errorString != null) 
       Console.WriteLine("Error = \"{0}\"", errorString); 
      else 
       Console.WriteLine("outputInt = {0}, outputString = \"{1}\"", 
            outputInt, outputString); 
      Console.Write("Press Enter:"); 
      Console.ReadLine(); 
     } 
    } 
} 

Mam nadzieję, że ta informacja pomaga kogoś innego, aby nie trzeba ciągnąć swoje włosy tak samo jak ja.

+0

naprawdę nie jest pytanie, ale +1 :). –

+0

Nie jestem zaznajomiony z delphi, ale wiem, czy jest możliwe przekonwertowanie go na "COM' jego łatwego w użyciu w języku C#, zrobiłem małe wyszukiwanie i znajdę trochę zasobów, które są o delphi i relacji COM tutaj: http: // delphi.about.com/library/weekly/aa122804a.htm –

+5

Powinieneś przeformułować swoje pytanie, aby powiedzieć "Jaki jest właściwy sposób korzystania z biblioteki DLL Delphi z aplikacji C# .NET?" a następnie odpowiedz sobie na resztę swojego postu. Zobacz http://stackoverflow.com/faq (możesz odpowiedzieć na własne pytanie) i tutaj: http://meta.stackexchange.com/questions/12513/should-i-not-answer-my-own-questions –

Odpowiedz

5

Jak powiedział Jeroen Pluimers w swoim komentarzu, należy wziąć pod uwagę, że ciągi Delphi są traktowane jako odnośniki.

IMO, w takich okolicznościach, w których powinieneś zwrócić ciąg znaków w heterogenicznych środowiskach, powinieneś poprosić rozmówcę o dostarczenie bufora dla wyniku, a funkcja powinna wypełnić ten bufor. W ten sposób wywołujący odpowiada za utworzenie bufora i usunięcie go, gdy zostanie z nim wykonany. Jeśli spojrzysz na funkcje Win32 API, zobaczysz, że robią to samo, gdy muszą zwrócić ciąg do dzwoniącego.

Aby to zrobić, możesz użyć PChar (albo PAnsiChar lub PWideChar) jako typ parametru funkcji, ale powinieneś także poprosić rozmówcę o podanie rozmiaru bufora. Spójrz na moją odpowiedź w poniższym linku do kodu źródłowego próbki:

Exchanging strings (PChar) between a Freepascal compiled DLL and a Delphi compiled EXE

Pytanie jest konkretnie o wymianie łańcucha między FreePascal i Delphi, ale pomysł i odpowiedź ma zastosowanie do sprawy zbyt .

+0

Dziękuję wam wszystkim za wskazanie tego - to rodzaj potencjalnego błędu, który może trwać wiecznie, aby wyplenić. W głębi duszy zastanawiałem się nad tym, ale nie zwracałem wystarczającej uwagi na ten mały głos rozsądku. Wstyd mi. ; p Poniżej zamieściłem lepszy przykład, który rozwiązuje ten problem. –

+0

W moich testach stwierdziłem, że przekazywanie wyniku 'StrNew (PChar (s)) nie powoduje błędów takich jak' GetMem' -> 'StrPLCopy', czy to jest bezpieczniejsze? –

22

Na podstawie odpowiedzi na mój wpis, stworzyłem nowy przykład, który używa buforów ciągów do zwracanych ciągów, zamiast tylko zwracania PAnsiChars.

Delphi DLL źródło:

library DelphiLibrary; 

uses SysUtils; 

// Compiled using Delphi 2007. 

// NOTE: If your project doesn't have version information included, you may 
// receive the error "The "ResolveManifestFiles" task failed unexpectedly" 
// when compiling the C# application. 

{$R *.res} 

// A note on returing strings. I had originally written this so that the 
// output string was just a PAnsiChar. But several people pointed out that 
// since Delphi strings are reference-counted, this was a bad idea since the 
// memory for the string could get overwritten before it was used. 
// 
// Because of this, I re-wrote the example so that you have to pass a buffer for 
// the result strings. I saw some examples of how to do this, where they 
// returned the actual string length also. This isn't necessary, because the 
// string is null-terminated, and in fact the examples themselves never used the 
// returned string length. 


// Example function takes an input integer and input string, and returns 
// inputInt + 1, and inputString + ' ' + IntToStr(outputInt). If successful, 
// the return result is true, otherwise errorMsgBuffer contains the the 
// exception message string. 
function DelphiFunction(inputInt : integer; 
         inputString : PAnsiChar; 
         out outputInt : integer; 
         outputStringBufferSize : integer; 
         var outputStringBuffer : PAnsiChar; 
         errorMsgBufferSize : integer; 
         var errorMsgBuffer : PAnsiChar) 
         : WordBool; stdcall; export; 
var s : string; 
begin 
    outputInt := 0; 
    try 
    outputInt := inputInt + 1; 
    s := inputString + ' ' + IntToStr(outputInt); 
    StrLCopy(outputStringBuffer, PAnsiChar(s), outputStringBufferSize-1); 
    errorMsgBuffer[0] := #0; 
    Result := true; 
    except 
    on e : exception do 
    begin 
     StrLCopy(errorMsgBuffer, PAnsiChar(e.Message), errorMsgBufferSize-1); 
     Result := false; 
    end; 
    end; 
end; 

// I would have thought having "export" at the end of the function declartion 
// (above) would have been enough to export the function, but I couldn't get it 
// to work without this line also. 
exports DelphiFunction; 

begin 
end. 

C# Kod:

using System; 
using System.Runtime.InteropServices; 

namespace CsharpApp 
{ 
    class Program 
    { 
     // I added DelphiLibrary.dll to my project (NOT in References, but 
     // "Add existing file"). In Properties for the dll, I set "BuildAction" 
     // to None, and "Copy to Output Directory" to "Copy always". 
     // Make sure your Delphi dll has version information included. 

     [DllImport("DelphiLibrary.dll", 
        CallingConvention = CallingConvention.StdCall, 
        CharSet = CharSet.Ansi)] 
     public static extern bool 
      DelphiFunction(int inputInt, string inputString, 
          out int outputInt, 
          int outputStringBufferSize, ref string outputStringBuffer, 
          int errorMsgBufferSize, ref string errorMsgBuffer); 

     static void Main(string[] args) 
     { 
      int inputInt = 1; 
      string inputString = "This is a test"; 
      int outputInt; 
      const int stringBufferSize = 1024; 
      var outputStringBuffer = new String('\x00', stringBufferSize); 
      var errorMsgBuffer = new String('\x00', stringBufferSize); 

      if (!DelphiFunction(inputInt, inputString, 
           out outputInt, 
           stringBufferSize, ref outputStringBuffer, 
           stringBufferSize, ref errorMsgBuffer)) 
       Console.WriteLine("Error = \"{0}\"", errorMsgBuffer); 
      else 
       Console.WriteLine("outputInt = {0}, outputString = \"{1}\"", 
            outputInt, outputStringBuffer); 

      Console.Write("Press Enter:"); 
      Console.ReadLine(); 
     } 
    } 
} 

A oto dodatkowe klasy, który pokazuje w jaki sposób można załadować biblioteki DLL dynamicznie (przepraszam za długich liniach):

using System; 
using System.Runtime.InteropServices; 

namespace CsharpApp 
{ 
    static class DynamicLinking 
    { 
     [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")] 
     static extern int LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName); 

     [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")] 
     static extern IntPtr GetProcAddress(int hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName); 

     [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")] 
     static extern bool FreeLibrary(int hModule); 

     [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)] 
     delegate bool DelphiFunction(int inputInt, string inputString, 
            out int outputInt, 
            int outputStringBufferSize, ref string outputStringBuffer, 
            int errorMsgBufferSize, ref string errorMsgBuffer); 

     public static void CallDelphiFunction(int inputInt, string inputString, 
               out int outputInt, out string outputString) 
     { 
      const string dllName = "DelphiLib.dll"; 
      const string functionName = "DelphiFunction"; 

      int libHandle = LoadLibrary(dllName); 
      if (libHandle == 0) 
       throw new Exception(string.Format("Could not load library \"{0}\"", dllName)); 
      try 
      { 
       var delphiFunctionAddress = GetProcAddress(libHandle, functionName); 
       if (delphiFunctionAddress == IntPtr.Zero) 
        throw new Exception(string.Format("Can't find function \"{0}\" in library \"{1}\"", functionName, dllName)); 

       var delphiFunction = (DelphiFunction)Marshal.GetDelegateForFunctionPointer(delphiFunctionAddress, typeof(DelphiFunction)); 

       const int stringBufferSize = 1024; 
       var outputStringBuffer = new String('\x00', stringBufferSize); 
       var errorMsgBuffer = new String('\x00', stringBufferSize); 

       if (!delphiFunction(inputInt, inputString, out outputInt, 
            stringBufferSize, ref outputStringBuffer, 
            stringBufferSize, ref errorMsgBuffer)) 
        throw new Exception(errorMsgBuffer); 

       outputString = outputStringBuffer; 
      } 
      finally 
      { 
       FreeLibrary(libHandle); 
      } 
     } 
    } 
} 

-Dan

1

W Delphi 2009 kod działa lepiej, jeśli jawnie typu zmiennej s jako mianowicie AnsiString:

var s : Ansistring; 

daje oczekiwany wynik z C# następujące połączenia:

outputInt = 2, outputString = "This is a test 2" 

zamiast

outputInt = 2, outputString = "T" 
0

Łatwiej jest wycofać ciąg za pomocą PString:

function DelphiFunction(inputString : PAnsiChar; 
        var outputStringBuffer : PString; 
        var errorMsgBuffer : PString) 
        : WordBool; stdcall; export; 
var 
    s : string; 
begin 
    try 
    s := inputString; 
    outputStringBuffer:=PString(AnsiString(s)); 
    Result := true; 
    except 
    on e : exception do 
    begin 
     s:= 'error'; 
     errorMsgBuffer:=PString(AnsiString(e.Message)); 
     Result := false; 
    end; 
    end; 
end; 

w C# następnie:

const int stringBufferSize = 1024; 

    var str = new IntPtr(stringBufferSize); 

    string loginResult = Marshal.PtrToStringAnsi(str); 
+1

Powodujesz zwracanie wskaźnika do zmiennej lokalnej na stosie. Gdy 'DelphiFunction' wróci, ta zmienna nie będzie ważna. Może nadal działać na szczęście, ale nie powinieneś polegać na nim. –

Powiązane problemy