Search code examples
c#c++c.netpinvoke

What happens if a string in a struct is longer or shorter than the p/Invoked signature used?


Case in point, I want to use DEV_BROADCAST_DEVICEINTERFACE_A from C#. However, I'm not sure how to declare the struct since the size of dbcc_name is dependent on dbcc_size (It's officially declared as char dbcc_name[1]).

According to this question it seems like I need to add

[MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)]

over dbcc_name.

But why use SizeConst=255? We don't know the size. (And it seems from other answers I've seen, that there is no simple way to declare it such that it will know the correct size or a way to specify the size case by case.)

So what happens if I set a static length as in the linked answer. What would happen if the string is shorter or longer?

Testing has shown that if it's longer I get the correct string, and if shorter - I get a truncated string (e.g. if I set SizeConst to 2 and the real string is "abc", I get "ab".) But can I be sure that that's how it works, or is it dependent on something that just happens to be fine in this specific case?


Solution

  • You can't declare completely the structure, what you can do is something like this:

    [StructLayout(LayoutKind.Sequential)]
    private struct _DEV_BROADCAST_DEVICEINTERFACE_A
    {
        public int dbcc_size;
        public uint dbcc_devicetype;
        public uint dbcc_reserved;
        public Guid dbcc_classguid;
        public char dbcc_name; // just for offset; don't use!
    }
    

    And use it like this:

    // get ptr to structure from somewhere (lParam from WM_DEVICECHANGE ...)
    IntPtr ptr = ...
    
    // read structure
    var iface = Marshal.PtrToStructure<_DEV_BROADCAST_DEVICEINTERFACE_A>(ptr);
    
    // get name pointer
    var namePtr = ptr + Marshal.OffsetOf<_DEV_BROADCAST_DEVICEINTERFACE_A>(nameof(_DEV_BROADCAST_DEVICEINTERFACE_A.dbcc_name)).ToInt32();
    
    // get name
    var name = Marshal.PtrToStringAnsi(namePtr);
    

    Note if the name can contain zeros, you should instead use Marshal.PtrToStringAnsi(namePtr, len) with len = dbcc_size - offset of dbcc_name