对托管对象的非托管内存分配

时间:2011-10-10 20:21:43

标签: c# c#-4.0 unsafe

我想知道从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(),并且使用的方法是将固定的GCHandle实例化为在调用DeviceIoControl()时使用的数据结构,进行调用,复制出适当的数据然后释放句柄。这种方法似乎不易出错,并且与尽可能快地分配和释放非托管内存的模型保持一致。

正如我所提到的,我现在所使用的代码中使用的方法并没有“感觉”正确,但我不确定原因。我在谷歌上搜索了“在分配后更改了内存地址”之类的内容,结果一无所获。这种方法应该改变吗? (过去,我使用了固定的GC手柄。)上述方法听起来有效吗?无论如何,为什么指针会在程序启动期间说出一个内存地址,然后在实际执行ReadFromDevice()调用时又是另一个?当我写这篇文章时,我想知道地址的变化是否像我最初想的那样“奇怪”。尽管如此,我仍在质疑指针的使用。请指教。

谢谢, 安迪

2 个答案:

答案 0 :(得分:1)

这没有必要,你可以把它留给pinvoke marshaller来做正确的事情。只需提供函数的重载即可传递参数,并根据调用进行自定义。例如:

[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);

答案 1 :(得分:0)

是否有效?你是想尝试修复有用的东西吗?

我从不在c#中使用VirtualAlloc。如果需要内存缓冲区,为什么不直接在c#中分配它。我认为你不需要VirtualAlloc。直接在c#中分配一个非托管固定数组,或者分配一个常规数组并通过编组将它传递给IOCTL函数。