Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine C# P/Invoke Structure Alignment at Runtime

I'm trying to write good P/Invoke signatures for some Windows setupapi calls, and I've encountered the following problem with the packing of setupapi's structures:

// Excerpt from setupapi.h
#if defined(_WIN64)
#include <pshpack8.h>   // Assume 8-byte (64-bit) packing throughout
#else
#include <pshpack1.h>   // Assume byte packing throughout (32-bit processor)
#endif

Now, what that means is that I can't just set the StructLayoutAttribute.Pack property to a constant value.

I tried doing the following:

[StructLayout(LayoutKind.Sequential, Pack = Environment.Is64BitProcess ? 8 : 1)]
public struct SP_DEVINFO_DATA
{
    public uint cbSize;
    public Guid ClassGuid;
    public uint DevInst;
    public IntPtr Reserved;
}

As expected, this fails with the following compilation error:

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

I'd really like to avoid #if and setting up different compilation platforms, as opposed to Any CPU. Can I determine the packing of a C# structure at run-time?

like image 477
DoomMuffins Avatar asked Sep 01 '26 02:09

DoomMuffins


1 Answers

No, packing is a compile-time concept, because it determines (among other things) the overall size of the type. That can't change at run-time.

In this case, if you're not willing to have separate builds for x86 and x64, you'll have to do the marshalling yourself. Thanks for @Hans Passant for a clean way to achieve this:

  • Define two structures, say SP_DEVINFO_DATA32 and SP_DEVINFO_DATA64,
  • Define a method overload for each structure
  • At run-time, select which structure to create and call the appropriate overload.

It would look something like this:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SP_DEVINFO_DATA32 { /* stuff */ }
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct SP_DEVINFO_DATA64 { /* stuff */ }

[DllImport("setupapi.dll")]
public static extern void Function(ref SP_DEVINFO_DATA32 data);

[DllImport("setupapi.dll")]
public static extern void Function(ref SP_DEVINFO_DATA64 data);

public void DoStuff()
{
    if (Environment.Is64BitProcess)
    {
        var data = new SP_DEVINFO_DATA64 
        { 
            cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA64)) 
        };
        Function(ref data);
    }
    else
    {
        var data = new SP_DEVINFO_DATA32 
        {
            cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA32)) 
        };
        Function(ref data);
    }
}
like image 150
Michael Edenfield Avatar answered Sep 03 '26 17:09

Michael Edenfield