c# .net delphi dll

Llamar a una DLL de Delphi desde una aplicación C#.NET



(4)

EDITAR: He publicado una mejor implementación de esto, a continuación. Dejé esto aquí para que las respuestas tuvieran sentido.

Realicé numerosas búsquedas del método correcto para escribir un archivo DLL en Delphi y poder llamarlo desde C #, pasando y devolviendo cadenas. Mucha de la información era incompleta o incorrecta. Después de mucho ensayo y error, encontré la solución.

Esto fue compilado usando Delphi 2007 y VS 2010. Sospecho que funcionará bien en otras versiones también.

Aquí está el código Delphi. Recuerde incluir información de versión en el proyecto.

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.

Aquí está el código 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(); } } }

Espero que esta información ayude a alguien más a no tener que tirar de sus pelos tanto como yo.


Como dijo Jeroen Pluimers en su comentario, debería tener en cuenta que las cadenas de Delphi son contadas por referencia.

OMI, en circunstancias tales que se supone que debe devolver una cadena en entornos heterogéneos, debe pedirle a la persona que llama que proporcione un búfer para el resultado, y la función debe llenar ese búfer. De esta manera, la persona que llama es responsable de crear el búfer y eliminarlo cuando termina. Si echas un vistazo a las funciones de la API de Win32, verás que hacen lo mismo cuando necesitan devolver una cadena a la persona que llama.

Para hacerlo, puede usar PChar (ya sea PAnsiChar o PWideChar) como el tipo de parámetro de función, pero también debe pedirle a la persona que llama que proporcione el tamaño del búfer. Eche un vistazo a mi respuesta en el siguiente enlace, para obtener un código fuente de muestra:

Intercambio de cadenas (PChar) entre una DLL compilada de Freepascal y un EXE compilado de Delphi

La pregunta es específicamente sobre el intercambio de cadenas entre FreePascal y Delphi, pero la idea y la respuesta también se aplican a su caso.


En Delphi 2009, el código funciona mejor si escribe explícitamente s como AnsiString viz:

var s : Ansistring;

dando el resultado esperado de C # después de la llamada:

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

en lugar de

outputInt = 2, outputString = "T"


En base a las respuestas a mi publicación, he creado un nuevo ejemplo que utiliza búferes de cadenas para las cadenas devueltas, en lugar de simplemente devolver PAnsiChars.

Fuente de Delphi DLL:

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

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

Y aquí hay una clase adicional que muestra cómo cargar la DLL dinámicamente (perdón por las líneas largas):

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


Es más fácil retirar una cadena usando 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;

En c # entonces:

const int stringBufferSize = 1024; var str = new IntPtr(stringBufferSize); string loginResult = Marshal.PtrToStringAnsi(str);