2010-11-12 26 views
24

EDIT: Ho pubblicato una migliore implementazione di questo, di seguito. Ho lasciato questo qui così le risposte avrebbero avuto senso.Chiamare una DLL Delphi da un'applicazione C# .NET

Ho eseguito numerose ricerche per il metodo corretto per scrivere una DLL in Delphi e per poterlo chiamare da C#, passando e restituendo stringhe. Molte informazioni erano incomplete o errate. Dopo molte prove ed errori, ho trovato la soluzione.

Questo è stato compilato utilizzando Delphi 2007 e VS 2010. Ho il sospetto che funzionerà bene anche in altre versioni.

Ecco il codice Delphi. Ricordati di includere le informazioni sulla versione nel progetto.

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. 

Ecco il codice 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(); 
     } 
    } 
} 

Spero che queste informazioni aiuta a qualcun altro di non dover tirare i capelli tanto quanto ho fatto.

+0

Non proprio una domanda, ma uno :). –

+0

Non ho familiarità con Delphi ma so se è possibile convertirlo in 'COM' è facile usarlo in C#, ho fatto una piccola ricerca e ho trovato una risorsa che riguarda delphi e la relazione COM qui: http: // delphi.about.com/library/weekly/aa122804a.htm –

+5

Dovresti riformulare la domanda per dire "Qual è il modo corretto di utilizzare una DLL Delphi da un'applicazione C# .NET?" e poi rispondi a te stesso con il resto del tuo post. Vedi http://stackoverflow.com/faq (Puoi rispondere alla tua stessa domanda) e qui: http://meta.stackexchange.com/questions/12513/should-i-not-answer-my-own-questions –

risposta

5

Come ha detto Jeroen Pluimers nel suo commento, è necessario tenere presente che le stringhe Delphi sono conteggiate con riferimento.

IMO, in tali circostanze che si suppone di restituire una stringa in ambienti eterogenei, è necessario chiedere al chiamante di fornire un buffer per il risultato e la funzione dovrebbe riempire tale buffer. In questo modo, il chiamante è responsabile della creazione del buffer e del suo smaltimento una volta terminato. Se dai un'occhiata alle funzioni dell'API di Win32, vedrai che fanno lo stesso quando devono restituire una stringa a un chiamante.

Per fare ciò, è possibile utilizzare PChar (PAnsiChar o PWideChar) come tipo di parametro di funzione, ma è necessario anche chiedere al chiamante di fornire anche la dimensione del buffer. Date un'occhiata alla mia risposta nel link qui sotto, per un codice sorgente di esempio:

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

La domanda è specificamente circa lo scambio di corda tra FreePascal e Delphi, ma l'idea e la risposta è applicabile al vostro caso troppo .

+0

Grazie a tutti e due per averlo fatto notare - è il tipo di potenziale bug che potrebbe richiedere un'eternità per estirparlo. Nel profondo della mia mente mi stavo chiedendo di questo, ma non ho prestato abbastanza attenzione a quella piccola voce della ragione. Mi vergogno. ; p Ho pubblicato un esempio migliore di seguito, che risolve questo problema. –

+0

Nel mio test ho trovato che passare il risultato di 'StrNew (PChar (s))' non ha errori come 'GetMem' ->' StrPLCopy', è più sicuro? –

22

In base alle risposte al mio post, ho creato un nuovo esempio che utilizza i buffer di stringa per le stringhe restituite, invece di restituire solo PAnsiChars.

Delphi DLL fonte:

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# Codice:

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(); 
     } 
    } 
} 

Ed ecco una classe aggiuntiva che mostra come caricare la DLL in modo dinamico (scusate per le lunghe code):

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

Nel Delphi 2009 il codice funziona meglio se si digita in modo esplicito variabile s come cioè AnsiString:

var s : Ansistring; 

dando il risultato atteso da C# a seguito della chiamata:

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

invece di

outputInt = 2, outputString = "T" 
0

È più semplice recuperare una stringa utilizzando 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; 

in C# allora:

const int stringBufferSize = 1024; 

    var str = new IntPtr(stringBufferSize); 

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

Si sta restituendo un puntatore a una variabile locale nello stack. Una volta restituito 'DelphiFunction', quella variabile non sarà valida. Può ancora funzionare per fortuna, ma non si dovrebbe fare affidamento su di esso. –

Problemi correlati