一个堆被损坏了。C# dllimport,delphi PChar返回值

5
我已经导入了dll。其他部分都可以正常工作,但是导入的方法返回的字符串值给出了以下信息:
未处理的异常0xC0000374:堆已被损坏(参数:0x774C4270)在***.exe中的0x7748EA5F(ntdll.dll)处。
它仍然返回字符串,但我担心这会导致一些难以调试的后续错误。从我测试过的情况来看,感觉可能是任何东西导致这种情况。
这是我的导入代码:
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
private delegate String GetStringDelegate(int handle, int index);

private static GetStringDelegate getString { get; set; }

var addressOfGetString = NativeMethods.GetProcAddress(_handle, "GetString");
getString = (GetStringDelegate)Marshal.GetDelegateForFunctionPointer(addressOfGetString, typeof(GetStringDelegate));

使用方法

getString(Handle, 1);

这个方法可以起作用,但会出现错误。在调试时,仅需按下“继续”按钮即可处理并显示结果。结果是正确的。

Delphi DLL 中是这样实现的。

function GetString(Hnd,Index : Integer) : PChar; stdcall;
begin
 Result:=TControl(Hnd).Stack.GetString(Index);
end;

我有一段类似的整数、双精度浮点数和布尔值代码,这个dll能够正常工作且没有错误。因此我认为它可能会产生某种溢出或内存分配错误的大小问题。
注意:如果我创建控制台应用程序,则会在不断开错误的情况下失败。如果我在没有调试器的情况下运行控制台(ctrl + f5),它仍然可以正常工作,但没有错误。当我从表单应用程序调用时,会生成堆错误。
简而言之,这段代码能够正常工作,但返回整数、布尔等时完美无误地显示堆错误。

在最近版本的Delphi中,PCHAR是Unicode,因此[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]应该改为[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)] - xanatos
@xanatos 除非用户仍在使用D2007或更早版本。 - David Heffernan
@DavidHeffernan 我已经写过 "在最近的版本中"。 - xanatos
如果我将其更改为Unicode,那么该字符串就是一些中文字符。 - Katu
1
@Katu 这意味着该文本确实是 ANSI 编码的,因此 CharSet.Ansi 是适用于该 DLL 的。 - David Heffernan
1个回答

10

当您将字符串作为p/invoke函数的返回值返回时,编组程序将负责释放该内存。它假定内存是在COM堆上分配的,例如使用CoTaskMemAlloc。但是,您的字符串不符合这个要求。

  • 您可以更改Delphi代码以那种方式分配内存。
  • 您可以返回IntPtr,并使用Marshal.PtrToStringAnsi进行手动编组。然后问题在于内存是否需要被释放,如果需要,应如何处理。
  • 您可以返回COM BSTR,但这仅适用于Delphi作为输出参数而不是函数返回值。请参见Why can a WideString not be used as a function return value for interop?
  • 您可以要求调用者分配内存并让被调用者填充它。

我无法完全了解您的代码,但我不会感到惊讶,如果您正在返回PChar(s),其中s是局部变量。这意味着您将返回已释放内存的地址。

总之,从被调用者到调用者传递字符串(或数组或其他动态结构)比传递简单值类型要复杂得多。您需要重新考虑如何做到这一点。


1
我明白了。我不是这个dll的作者,但我会把这个答案指给他。谢谢。 - Katu
1
不,那完全是错的。你的代码在所有版本的.NET上都有问题,从版本1开始就有了。你只是一直在逃避它。它只是碰巧破坏了最新的框架。我不明白为什么你会问这个问题,如果你不想理解这个问题的话。当我告诉你marshaller正在调用CoTaskMemFree时,你不相信我吗? - David Heffernan
好的?嗯,也许我有点匆忙地得出了结论,但它似乎是显而易见的解决方案。但是dll的作者说,在我创建调用来执行此操作(通过启动另一个请求)后,dll将清除引用的字符串,因此字符串引用应该是有效的,我应该能够复制它(我能够做到,只是出现了这个错误),然后允许dll清除其引用。我不明白的是,为什么4.5会抛出这个错误。没有其他版本会这样做。 - Katu
4
谢谢您给我一点启发!您的解决方法是将返回类型从string更改为IntPtr,然后使用Marshal.PtrToStringAnsi处理返回值,这个方法有效。 - Katu
1
我很高兴你坚持下来了。并且很高兴切换到 IntPtr 后问题得到解决。如果有任何疑问,请随时提出,怀疑是明智的。同样地,要质疑自己,在这种情况下,你一定知道转换 .net 框架不太对劲!;-) - David Heffernan
显示剩余4条评论

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