2010-11-12 71 views
24

编辑:我已经发布了一个更好的实现,下面。我在这里留下这个回应是有道理的。从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(); 
     } 
    } 
} 

我希望这个信息可以帮助别人不是要拉自己的头发和我一样多。

+0

不是一个真正的问题,但+1 :)。 – 2010-11-12 09:57:22

+0

我不熟悉德尔福,但知道如果可以将它转换为'COM'它很容易在c#中使用它,我做了一点搜索,并找到一些关于delphi和COM关系的资源:http:// delphi.about.com/library/weekly/aa122804a.htm – 2010-11-12 10:05:26

+5

你应该改说你的问题,说“从C#.NET应用程序使用Delphi DLL的正确方法是什么?”然后用你的帖子的其余部分回答自己。见http://stackoverflow.com/faq(你可以回答你自己的问题)和在这里:http://meta.stackexchange.com/questions/12513/should-i-not-answer-my-own-questions – 2010-11-12 10:36:25

回答

5

由于Jeroen Pluimers在他的评论中说,你应该注意到Delphi字符串是引用计数的。

IMO,在这种情况下,你应该在异构环境中返回一个字符串,你应该要求调用者为结果提供一个缓冲区,并且该函数应该填充该缓冲区。这样,调用者负责创建缓冲区,并在完成缓冲区时进行处理。如果你看一下Win32 API函数,你会发现当他们需要返回一个字符串给调用者时,他们也会这样做。

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

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

的问题是,特别是约交换FreePascal的和德尔福之间串,但这个想法,答案是适用于你的情况太。

+0

感谢你们两人的指出 - 这是潜在的错误,可能会永远消除。在我的脑海里,我对这件事感到好奇,但我没有把足够的注意力放在那种理性的小声音上。对我感到羞耻。 ; p我在下面发布了一个更好的例子,它解决了这个问题。 – 2010-11-14 16:42:48

+0

在我的测试中,我发现传回'StrNew(PChar(s))'的结果不会像'GetMem' - >'StrPLCopy'那样出错,这是否更安全? – 2013-09-24 10:19:08

22

根据对我的文章的回复,我创建了一个新的例子,它为返回的字符串使用字符串缓冲区,而不仅仅是返回PAnsiChars。

德尔福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); 
      } 
     } 
    } 
} 

-Dan

1

在2009年德尔福代码工作更好,如果你明确地输入变量s作为AnsiString类型即:

var s : Ansistring; 

给从C#预期的结果调用以下内容:

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

代替

outputInt = 2, outputString = "T" 
0

更容易使用到retireve字符串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); 
+1

您正在返回一个指向堆栈上局部变量的指针。一旦'DelphiFunction'返回,该变量将无效。它可能仍然运气好,但你不应该依赖它。 – 2015-11-12 04:53:30