【问题标题】:Calling a Delphi DLL from a C# .NET application从 C# .NET 应用程序调用 Delphi DLL
【发布时间】:2010-11-12 09:49:20
【问题描述】:

编辑:我在下面发布了一个更好的实现。我把它留在这里,这样回复才有意义。

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

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

这是德尔福代码。记得在项目中包含版本信息。

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

我希望这些信息可以帮助其他人不必像我那样拔头发。

【问题讨论】:

  • 不是真正的问题,而是 +1 :)。
  • 我不熟悉delphi,但知道是否可以将其转换为COM,它在c#中易于使用,我进行了一些搜索并找到了一些关于delphi和COM的资源这里的关系:delphi.about.com/library/weekly/aa122804a.htm
  • 您应该将您的问题改写为“从 C# .NET 应用程序中使用 Delphi DLL 的正确方法是什么?”然后用其余的帖子回答自己。见stackoverflow.com/faq(你可以回答你自己的问题)和这里:meta.stackexchange.com/questions/12513/…
  • 这里需要非常小心。 Delphi 字符串是引用计数的; DelphiFunctions 的引用计数将在函数结束时为零,因此 s 使用的内存将返回给内存分配器,并可能在之后被其他东西使用(和覆盖) DelphiFunction 在 C# 调用者获取内容之前返回。当这种情况发生时,就会发生各种破坏。在多线程情况下(尤其是在多核系统上)可能很快。
  • 詹斯 - 感谢您提供信息。我想知道我这样做是否正确。 -丹

标签: c# .net delphi dll


【解决方案1】:

根据对我的帖子的回复,我创建了一个新示例,该示例使用字符串缓冲区作为返回的字符串,而不仅仅是返回 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);
            }
        }
    }
}

-丹

【讨论】:

  • 我正在尝试将上面的动态版本与我没有源代码的 Delphi 过程一起使用,但我知道该函数有一个 void 返回并接受一个布尔值作为参数.运行时出现 PInvokeStackImbalance 异常。我可以将 C# bool 传递给 Delphi 过程,还是必须将其转换为另一种类型,例如字节?
【解决方案2】:

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

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

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

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

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

【讨论】:

  • 感谢你们两位指出这一点 - 这是一种可能需要永远清除的潜在错误。在我的脑海里,我在想这件事,但我没有足够注意那个小小的理性声音。为我感到羞耻。 ;p 我在下面发布了一个更好的示例,可以解决这个问题。
  • 在我的测试中,我发现传回StrNew(PChar(s)) 的结果不会像GetMem -> StrPLCopy 那样出错,这样更安全吗?
【解决方案3】:

在 Delphi 2009 中,如果您将变量 s 显式键入为 AnsiString 即,代码会更好地工作:

var s : Ansistring;

在调用之后从 C# 中给出预期结果:

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

而不是

outputInt = 2, outputString = "T"

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 您正在返回一个指向堆栈上的局部变量的指针。一旦DelphiFunction 返回,该变量将无效。它可能仍然靠运气工作,但你不应该依赖它。
    【解决方案5】:

    如果有人在 2022 年或以后遇到此错误,请使用 VisualStudio 2010 编译 DLLImport 和 P/Invoke 操作,以后的版本(可能除了 Visual Studio 2012)不允许在 C# 应用程序中从 Delphi 加载托管代码来自非托管 x86 DLL 库的目标 x64 机器系统。使用 .Net Framework 4.0 而不是 .Net Framework 4.8 或更高版本,在处理/导出 RAD Studio 和 Delphi 编译器时也避免使用 .Net Core、Standard 和 .Net。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多