Delphi DLL从C#返回字符串… .NET 4.5 Heap Corruption但.NET 4.
我一直在学习将非托管DLL导入编组到C#中…而且我遇到了一些我不太了解的东西.
在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中有效? 解决方法
在文档中很难找到的关键信息涉及marshaller使用返回值为string类型的p / invoke函数执行的操作.返回值映射到以null结尾的字符数组,即Win32术语中的LPCTSTR.到现在为止还挺好.
但是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堆损坏引发错误. 我的观点是,没有必要理解为什么破碎的代码似乎有效.代码坏了.修复它,继续前进. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |