从C#.NET应用程序调用Delphi DLL

编辑:我在下面发布了一个更好的实现。 我把它留在这里,所以回答是有道理的。

我已经完成了大量搜索在Delphi中编写DLL的正确方法,并能够从C#调用它,传递和返回字符串。 很多信息都不完整或不正确。 经过多次试验和错误,我找到了解决方案。

这是使用Delphi 2007和VS 2010编译的。我怀疑它在其他版本中也能正常工作。

这是Delphi代码。 请记住在项目中包含版本信息。

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. 

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

我希望这些信息可以帮助别人不必像我一样把头发拉出来。

基于对我的post的回复,我创建了一个新的示例,它为返回的字符串使用字符串缓冲区,而不是仅返回PAnsiChars。

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#代码:

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

这里有一个额外的类,它显示了如何动态加载DLL(对于长行而言):

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

-担

正如Jeroen Pluimers在评论中所说,你应该注意Delphi字符串是引用计数的。

IMO,在您应该在异构环境中返回字符串的情况下,您应该要求调用者为结果提供缓冲区,并且该函数应该填充该缓冲区。 这样,调用者负责创建缓冲区并在完成缓冲区时进行处理。 如果您看一下Win32 API函数,当他们需要将字符串返回给调用者时,您会看到它们执行相同的操作。

为此,您可以使用PChar(PAnsiChar或PWideChar)作为函数参数的类型,但您也应该要求调用者提供缓冲区的大小。 请在下面的链接中查看我的答案,以获取示例源代码:

在Freepascal编译的DLL和Delphi编译的EXE之间交换字符串(PChar)

问题是关于在FreePascal和Delphi之间交换字符串,但这个想法和答案也适用于你的情况。

在Delphi 2009中,如果您明确地将变量s键入为AnsiString,则代码可以更好地运行:

 var s : Ansistring; 

在通话后给出C#的预期结果:

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

代替

 outputInt = 2, outputString = "T" 

使用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; 

在c#然后:

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