3

I'm trying to interface C# (.NET Compact Framework 3.5) with a Windows CE 6 R2 stream driver using P/Invoked DeviceIoControl() calls . For one of the IOCTL codes, the driver requires a DeviceIoControl input buffer that is the following unmanaged struct that contains an embedded pointer:

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

I defined the struct in C# as:

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

and my P/Invoke signature as:

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

However, whenever I call DeviceIoControl() in C#, it always returns false, with a last Win32 error of ERROR_INVALID_PARAMETER. Here's a source code snippet from the IOCTL switch statement in the driver that handles the IOCTL code and does error checking on the input buffer, where inSize is the nInBufferSize parameter:

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

I tried hard coding sizes that should pass the driver's error checking with no success, suggesting that it's a marshalling problem. I probably did not define the embedded pointer in the C# struct correctly or have my P/Invoke signature wrong. Any ideas?

Thanks in advance, Ben

For reference, I can talk to the driver from C++ with no problems like this:

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

Update: here's what worked! Used JaredPar's IntPtr suggestion and cleaned up my P/Invoke signature by SwDevMan81's suggestion:

    [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);
2
  • You're putting the IntPtr at a random value in memory. You need to actually allocate the pointer to real memory Commented Nov 5, 2009 at 17:14
  • can you please explain me this line GCHandle pin = GCHandle.Alloc(regData, GCHandleType.Pinned); what exactly it is doing ? Commented Mar 25, 2011 at 11:47

3 Answers 3

2

When marshaling a struct which has an inline pointer, you need to define the value as an IntPtr and not an array

[StructLayout(LayoutKind.Sequential)]
public struct IoctlWriteRegsIn
{
    public uint Address;
    public IntPtr Buffer;
    public uint Size;
}
Sign up to request clarification or add additional context in comments.

5 Comments

Tried this without success. I defined the struct as you suggested and then made these calls: 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); The size and (uint)Marshal.SizeOf(IoctlTwlWriteRegs)+1 stuff matches my working C++ code so I don't think that's the cause.
@Ben, can you post this into your question? It's really hard to follow in the commets
I updated the question, didn't realize the comments wouldn't have formatting, sorry about that!
can you please explain me this line GCHandle pin = GCHandle.Alloc(regData, GCHandleType.Pinned); what exactly it is doing ?
@FosterZ Managed memory is fluid--objects can and will be moved as necessary to different locations than where they started. "Pinning" an object tells the garbage collector not to move the object. This is needed when marshaling objects, since you're allocating memory in a specific location and then sending a pointer to that memory location to native code. If you didn't pin the memory, by the time the native code looked at it, it could be anything instead of the object you expect.
1

Give it a shot by replacing the byte[] array with an IntPtr..

2 Comments

And make sure you pin the memory that the IntPtr references and unpin it when you're done.
And show that GC how it's done :) ie. that you'll be gentle/infrequent and quick in giving-back/unpinning it :)
0

You might have to specify the size of the byte[] (replace 64 with the actual size)

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

Then you should be able to set the size like this:

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

I would change the P/Invoke call to this:

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

and call it using this:

uint num_bytes = (uint)Marshal.SizeOf(writeInBuffer);
bool writeSuccess = DeviceIoControl(driverHandle, IoctlTwlWriteRegs, ref writeInBuffer, num_bytes, IntPtr.Zero, 0, ref numBytesReturned, IntPtr.Zero);

6 Comments

I tried this and it didn't work. I can get by by just passing a single byte--don't need a whole buffer since I only need to write one 8-bit register at a time--so I tried this with SizeConst = 1 with no luck.
Same error? Can you change the lpOutBuffer to IntPtr lpOutBuffer and try again
Could you also post your call to DeviceIoControl
I updated the question with the DeviceIoControl call. The size of lpInBuffer looks suspect but its the same as what gets passed in my working C++ code (nInBufferSize = 13) and should pass the driver's input validation.
I'll try that in a bit, but note that Pack isn't supported in the Compact Framework.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.