加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Delphi DLL从C#返回字符串… .NET 4.5 Heap Corruption但.NET 4.

发布时间:2020-12-15 04:11:44 所属栏目:大数据 来源:网络整理
导读:我一直在学习将非托管DLL导入编组到C#中…而且我遇到了一些我不太了解的东西. 在Delphi中,有一个函数从过程SomeFunc()返回Result:= NewStr(PChar(somestring)):PChar; STDCALL; 根据我的理解,NewStr只在本地堆上分配一个缓冲区…而SomeFunc正在返回一个指
我一直在学习将非托管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中有效?
>我应该关注NewStr()在本机DLL中的分配吗?
>如果对#2回答“否”,那么第二个代码示例是否有效?

解决方法

在文档中很难找到的关键信息涉及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堆损坏引发错误.

我的观点是,没有必要理解为什么破碎的代码似乎有效.代码坏了.修复它,继续前进.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读