.NET 4.0中的P/Invoke环境是否发生了变化?

13
我开始将一个 .NET 2.0 WinForms 应用程序升级到.NET 4.0。好吧,升级过程只是切换平台目标,但使它实际工作是另一回事。我假设这就是所有要做的事情。
但似乎在 .NET 4.0 中有一些关于交互的重大变化。使用 DllImport(),应用程序嵌入了几个 Delphi dll。当应用程序目标为 .NET 2.0 时,一切都正常工作。但是当我将其更改为目标为 .NET 4.0 时,一切都开始变得混乱,就像某些东西正在破坏内存一样。
例如,在奇怪的地方用“0”替换单个数字。传递给 IStream 的数据将有 8 个字符替换为(十六进制)00 00 00 00 00 00 00 80,但仅约 70% 的时间。连续两次调用以检索相同的值会返回不同的结果(从内存高速缓存中检索值,第一次成功,第二次失败)。发送到日志的字符串显示被截断。
我尝试了很多东西尝试使调用约定更明确,但没有任何效果。在 .NET 端,所有字符串都处理为 [MarshalAs(UnmanagedType.LPWStr)],Delphi 端则为 PWChar。
.NET 4.0 中改变了什么,会导致这样破坏 P/Invoke 呢?
[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中的boolean和C#中的bool,我将它们更改为更加明确,以防万一。

----------------------------编辑-------------------------------------

我之前写的关于WinForms的内容似乎并不完全相关,我已经重新创建了一个没有任何UI的问题。在2.0/3.5下,以下程序生成0,1,2,3,4,5,6,7,8,9,但在4.0下生成0,-1,-1,-1,-1,-1,-1,-1,-1。

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.

你能展示一下PInvoke签名吗? - JaredPar
这篇帖子http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/922fa431-5426-48c6-8949-538dfbb2f266表明在4.0中,尽管没有提供完整的更改清单,但是马歇尔确实发生了变化。 - Bryce Wagner
或许有趣的是:Visual Studio 2010 SP1并没有解决这个问题,我们在这里刚刚尝试过。 - Wim Coenen
4个回答

6
似乎是Visual Studio 2010调试器中的一个bug。它似乎破坏了不属于它的内存。我观察到的所有问题(全部可以可靠地重现)都会完全消失,如果我直接运行应用程序,而不是通过Visual Studio 2010运行。
实际上,这个bug在Managed Debug Assistant中。如果你完全关闭它(设置HKLM\Software\Microsoft.NETFramework\MDA =“0”),问题就会消失。但当然,这样做会失去一些调试能力。

3

你所描述的问题是不同的。当我使用Cdecl而不是StdCall测试上面的示例代码时,会出现堆栈不平衡异常。但是如果两边都声明为StdCall,则不会使堆栈不平衡,只会破坏Delphi内部。在你的情况下,MDA有助于追踪合法问题,在Delphi的情况下,MDA实际上会导致问题。 - Bryce Wagner
但是如果有人只看我的原始问题的标题,你在这里的回答是非常相关的,P/Invoke确实在4.0中发生了变化。只是它与我的问题无关。 - Bryce Wagner

0

我发现在 Delphi dll 中存在类似的问题: social_msdn
我注意到,使用 FreePascal 编译的库甚至在 VS2010 中也能正常使用,没有任何问题。因此,我不知道是 Delphi、.NET4 调试器还是它们的组合导致了这些麻烦。

有证据表明,在 dll 启动期间分配的内存(例如初始化部分)受到了内存损坏的影响。


我曾经看到过一些与启动明显无关的问题...除非它以某种方式破坏了Delphi内存管理器,使其为不同的事物重新分配相同的内存。它似乎表现出确定性但不可预测的方式。 - Bryce Wagner

0

布尔类型在 Delphi 中是一字节类型。因此,更改它们必须使用一字节类型。


我不确定每一边假设的大小是多少,所以我把它改成了WordBool和short(都是16位)在它出现的地方。但是这没有任何效果。 - Bryce Wagner

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