.NET 4.0 中的 P/Invoke 环境是否发生了变化?
我已开始将 .NET 2.0 WinForms 应用程序升级到 .NET 4.0。好吧,升级过程只是切换平台目标的问题,但要使其真正发挥作用。我以为这就是全部了。
但 .NET 4.0 中的互操作性似乎发生了巨大变化。使用 DllImport(),应用程序嵌入了几个 Delphi dll。当应用程序面向.NET 2.0时,一切正常。但当我将其更改为目标 .NET 4.0 时,事情开始变得混乱,就像有什么东西正在破坏内存一样。
例如,它在奇怪的地方用“0”替换单个数字。 IStream 中传递的数据会用 (十六进制) 00 00 00 00 00 00 00 80 替换 8 个字符,但只有大约 70% 的情况如此。两次连续调用检索相同的值会返回不同的结果(从内存中的缓存中检索值,第一次成功,第二次失败)。发送到日志的字符串显示被截断。
我尝试了很多东西试图使调用约定更加明确,但没有任何效果。所有字符串在 .NET 端都被处理为 [MarshalAs(UnmanagedType.LPWStr)],而在 Delphi 端则被处理为 PWChar。
.NET 4.0 中的哪些变化会像这样破坏 P/Invoke?
- - - - - - - - - - - - - - 编辑 - - - - - - - - - - - ----------------
这是最简单的例子。它生成的 PDF 有时可以正常工作,但更频繁地最终会损坏(并且在 .NET 2.0 中可以正常工作):
[DllImport(DLLName)]
public static extern void SetDBParameters(
[MarshalAs(UnmanagedType.LPWStr)] string Server,
[MarshalAs(UnmanagedType.LPWStr)] string Database,
[MarshalAs(UnmanagedType.LPWStr)] string User,
[MarshalAs(UnmanagedType.LPWStr)] string Password,
short IntegratedSecurity);
procedure SetDBParameters(Server, Database, User, Password: PWChar;
IntegratedSecurity: WordBool); stdcall;
[DllImport(DLLName)]
public static extern short GeneratePDF(
[MarshalAs(UnmanagedType.LPWStr)] string Param1,
[MarshalAs(UnmanagedType.LPWStr)] string Param2,
[MarshalAs(UnmanagedType.LPWStr)] string Param3,
[MarshalAs(UnmanagedType.LPWStr)] string Param4,
out IStream PDFData);
function GeneratePDF(Param1, Param2, Param3, Param4: PWChar;
out PDFData: IStream): WordBool; stdcall;
private byte[] ReadIStream(IStream Stream)
{
if (Stream == null)
return null;
System.Runtime.InteropServices.ComTypes.STATSTG streamstats;
Stream.Stat(out streamstats, 0);
Stream.Seek(0, 0, IntPtr.Zero);
if (streamstats.cbSize <= 0)
return null;
byte[] result = new byte[streamstats.cbSize];
Stream.Read(result, (int)streamstats.cbSize, IntPtr.Zero);
return result;
}
WordBool 和 Short 最初是布尔值(Delphi)和布尔值(C#),我将它们更改为更明确,只是在案件。
- - - - - - - - - - - - - - 编辑 - - - - - - - - - - - ----------------
我之前写的有关 WinForms 的内容似乎并不完全相关,我在没有任何 UI 的情况下重新创建了其中一个问题。下面的程序在2.0/3.5下生成0,1,2,3,4,5,6,7,8,9,但是0,-1,-1,-1,-1,-1,-1,- 1,-1 低于 4.0。
using System;
using System.Runtime.InteropServices;
namespace TestNet4interop
{
static class Program
{
[DllImport("TestSimpleLibrary.dll", PreserveSig=true, CallingConvention = CallingConvention.StdCall)]
public static extern void AddToList(long value);
[DllImport("TestSimpleLibrary.dll", PreserveSig=true, CallingConvention = CallingConvention.StdCall)]
public static extern int GetFromList(long value);
static void Main()
{
for (long i = 0; i < 10; i++)
{
AddToList(i);
Console.WriteLine(GetFromList(i));
}
}
}
}
以及Delphi端(用Delphi 2007编译):
library TestSimpleLibrary;
uses
SysUtils,
Classes;
{$R *.res}
var
List: TStringList;
procedure AddToList(value: int64); stdcall;
begin
List.Add(IntToStr(value));
end;
function GetFromList(value: int64): integer; stdcall;
begin
result := List.IndexOf(IntToStr(value));
end;
exports
AddToList,
GetFromList;
begin
List := TStringList.Create;
end.
I've started upgrading a .NET 2.0 WinForms application to .NET 4.0. Well, OK, the upgrade process was just a matter of switching platform target, but making it actually work. I assumed that's all there would be to it.
But it seems that something drastically changed in .NET 4.0 regarding interop. Using DllImport(), the application embeds a couple Delphi dlls. When the application targets .NET 2.0, everything works normally. But when I changed it to target .NET 4.0, stuff starts going haywire, like something is corrupting memory.
For example, it replaces single digits with "0" in strange places. Data passed in an IStream gets 8 characters replaced with (Hex) 00 00 00 00 00 00 00 80, but only about 70% of the time. Two consecutive calls to retrieve the same value return different results (retrieving a value from a cache in memory, succeeds the first time, fails the second time). Strings being sent to a log are showing up truncated.
I've tried lots of stuff trying to make calling conventions more explicit, none of it has any effect. All strings are handled as [MarshalAs(UnmanagedType.LPWStr)] on the .NET side and PWChar on the Delphi side.
What changed in .NET 4.0 that would break P/Invoke like this?
----------------------------Edit-------------------------------------
Here's the simplest example. It generates a PDF which sometimes works correctly, but more frequently ends up corrupt (and works correctly in .NET 2.0):
[DllImport(DLLName)]
public static extern void SetDBParameters(
[MarshalAs(UnmanagedType.LPWStr)] string Server,
[MarshalAs(UnmanagedType.LPWStr)] string Database,
[MarshalAs(UnmanagedType.LPWStr)] string User,
[MarshalAs(UnmanagedType.LPWStr)] string Password,
short IntegratedSecurity);
procedure SetDBParameters(Server, Database, User, Password: PWChar;
IntegratedSecurity: WordBool); stdcall;
[DllImport(DLLName)]
public static extern short GeneratePDF(
[MarshalAs(UnmanagedType.LPWStr)] string Param1,
[MarshalAs(UnmanagedType.LPWStr)] string Param2,
[MarshalAs(UnmanagedType.LPWStr)] string Param3,
[MarshalAs(UnmanagedType.LPWStr)] string Param4,
out IStream PDFData);
function GeneratePDF(Param1, Param2, Param3, Param4: PWChar;
out PDFData: IStream): WordBool; stdcall;
private byte[] ReadIStream(IStream Stream)
{
if (Stream == null)
return null;
System.Runtime.InteropServices.ComTypes.STATSTG streamstats;
Stream.Stat(out streamstats, 0);
Stream.Seek(0, 0, IntPtr.Zero);
if (streamstats.cbSize <= 0)
return null;
byte[] result = new byte[streamstats.cbSize];
Stream.Read(result, (int)streamstats.cbSize, IntPtr.Zero);
return result;
}
WordBool and short were originally boolean (Delphi) and bool (C#), I changed them to be more explicit, just in case.
----------------------------Edit-------------------------------------
The stuff I wrote earlier about WinForms appears to have turned out to be not completely relevant, I've recreated one of the issues without any UI. The following program generates 0,1,2,3,4,5,6,7,8,9 under 2.0/3.5, but 0,-1,-1,-1,-1,-1,-1,-1,-1 under 4.0.
using System;
using System.Runtime.InteropServices;
namespace TestNet4interop
{
static class Program
{
[DllImport("TestSimpleLibrary.dll", PreserveSig=true, CallingConvention = CallingConvention.StdCall)]
public static extern void AddToList(long value);
[DllImport("TestSimpleLibrary.dll", PreserveSig=true, CallingConvention = CallingConvention.StdCall)]
public static extern int GetFromList(long value);
static void Main()
{
for (long i = 0; i < 10; i++)
{
AddToList(i);
Console.WriteLine(GetFromList(i));
}
}
}
}
And the Delphi side (compiled with Delphi 2007):
library TestSimpleLibrary;
uses
SysUtils,
Classes;
{$R *.res}
var
List: TStringList;
procedure AddToList(value: int64); stdcall;
begin
List.Add(IntToStr(value));
end;
function GetFromList(value: int64): integer; stdcall;
begin
result := List.IndexOf(IntToStr(value));
end;
exports
AddToList,
GetFromList;
begin
List := TStringList.Create;
end.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这似乎是 Visual Studio 2010 调试器中的一个错误。它似乎正在破坏不属于它的记忆。如果我直接运行该应用程序,而不是通过 Visual Studio 2010,我观察到的所有问题(所有这些问题都可以可靠地重现)都会完全消失。
该错误实际上位于托管调试助手中。如果您完全关闭它(设置 HKLM\Software\Microsoft.NETFramework\MDA =“0”),问题就会消失。但这样做当然会失去一些调试能力。
It appears to be a bug in the Visual Studio 2010 debugger. It seems to be clobbering memory that doesn't belong to it. All of the problems I've observed (all of which can be reproduced reliably) disappear completely if I run the application directly, instead of through Visual Studio 2010.
The bug is actually in the Managed Debug Assistant. If you turn it off completely (set HKLM\Software\Microsoft.NETFramework\MDA = "0"), the problem goes away. But of course you lose some debugging capability by doing so.
看来这是 DllImport 属性中的调用约定属性的问题。应该是 Cdecl 而不是默认的 StdCall。我从2.0迁移到4.0并在VS2010中运行时遇到了这个问题。请参阅此处的文章。 http://codenition.blogspot.com/2010/05 /pinvokestackimbalance-in-net-40i-beg.html
Appears that this is a problem with the Calling Convention property in the DllImport attribute. Should be Cdecl not the default StdCall. I had this problem when migrating from 2.0 to 4.0 and running in VS2010. See article here. http://codenition.blogspot.com/2010/05/pinvokestackimbalance-in-net-40i-beg.html
Boolean 是 Delphi 上的一种单字节类型。因此更改它们必须使用单字节类型
Boolean is a one byte type on Delphi. So changing them must be with a one byte type
我在 Delphi dll 中看到类似的问题:
social_msdn
我注意到我用 FreePascal(而不是 Delphi)编译的库甚至在 VS2010 中也能正常工作,没有任何问题。因此我不知道 Delphi、
.NET4
调试器或其组合是否是造成问题的原因。有一些证据表明 dll 启动期间分配的内存(例如在初始化部分)受到内存损坏的影响。
I see a similar problem with a Delphi dll:
social_msdn
I have noticed that my library compiled with FreePascal (instead of Delphi) works even within VS2010 without any problems. Therefore I don't know if Delphi, the
.NET4
debugger or the combination is the reason for the trouble.There is some evidence that memory allocated during dll start-up (e.g. in the initialization section) is affected by the memory corruption.