Маршалинговая структура со встроенным указателем из C# в неуправляемый драйвер

Я пытаюсь связать С# (.NET Compact Framework 3.5) с потоковым драйвером Windows CE 6 R2, используя вызовы P/Invoked DeviceIoControl(). Для одного из кодов IOCTL драйверу требуется входной буфер DeviceIoControl, представляющий собой следующую неуправляемую структуру, содержащую встроенный указатель:

typedef struct {
    DWORD address;
    const void* pBuffer;
    DWORD size; // buffer size
} IOCTL_TWL_WRITEREGS_IN;

Я определил структуру в С# как:

[StructLayout(LayoutKind.Sequential)]
public struct IoctlWriteRegsIn
{
    public uint Address;
    public byte[] Buffer;
    public uint Size;
}

и моя подпись P/Invoke как:

[DllImport("coredll.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool DeviceIoControl(IntPtr hDevice,
                                    UInt32 dwIoControlCode,
                                    ref IoctlWriteRegsIn lpInBuffer,
                                    UInt32 nInBufferSize,
                                    UInt32[] lpOutBuffer,
                                    UInt32 nOutBufferSize,
                                    ref UInt32 lpBytesReturned,
                                    IntPtr lpOverlapped);

Однако всякий раз, когда я вызываю DeviceIoControl() в C#, он всегда возвращает false с последней ошибкой Win32 ERROR_INVALID_PARAMETER. Вот фрагмент исходного кода оператора IOCTL switch в драйвере, который обрабатывает код IOCTL и выполняет проверку ошибок во входном буфере, где inSize — это параметр nInBufferSize:

    case IOCTL_TWL_WRITEREGS:
        if ((pInBuffer == NULL) || 
            (inSize < sizeof(IOCTL_TWL_WRITEREGS_IN)))
            {
            SetLastError(ERROR_INVALID_PARAMETER);
            break;
            }
        address = ((IOCTL_TWL_WRITEREGS_IN*)pInBuffer)->address;
        pBuffer = ((IOCTL_TWL_WRITEREGS_IN*)pInBuffer)->pBuffer;
        size = ((IOCTL_TWL_WRITEREGS_IN*)pInBuffer)->size;
        if (inSize < (sizeof(IOCTL_TWL_WRITEREGS_IN) + size))
            {
            SetLastError(ERROR_INVALID_PARAMETER);
            break;
            }
        rc = TWL_WriteRegs(context, address, pBuffer, size);

Я попробовал жесткие размеры кодирования, которые должны пройти проверку ошибок драйвера, но безуспешно, предполагая, что это проблема сортировки. Вероятно, я неправильно определил встроенный указатель в структуре C# или ошибся в подписи P/Invoke. Любые идеи?

Заранее спасибо, Бен

Для справки, я могу без проблем общаться с драйвером из C++:

IOCTL_TWL_WRITEREGS_IN reg;
reg.address = 0x004B0014;
unsigned char data = 0xBE;
reg.pBuffer = &data;
reg.size = sizeof(char);

BOOL writeSuccess = DeviceIoControl(driver, IOCTL_TWL_WRITEREGS, &reg, sizeof(IOCTL_TWL_WRITEREGS_IN) + 1, NULL, 0, NULL, NULL);

Обновление: вот что сработало! Использовал предложение JaredPar IntPtr и очистил мою подпись P/Invoke по предложению SwDevMan81:

    [StructLayout(LayoutKind.Sequential)]
    public struct IoctlWriteRegsIn
    {
        public uint Address;
        public IntPtr Buffer;
        public uint Size;
    }

    // elided

    byte regData = 0xFF;
    GCHandle pin = GCHandle.Alloc(regData, GCHandleType.Pinned);
    IoctlWriteRegsIn writeInBuffer = new IoctlWriteRegsIn{Address = twlBackupRegA, Buffer = pin.AddrOfPinnedObject(), Size = 1};
    bool writeSuccess = DeviceIoControl(driverHandle, IoctlTwlWriteRegs, ref writeInBuffer, (uint) Marshal.SizeOf(writeInBuffer) + 1, IntPtr.Zero, 0, ref numBytesReturned, IntPtr.Zero);

    // P/Invoke signature
    [DllImport("coredll.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool DeviceIoControl(IntPtr hDevice,
                                        UInt32 dwIoControlCode,
                                        ref IoctlWriteRegsIn lpInBuffer,
                                        UInt32 nInBufferSize,
                                        IntPtr lpOutBuffer,
                                        UInt32 nOutBufferSize,
                                        ref UInt32 lpBytesReturned,
                                        IntPtr lpOverlapped);

person Ben Schoepke    schedule 05.11.2009    source источник
comment
Вы помещаете IntPtr в случайное значение в памяти. Вам нужно фактически выделить указатель на реальную память   -  person JaredPar    schedule 05.11.2009
comment
Упс, хороший улов. Теперь это работает! Большое спасибо, я очень ценю вашу помощь.   -  person Ben Schoepke    schedule 05.11.2009
comment
не могли бы вы объяснить мне эту строку GCHandle pin = GCHandle.Alloc(regData, GCHandleType.Pinned); что именно она делает?   -  person FosterZ    schedule 25.03.2011


Ответы (3)


При маршалинге структуры со встроенным указателем вам необходимо определить значение как IntPtr, а не массив.

[StructLayout(LayoutKind.Sequential)]
public struct IoctlWriteRegsIn
{
    public uint Address;
    public IntPtr Buffer;
    public uint Size;
}
person JaredPar    schedule 05.11.2009
comment
Пробовал это безуспешно. Я определил структуру, как вы предложили, а затем сделал следующие вызовы: IoctlWriteRegsIn writeInBuffer = new IoctlWriteRegsIn { Address = twlBackupRegA, Data = new IntPtr(0xFF), Size = 1 }; bool writeSuccess = DeviceIoControlWrite(driverHandle, IoctlTwlWriteRegs, ref writeInBuffer,(uint)Marshal.SizeOf(IoctlTwlWriteRegs)+1, new uint[0], 0, ref numBytesReturned, IntPtr.Zero); Размер и (uint)Marshal.SizeOf(IoctlTwlWriteRegs)+1 материал соответствует моему рабочему коду C++, поэтому я не думаю, что это причина. - person Ben Schoepke; 05.11.2009
comment
@ Бен, можешь опубликовать это в своем вопросе? Очень сложно следить за кометами - person JaredPar; 05.11.2009
comment
Я обновил вопрос, не осознавая, что комментарии не будут форматированы, извините за это! - person Ben Schoepke; 05.11.2009
comment
не могли бы вы объяснить мне эту строку GCHandle pin = GCHandle.Alloc(regData, GCHandleType.Pinned); что именно делает? - person FosterZ; 25.03.2011
comment
@FosterZ Управляемая память изменчива — объекты могут и будут перемещаться по мере необходимости в места, отличные от исходных. Закрепление объекта указывает сборщику мусора не перемещать объект. Это необходимо при маршалинге объектов, поскольку вы выделяете память в определенном месте, а затем отправляете указатель на это место в памяти собственному коду. Если вы не закрепили память, то к тому времени, когда нативный код ее просмотрит, это может быть что угодно, а не объект, который вы ожидаете. - person Josh; 31.10.2014

Попробуйте, заменив byte[] array на IntPtr..

person rama-jka toti    schedule 05.11.2009
comment
И убедитесь, что вы закрепили память, на которую ссылается IntPtr, и открепите ее, когда закончите. - person Peter Tate; 05.11.2009
comment
И покажите этому сборщику мусора, как это делается :) т.е. что вы будете нежными/нечастыми и быстрыми в отдаче/откреплении :) - person rama-jka toti; 05.11.2009

Возможно, вам придется указать размер byte[] (замените 64 фактическим размером)

[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct IoctlWriteRegsIn
{    
   public uint Address; 
   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] 
   public byte[] Buffer; 
   public uint Size;
}

Затем вы сможете установить размер следующим образом:

IoctlWriteRegsIn io_struct = new IoctlWriteRegsIn();
io_struct.Address = 5;
io_struct.Buffer = new byte[1] { 0xBE };
// size of buffer, not struct
io_struct.Size = 1;//Marshal.SizeOf(io_struct); 

Я бы изменил вызов P/Invoke на это:

[DllImport("coredll.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool DeviceIoControl(IntPtr hDevice,  
   UInt32 dwIoControlCode,
   ref IoctlWriteRegsIn lpInBuffer, 
   UInt32 nInBufferSize,
   IntPtr lpOutBuffer,
   UInt32 nOutBufferSize,
   ref UInt32 lpBytesReturned,
   IntPtr lpOverlapped);

и вызовите его, используя это:

uint num_bytes = (uint)Marshal.SizeOf(writeInBuffer);
bool writeSuccess = DeviceIoControl(driverHandle, IoctlTwlWriteRegs, ref writeInBuffer, num_bytes, IntPtr.Zero, 0, ref numBytesReturned, IntPtr.Zero);
person SwDevMan81    schedule 05.11.2009
comment
Я пробовал это, и это не сработало. Я могу обойтись, просто передав один байт - мне не нужен весь буфер, так как мне нужно записывать только один 8-битный регистр за раз - поэтому я попробовал это с SizeConst = 1 безуспешно. - person Ben Schoepke; 05.11.2009
comment
Та же ошибка? Можете ли вы изменить lpOutBuffer на IntPtr lpOutBuffer и повторить попытку? - person SwDevMan81; 05.11.2009
comment
Не могли бы вы также опубликовать свой звонок в DeviceIoControl - person SwDevMan81; 05.11.2009
comment
Я обновил вопрос вызовом DeviceIoControl. Размер lpInBuffer выглядит подозрительно, но он совпадает с тем, что передается в моем рабочем коде C++ (nInBufferSize = 13) и должен пройти проверку ввода драйвера. - person Ben Schoepke; 05.11.2009
comment
Я попробую это через некоторое время, но учтите, что Pack не поддерживается в Compact Framework. - person Ben Schoepke; 05.11.2009
comment
Ах, хорошо, да, пропустил CF, потому что я смотрел на теги, это, вероятно, не сработает (stackoverflow.com/questions/1268898/). Вам нужно изменить его на этот IntPtr, как было предложено выше. - person SwDevMan81; 05.11.2009