Windows 7 + .NET 4.0中“ Marshal.StructureToPtr”处的访问冲突异常(Windows XP + .NET 3.5正常运行)

Cof*_*fka 5 .net c# unmanaged marshalling

这是我的代码:

internal void Show()
{
    if (Parent == null)
        throw new NullReferenceException();
    EDITBALLOONTIP ebt = new EDITBALLOONTIP();
    ebt.cbStruct = Marshal.SizeOf(ebt);
    ebt.pszText = Text;
    ebt.pszTitle = Caption;
    ebt.ttiIcon = (int)Icon;
    IntPtr ptrStruct = Marshal.AllocHGlobal(Marshal.SizeOf(ebt));
    Marshal.StructureToPtr(ebt, ptrStruct, true); // Here we go.
    // Access violation exception in Windows 7 + .NET 4.0
    // In Windows XP + .NET 3.5, it works just fine.

    // ... Some other code ...

    Marshal.FreeHGlobal(ptrStruct);
}
Run Code Online (Sandbox Code Playgroud)

这是结构:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct EDITBALLOONTIP
{
    internal int cbStruct;
    internal string pszTitle;
    internal string pszText;
    internal int ttiIcon;
}
Run Code Online (Sandbox Code Playgroud)

为什么在Windows XP + .NET 3.5中可以正常工作,并在Windows 7 + .NET 4.0中引发异常?可能是CharSet问题吗?

====================已解决=======================

解决方案和解释

如您所见,Marshal.StructureToPtr(ebt,ptrStruct,true); 将第三个参数设置为true。这意味着系统将尝试为ptrStruct释放最后分配的内存。但是Show(),第一次调用方法时,没有为该结构分配内存(ptrStruct = IntPtr.Zero)。因此,系统将尝试释放位于零指针处的内存。当然,这将引发一个例外。Windows XP只是忽略了这一点,而Windows 7则没有。

这是最好的解决方案恕我直言:

   Marshal.StructureToPtr(ebt, ptrStruct, false);
   //Working...
   //Free resources
   Marshal.FreeHGlobal(ptrStruct);
Run Code Online (Sandbox Code Playgroud)

ken*_*n2k 2

我不想在这里添加答案,因为您已经解决了您的问题,我所说的不会为您遇到的问题提供任何答案,但它不适合作为评论,因为我提供了一些代码。所以我有义务将其发布在这里作为答案。

您可能已经知道它(并且没有那样写,因此您问题中的代码更简单),但我只是想说,分配非托管内存时应在各处使用的最佳实践是封装代码在 try/finally 块中以确保内存始终被释放,即使抛出异常:

private static void Test()
{
    IntPtr ptrStruct = IntPtr.Zero;

    try
    {
        Marshal.AllocHGlobal(0x100);

        // Some code here
        // At some point, an exception is thrown
        throw new IndexOutOfRangeException();
    }
    finally
    {
        // Even if the exception is thrown and catch
        // from the code that calls the Test() method,
        // the memory will be freed.
        if (ptrStruct != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(ptrStruct);
        }
    }
}

try
{
    Test();
}
catch (IndexOutOfRangeException)
{
    // Catches the exception, so the program won't crash
    // but it'll exit the Test() method, so the try/finally
    // ensures it won't leave without freeing the memory
    Debugger.Break();
}
Run Code Online (Sandbox Code Playgroud)