从 C# .NET 应用程序调用 Delphi DLL
编辑:我在下面发布了一个更好的实现。我将其留在这里,以便响应有意义。
我已经进行了大量搜索,寻找在 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();
}
}
}
我希望这些信息可以帮助其他人不必像我一样费力气。
EDIT: I've posted a better implementation of this, below. I left this here so the responses would make sense.
I've done numerous searches for the correct method for writing a DLL in Delphi, and being able to call it from C#, passing and returning strings. A lot of the information was incomplete or incorrect. After much trial and error, I found the solution.
This was compiled using Delphi 2007 and VS 2010. I suspect it will work fine in other versions as well.
Here's the Delphi code. Remember to include version information in the project.
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.
Here's the 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
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();
}
}
}
I hope this information helps someone else to not have to pull their hair out as much as I did.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
根据对我的帖子的回复,我创建了一个新示例,该示例使用字符串缓冲区来返回字符串,而不仅仅是返回 PAnsiChars。
Delphi DLL 源代码:
C# 代码:
这里有一个附加类,显示如何动态加载 DLL(抱歉,代码太长):
-Dan
Based on responses to my post, I have created a new example that uses string buffers for the returned strings, instead of just returning PAnsiChars.
Delphi DLL source:
C# Code:
And here's an additional class that shows how to load the DLL dynamically (sorry for the long lines):
-Dan
正如 Jeroen Pluimers 在他的评论中所说,您应该注意 Delphi 字符串是引用计数的。
IMO,在您应该在异构环境中返回字符串的情况下,您应该要求调用者为结果提供一个缓冲区,并且该函数应该填充该缓冲区。这样,调用者负责创建缓冲区并在使用完毕后将其释放。如果您查看一下 Win32 API 函数,您会发现它们在需要将字符串返回给调用者时会执行相同的操作。
为此,您可以使用 PChar(PAnsiChar 或 PWideChar)作为函数参数的类型,但您还应该要求调用者也提供缓冲区的大小。看看我在下面的链接中的答案,获取示例源代码:
在 Freepascal 编译的 DLL 和 Delphi 编译的 EXE 之间交换字符串 (PChar)
这个问题具体是关于 FreePascal 和 Delphi 之间交换字符串的,但是想法和答案也适用于你的情况。
As Jeroen Pluimers said in his comment, you should take note that Delphi strings are reference-counted.
IMO, in such circumstances which you are supposed to return a string in heterogeneous environments, you should ask the caller to provide a buffer for the result, and the function should fill that buffer. This way, the caller is responsible for creating the buffer and disposing it when it is done with it. If you take a look at Win32 API functions, you'll see they do the same when they need to return a string to a caller.
To do so, you can use PChar (either PAnsiChar or PWideChar) as the type of function parameter, but you should also ask caller to provide size of the buffer too. Take a look at my answer in the link below, for a sample source code:
Exchanging strings (PChar) between a Freepascal compiled DLL and a Delphi compiled EXE
The question is specifically about exchanging string between FreePascal and Delphi, but the idea and the answer is applicable to your case too.
在 Delphi 2009 中,如果您将变量 s 显式键入为 AnsiString,则代码效果会更好,即:
在调用后给出 C# 的预期结果:
而不是
In Delphi 2009 the code works better if you explicitly type variable s as an AnsiString viz:
giving the expected result from C# following the call:
instead of
使用 PString 更容易退休字符串:
在 c# 中,然后:
It is easier to retireve a string using PString:
In c# then:
如果有人在 2022 年或更高版本遇到此错误,请使用 VisualStudio 2010 编译 DLLImport 和 P/Invoke 操作,未来版本(可能除了 Visual Studio 2012)不允许在面向 x64 机器的 C# 应用程序中从 Delphi 加载托管代码来自非托管 x86 DLL 库的系统。使用 .Net Framework 4.0 而不是 .Net Framework 4.8 或更高版本,在处理/从 RAD Studio 和 Delphi 编译器导出时也避免使用 .Net Core、Standard 和 .Net。
If somebody experienced this error in 2022 or later, use VisualStudio 2010 to compile DLLImport and P/Invoke operations, the future versions (may be except for Visual Studio 2012) do not allow to load managed code from Delphi in C# application that target x64 machiene system from an unmanaged x86 DLL library. Use .Net Framework 4.0 instead of .Net Framework 4.8 or later, also avoid using .Net Core, Standard and .Net when dealing with / exporting from RAD Studio and Delphi compilers.