ASP.NET网站在IIS Web服务器上调用Delphi DLL时,在返回PChar字符串时会出现死锁问题。

4

如果我不返回任何内容,或者返回一个整数,那么就可以正常工作。但是如果我尝试返回一个PChar,例如...

result := PChar('')  or   result:= PChar('Hello')

网页应用程序会卡住,我会在任务管理器中看到它的内存计数逐渐增加。

奇怪的是,该DLL在VStudio调试服务器上或通过C#应用程序正常工作。唯一我能想到的区别就是IIS服务器正在运行64位Windows。

这似乎不是兼容性问题,因为我可以成功地从DLL中写入文本文件和执行其他操作……但我无法返回PChar字符串。

尝试使用PWideChar,尝试返回'something\0',尽我所能尝试了所有方法,但不幸的是没有成功。

[DllImport("TheLib.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
private static extern string SomeFunction();

string result = SomeFunction();

delphi:

library TheLib;

function SomeFunction() : PChar export; stdcall;
begin
return PChar('');
end;

exports
    SomeFunction
3个回答

5

Dampsquid的分析是正确的,所以我不会重复。然而,我更喜欢一种我认为更优雅的解决方案。我对这样一个问题的首选解决方案是使用Delphi Widestring,它是一个BSTR

在Delphi方面,您可以像这样编写:

function SomeFunction: Widestring; stdcall;
begin
  Result := 'Hello';
end;

在C#方面,您可以这样做:
[DllImport(@"TheLib.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string SomeFunction();

就是这样。因为双方都使用相同的COM分配器进行内存分配,所以一切都正常工作。

更新1

@NoPyGod有趣地指出,这段代码会失败并出现运行时错误。经过调查,我觉得这是Delphi端的问题。例如,如果我们保留C#代码不变,并使用以下内容,则错误将得到解决:

function SomeFunction: PChar; stdcall;
begin
  Result := SysAllocString(WideString('Hello'));
end;

似乎Delphi类型为WideString的返回值没有被正确处理。输出参数和变量参数的处理方式与预期相同。我不知道为什么返回值会以这种方式失败。
更新2:
原来,WideString类型的Delphi ABI与Microsoft工具不兼容。您不应使用WideString作为返回类型,而是通过out参数返回。有关详细信息,请参见为什么不能将WideString用作Interop的函数返回值?

我必须等到周一才能在工作中的IIS上尝试您的解决方案,但是它背后的推理听起来很有道理。不过有一件事..我一直在家里测试代码,但是我无法让返回值正常工作,它只会给出一个异常“尝试读取或写入受保护的内存”。然而,我可以通过使用“out”变量并以这种方式传递结果来实现我想要做的事情。您知道为什么我无法返回吗?我正在像您的代码一样进行编组结果。是因为它是Delphi 2007吗?我可以使用“out”技巧,但我想知道为什么需要这样做。 - NoPyGod
我看到Alex在这里有同样的问题 - http://stackoverflow.com/questions/8533505/accessing-delphi-dll-throwing-ocasional-exception - NoPyGod
1
在C#中,我们不应该像在C++中那样调用SysFreeString吗? - kobik
1
@kobik P/Invoke marshaller会为您完成这项工作,因此您不需要自己处理。更重要的是,由于C#声明不使用IntPtr,因此您无法获取本机指针以调用SysFreeString - David Heffernan

3

你不能返回这样的字符串,该字符串是局部变量,一旦函数返回,该字符串将被释放,导致返回的PChar指向无效位置。

你需要传入一个指针,在DLL内部进行动态创建和释放字符串,或在DLL中创建一个静态缓冲区并返回它。

迄今为止最安全的方法是将指针传递到函数中:

function SomeFunction( Buffer: PChar; MaxLength: PInteger ): wordbool; stdcall;
{
  // fill in the buffer and set MaxLength to length of data
}

在调用dll之前,您应该将MaxLength设置为缓冲区的大小,以便dll可以检查返回的数据是否有足够的空间。


C#端的代码会是什么样子?因为这里看起来除了maxlength参数之外与这里完全相同。另外,为什么这段代码在IIS之外也能工作?谢谢。 - NoPyGod
我猜你打错了,Pchar应该是某个变量名。等我到电脑前试一下。 - NoPyGod
你可以返回一个BSTR并让编组程序处理。那将是我首选的选项。 - David Heffernan
抱歉,我犯了一个错误。缓冲区是var,pchar是类型。最近没有做太多C#的工作,所以无法对此进行评论。至于它在IIS之外是否起作用,那只是因为指向的内存仍然包含字符串,尽管它不再有效,但它只是幸运地还没有被覆盖。 - Dampsquid

-1
尝试在应用程序池高级设置中启用32位应用程序:

enter image description here


我相信这是在使用32位COM Dll时成立的,而在C#中导入它时则不成立。 - kobik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接