Wpf: Replace use of `Marshal.SizeOf` with `sizeof` for blittable structs

Created on 3 Jul 2020  路  5Comments  路  Source: dotnet/wpf

https://github.com/dotnet/pinvoke/pull/463#discussion_r448457195

Marshal.SizeOf returns the size of the unmanaged view of the struct. sizeof returns the size of the managed view of the struct.

Unmanaged and managed views are identical for the blittable structs. It is better to use sizeof instead of Marshal.SizeOf for blittable structs. They return the same number and sizeof is much cheaper.

There are many uses of Marshal.SizeOf in the codebase - many of them likely for blittable types - that could be identified and updated to use faster sizeof.

/cc @dotnet/dotnet-winforms - This probably applies to Winforms as well.

issue-type-enhancement

Most helpful comment

Note that WinForms has had regressions when switching from Marshal.SizeOf to sizeof which you should be aware of.

In particular bool is not blittable but the runtime allows it anways (by design, has been enabled specifically by a PR, can't find it right now though linked below). So when switching from Marshal.SizeOf to sizeof you must make sure no bool is part of the struct. WinForms has various helper structs for differently sized booleans in the Windows API.

code from linked comment detailing the difference

  • Marshal.SizeOf for classic marshalling size
  • Marshal.OffsetOf for classic marshalling offsets
  • sizeof keyword for blitting size
  • pointer arithmetic for blitting offsets

Example where it makes a difference:

public struct Sample
{
    public int a;
    public bool b;
    public short c;
}

var sample = new Sample();
var blittingSize = sizeof(Sample);                              // 8
var blittingOffset = (byte*)&sample.c - (byte*)&sample;         // 6
var marshalSize = Marshal.SizeOf<Sample>();                     // 12
var marshalOffset = Marshal.OffsetOf<Sample>(nameof(Sample.c)); // 8

All 5 comments

Thank you @vatsan-madhavan, we're aware of this, and we've been making these changes as we go through our codebase.

Note that WinForms has had regressions when switching from Marshal.SizeOf to sizeof which you should be aware of.

In particular bool is not blittable but the runtime allows it anways (by design, has been enabled specifically by a PR, can't find it right now though linked below). So when switching from Marshal.SizeOf to sizeof you must make sure no bool is part of the struct. WinForms has various helper structs for differently sized booleans in the Windows API.

code from linked comment detailing the difference

  • Marshal.SizeOf for classic marshalling size
  • Marshal.OffsetOf for classic marshalling offsets
  • sizeof keyword for blitting size
  • pointer arithmetic for blitting offsets

Example where it makes a difference:

public struct Sample
{
    public int a;
    public bool b;
    public short c;
}

var sample = new Sample();
var blittingSize = sizeof(Sample);                              // 8
var blittingOffset = (byte*)&sample.c - (byte*)&sample;         // 6
var marshalSize = Marshal.SizeOf<Sample>();                     // 12
var marshalOffset = Marshal.OffsetOf<Sample>(nameof(Sample.c)); // 8

Makes sense.

In practice correct use of bool would look like BOOL with a MarshalAs attribute anyway, which wouldn't have been a blittable struct in the first place.

BTW https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types calls out bool and char as non-blittable.

I believe they both work with sizeof (probably because they are in fact blittable in a strict sense) but in practice aren't blittable (because of BOOL/MarshalAs[UnmanagedType.Bool] and unicode/ansi auto-marshaling considerations respectively).

bool fields shouldn't appear in P/Invoke structs anyway - at least not without a MarshalAs attribute (or a comment indicating why the attribute is left out). Anything else is suspicious IMO. In general, structs with bool should indeed be treated presumptively as non-blittable, until it can be proven with further analysis that they are blittable.

Very similar arguments apply to structs with char fields as well.

Yes bool/char is non-blittable but works anyways, it was specifically enabled here: dotnet/runtime#13772 (issue) and dotnet/runtime#1866 (PR). The runtime uses different size/alignment for bool than the classic marshalling layer, its just something to keep in mind when porting code to this syntax, besides that its well-defined and supported. Using helper structs is the right way to keep things readable considering the Windows API knows at least three different bool types, each with different sizes/rules.

Was this page helpful?
0 / 5 - 0 ratings