2

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?

ispiro
  • 26,556
  • 38
  • 136
  • 291
  • You need to reserve the max size of memory even if you use less. There is a property in c# that is the allocate size of the structure and you will get an exception when code is run if you exceed the size. You will not get a compiler error if you do not add the line. – jdweng Jun 03 '20 at 17:02
  • @jdweng See my edit (-the last paragraph in the question) - it seems like setting a high size will work fine. But I don't know if it will always work fine. – ispiro Jun 03 '20 at 17:20
  • Depends if a char is one or two bytes. If two bytes, than you need 511 instead of 255. It looks like is is a c# two byte char so you need 511. See pinvoke : http://www.pinvoke.net/default.aspx/Structures/DEV_BROADCAST_DEVICEINTERFACE.html – jdweng Jun 03 '20 at 17:41
  • @jdweng See my other question [here](https://stackoverflow.com/q/62176770/939213). `dbcc_name` is not a char, nor a pointer. It's just the first character of a string. – ispiro Jun 03 '20 at 17:49
  • It is a window structure and has to meet the windows requirement. – jdweng Jun 03 '20 at 18:14

1 Answers1

1

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

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Thanks. I'll look into that. (I updated my question just as you were posting your answer. See the last paragraph in my question - it seems like I can simply set a high number and it works fine. But I don't know if it'll always work fine.) – ispiro Jun 03 '20 at 17:19
  • You seem to be considering `dbcc_name` a pointer. From the documentation (linked in my question) and from [answers to my other question here](https://stackoverflow.com/q/62176770/939213) it seems like it's not. – ispiro Jun 03 '20 at 17:25
  • @ispiro - that's not what I'm considering. If you have a pointer on the _DEV_BROADCAST_DEVICEINTERFACE_A structure, then you can compute a pointer on dbcc_name (=> namePtr) – Simon Mourier Jun 03 '20 at 17:41
  • So we're calculating the pointer to the beginning of the string and then reading all of it (until a null character). Now I understand. Thank you for your perseverance. I assume we can also use the other overload [PtrToStringAnsi(IntPtr, Int32)](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.ptrtostringansi?view=netcore-3.1#System_Runtime_InteropServices_Marshal_PtrToStringAnsi_System_IntPtr_System_Int32_) and use `dbcc_size` to calculate how much to read. Thanks again. – ispiro Jun 03 '20 at 17:53
  • I see that your code is _supposed_ to work. But trying it now, I'm only getting the first character of `dbcc_name`. I think I'll try the other overload... – ispiro Jun 03 '20 at 18:11
  • Nope. It looks like when I don't tell it the length of dbcc_name it only gets the first character. I don't know why. I did, however, try byte instead of char since char in c# is 2 bytes. – ispiro Jun 03 '20 at 18:42
  • After much testing I found that I needed to use `PtrToStringAuto` instead of `PtrToStringAnsi`. Is there a reason that you used `PtrToStringAnsi`? (i.e. Is my code expected to break if I don't use `PtrToStringAnsi`?) – ispiro Jun 03 '20 at 19:31
  • I used ansi because you where talking about _DEV_BROADCAST_DEVICEINTERFACE_A (ansi) but it's possible that in fact, you're receiving a _DEV_BROADCAST_DEVICEINTERFACE_W (unicode), it depends on your code. Both structs differ only by dbcc_name. If it works with Auto, it means, it's the unicode version, so you can use PtrToStringUni instead. Also in the case of PtrToStringUni (IntPtr ptr, int len), len is the number of chars, not bytes, so you must divide by 2: len = (dbcc_size - offset of dbcc_name) / 2 – Simon Mourier Jun 03 '20 at 20:44
  • Thank you very much. Though this answer doesn't answer the literal question, as there is no other answer, I'm accepting it. Also, [in your comment above](https://stackoverflow.com/questions/62178285/what-happens-if-a-string-in-a-struct-is-longer-or-shorter-than-the-p-invoked-sig/62178762?noredirect=1#comment109969874_62178762) you seem to have answered it by saying it's `a good recipe for random crashes`. Thanks again. – ispiro Jun 04 '20 at 15:02
  • I found something strange. That I need to have `(dbcc_size - offset of dbcc_name) / 2` and then `-2`. I expected to only need `-1`. Hmmmmm.... – ispiro Jun 04 '20 at 16:30
  • sizeof struct should be 32, offset of dbcc_name should be 28. dbcc_size should be 28+. `dbcc_size - offset of dbcc_name` shouldn't be less than 0. It could be zero (meaning the name is null/nothing). Don't know how to test this. – Simon Mourier Jun 04 '20 at 17:25
  • I figured it out. I think :). If the length of the string is 3 characters, it will take 6 bytes. Which will mean that the total length of the struct should be 34 bytes. But it gets padded to 36 which is the next multiple of 4. And the padding is two nulls. And since the name ends with a null (which is a double null because it's unicode) - that's why I see 2 null characters at the end of the string if I don't do `-2`. – ispiro Jun 04 '20 at 18:37