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

从C#.NET应用程序调用Delphi DLL,c#,.net,delphi,dll,C#,.net,Delphi,Dll,编辑:我在下面发布了一个更好的实现。我把这个留在这里是为了让回答有意义。 我已经在Delphi中搜索了许多正确的方法来编写DLL,并且能够从C#调用它,传递和返回字符串。许多信息不完整或不正确。经过反复试验,我找到了解决办法 这是使用Delphi2007和VS2010编译的。我怀疑它在其他版本中也能正常工作 这是Delphi代码。记住在项目中包含版本信息 library DelphiLibrary; uses SysUtils; // Compiled using Delphi 2007.

编辑:我在下面发布了一个更好的实现。我把这个留在这里是为了让回答有意义。

我已经在Delphi中搜索了许多正确的方法来编写DLL,并且能够从C#调用它,传递和返回字符串。许多信息不完整或不正确。经过反复试验,我找到了解决办法

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

这是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代码:


我希望这些信息能帮助其他人不必像我那样把头发拔出来。

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

依我看,在异类环境中应该返回字符串的情况下,应该要求调用方为结果提供缓冲区,函数应该填充该缓冲区。通过这种方式,调用方负责创建缓冲区,并在处理完毕后进行处理。如果您看一下Win32 API函数,就会发现它们在需要向调用方返回字符串时也会执行相同的操作

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


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

根据对我文章的回复,我创建了一个新的示例,它使用字符串缓冲区来处理返回的字符串,而不仅仅是返回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#代码:

这里有一个额外的类,展示了如何动态加载DLL(很抱歉这么长的行):


-Dan

在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#中,然后:


这不是一个真正的问题,但+1:)。我不熟悉delphi,但知道是否可以将其转换为
COM
在c#中使用它很容易,我在这里进行了一些搜索并找到了一些关于delphi和COM关系的资源:您应该重新表述您的问题,说“从c#.NET应用程序使用delphi DLL的正确方法是什么?”然后用文章的其余部分回答你自己。看(你可以回答你自己的问题)这里:你需要非常小心。Delphi字符串被引用计数;
DelphiFunction
s
的refcount在函数末尾将为零,因此
s
使用的内存将返回到内存分配器,并且在DelphiFunction返回后,在C#调用者可以提取内容之前,可能会被其他内容使用(和覆盖)。当这种情况发生时,各种各样的灾难都会发生。在多线程的情况下(特别是在多核系统上),这很快就会实现。Jens-感谢您提供的信息。我想知道我是否做得对-DanThanks感谢你们两位指出这一点——这是一种可能需要很长时间才能消除的潜在缺陷。在我的脑海里,我一直在想这个问题,但是我没有对这个小小的理性之声给予足够的关注。我真丢脸;p我在下面发布了一个更好的例子,它解决了这个问题。在我的测试中,我发现传回StrNew(PChar(s))的结果不会像
GetMem
->
StrPLCopy
那样出错,这更安全吗?您正在返回一个指向堆栈上局部变量的指针。一旦
DelphiFunction
返回,该变量将无效。它可能仍然很幸运,但你不应该依赖它。我正试图将上面的动态版本与我没有源代码的Delphi过程一起使用,但我知道该函数有一个无效的返回,并接受一个bool作为参数。当我运行PinVokesTack时,会出现异常。我可以将C#bool传递给Delphi过程,还是必须将其转换为另一种类型,例如byte?
outputInt = 2, outputString = "This is a test 2"
outputInt = 2, outputString = "T"
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;
    const int stringBufferSize = 1024;

    var  str = new    IntPtr(stringBufferSize);

    string loginResult = Marshal.PtrToStringAnsi(str);