未托管内存分配到托管对象

3

我想了解在C#中从内部分配内存给指针(C/C++风格)的正确方式,以及将该内存保留一段时间。此外,这个分配的内存是用于调用DeviceIoControl()的。考虑以下类:

class Example {
    const uint memCommit = 0x1000;
    const uint pgReadWrite = 0x04;

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, uint flAllocationType, uint flProtect);

    [StructLayout(LayoutKind.Sequential)]
    struct AStruct {
        uint val1;
        uint val2;
        uint val3;
    }

    private static unsafe AStruct* pStruct = (AStruct*)VirtualAlloc(IntPtr.Zero, (UIntPtr)(sizeof(AStruct)), memCommit, pgReadWrite).ToPointer();


    public static unsafe void ReadFromDevice() {
        // setup the structure for the IOCTL call
        pStruct->val1 = 70; //
        pStruct->val2 = 0;
        pStruct->val3 = 0x0f;

        // call P/Invoked DeviceIoControl() here with pStruct as both the in/out pointers

        // check that all is well
    }
}

整个事情让我感觉不对劲,但多年来我已经学到了不立即质疑实现方法的教训,需要先进行研究。这就是我来到这个论坛的原因。我看到了以前从未见过的行为。
使用调试器,在指针通过调用VirtualAlloc()实例化的点上设置断点。我记录下给我的地址(例如0x03bf78cc)。我还在另一个调用上设置了断点,该调用调用了上面的ReadFromDevice()方法。当我逐步执行ReadFromDevice()时,我注意到pStruct包含的地址与程序最初开始时分配的地址不同。pStruct的值已从上面的数字更改为,假设为0x03cd9004。
我之前曾经使用过调用Kernel32函数DeviceIoControl()的方法,并且在调用DeviceIoControl()时使用的方法是将数据结构实例化为固定的GCHandle,进行调用,复制出适当的数据,然后释放句柄。这种方法似乎更少出错,并保持了尽快分配和释放非托管内存的模式的一致性。
正如我所提到的,我现在正在使用的代码方法让我感觉不对劲,但我不确定原因。在Google上搜索" c sharp memory address changed after alloc "等内容也没有找到任何信息。应该更改这种方法吗?(过去,我使用固定的GC句柄。)上述方法是否可靠?无论如何,在程序启动时指针为什么会显示一个内存地址,而在实际执行ReadFromDevice()调用时会显示另一个内存地址?当我写这篇文章时,我在想,地址的更改是否像我最初想的那么“奇怪”。尽管如此,我仍然对这种指针使用方式提出质疑。请给予建议。
谢谢, Andy
2个回答

1

这并非必须,您可以将其留给PInvoke Marhaller自行处理。只需提供函数重载以传递参数,以便满足调用要求即可。例如:

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool DeviceIoControl(
    IntPtr hDevice, uint dwIoControlCode,
    IntPtr lpInBuffer, uint nInBufferSize,
    out AStruct lpOutBuffer, uint nOutBufferSize,
    out uint lpBytesReturned, IntPtr lpOverlapped);

然后你可以这样调用它:

AStruct result;
uint resultSize = Marshal.SizeOf(result);
uint bytesReturned;
bool okay = DeviceIoControl(hDev, somecode, IntPtr.Zero, 0, 
               out result, resultSize,
               out bytesReturned, IntPtr.Zero);
if (!okay) throw new Win32Exception();
System.Diagnostic.Debug.Assert(bytesReturned == resultSize);

这也是我习惯的做法。或者,正如我在帖子中提到的那样,通过使用固定对象来实现。基本上,我想知道使用的实现是否好,还是应该改为您发布的内容?我在想,使用这种方法是否存在危险;即像那样具有静态不安全指针? - Andrew Falanga

0

它工作还是不工作? 你是在修复一些正在工作的东西还是什么?

我在C#中从未需要过VirtualAlloc。如果您需要一个内存缓冲区,为什么不直接在C#中分配它。我认为您不需要VirtualAlloc。要么在C#中直接分配非托管固定数组,要么通过编组将其分配给IOCTL函数的常规数组。


它认为它可以工作,大部分时间似乎也是这样。我正在处理一个缺陷,我相信它直接与这种使用未管理的内存有关。 - Andrew Falanga

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