在Delphi中,有一个函数从过程SomeFunc()返回Result:= NewStr(PChar(somestring)):PChar; STDCALL;
根据我的理解,NewStr只在本地堆上分配一个缓冲区…而SomeFunc正在返回一个指向它的指针.
在.NET 4.0(客户端配置文件)中,通过C#我可以使用:
[DllImport("SomeDelphi.dll",EntryPoint = "SomeFunc",CallingConvention = CallingConvention.StdCall)] public static extern String SomeFunc(uint ObjID);
这在Windows 7 .NET 4.0 Client Profile中很有效(或者正如David所说,“似乎工作”).在Windows 8中,它具有不可预测的行为,这使我走上了这条道路.
所以我决定在.NET 4.5中尝试相同的代码并获得Heap损坏错误.好的,现在我知道这不是正确的做事方式.所以我进一步挖掘:
仍在.NET 4.5中
[DllImport("SomeDelphi.dll",CallingConvention = CallingConvention.StdCall)] public static extern IntPtr _SomeFunc(); public static String SomeFunc() { IntPtr pstr = _SomeFunc(); return Marshal.PtrToStringAnsi(pstr); }
这没有任何障碍.我(新手)担心的是NewStr()已经分配了这个内存,它只是永远坐在那里.我的担忧无效吗?
在.NET 4.0中,我甚至可以这样做,它永远不会抛出异常:
[DllImport("SomeDelphi.dll",CallingConvention = CallingConvention.StdCall)] public static extern IntPtr _SomeFunc(); public static String SomeFunc() { String str; IntPtr pstr = _SomeFunc(); str = Marshal.PtrToStringAnsi(pstr); Marshal.FreeCoTaskMem(pstr); return str; }
但是,此代码在4.5中抛出相同的堆异常.这让我相信问题在于.Net 4.5中,封送程序正在尝试FreeCoTaskMem(),这就是抛出异常的原因.
所以问题:
>为什么这在.Net 4.0而不是4.5中有效?
>我应该关注NewStr()在本机DLL中的分配吗?
>如果对#2回答“否”,那么第二个代码示例是否有效?
解决方法
但是marshaller也知道字符串必须已经在某个堆上分配.并且由于本机函数已经完成,因此不能指望本机代码解除分配.所以marshaller解除了它.它还假设使用的共享堆是COM堆.因此,marshaller在本机代码返回的指针上调用CoTaskMemFree.这就是导致错误的原因.
结论是,如果您希望在C#p / invoke端使用字符串返回值,则需要在本机端匹配该值.为此,请返回PAnsiChar或PWideChar,并通过调用CoTaskMemAlloc来分配字符数组.
你绝对不能在这里使用NewStr.实际上你永远不应该调用那个函数.您现有的代码被全面破坏,您对NewStr的每次调用都会导致内存泄漏.
一些简单的示例代码将起作用:
德尔福
function SomeFunc: PAnsiChar; stdcall; var SomeString: AnsiString; ByteCount: Integer; begin SomeString := ... ByteCount := (Length(SomeString)+1)*SizeOf(SomeString[1]); Result := CoTaskMemAlloc(ByteCount); Move(PAnsiChar(SomeString)^,Result^,ByteCount); end;
C#
[DllImport("SomeDelphi.dll")] public static extern string SomeFunc();
为方便起见,您可能希望将本机代码包装在帮助程序中.
function COMHeapAllocatedString(const s: AnsiString): PAnsiChar; stdcall; var ByteCount: Integer; begin ByteCount := (Length(s)+1)*SizeOf(s[1]); Result := CoTaskMemAlloc(ByteCount); Move(PAnsiChar(s)^,ByteCount); end;
另一种选择是返回BSTR并在C#端使用MarshalAs(UnmanagedType.BStr).但是,在此之前,请阅读:Why can a WideString not be used as a function return value for interop?
为什么在不同的.net版本中看到不同的行为?很难说肯定.你的代码在两者中都是一样的.也许较新的版本更好地检测此类错误.也许还有其他一些不同之处.您是否在同一台机器上运行4.0和4.5,相同的操作系统.也许您的4.0测试是在较旧的操作系统上运行的,它不会为COM堆损坏引发错误.