Gigo
Gigo

Reputation: 3264

P/Invoke method with struct using union

I am building a managed wrapper in C# around the native Windows Biometric Framework, which is used to access biometric sensors like fingerprint sensors.

I have problems getting this method to work with P/Invoke: WinBioIdentify

HRESULT WINAPI WinBioIdentify(
    _In_      WINBIO_SESSION_HANDLE    SessionHandle,
    _Out_opt_ WINBIO_UNIT_ID           *UnitId,
    _Out_opt_ WINBIO_IDENTITY          *Identity,
    _Out_opt_ WINBIO_BIOMETRIC_SUBTYPE *SubFactor,
    _Out_opt_ WINBIO_REJECT_DETAIL     *RejectDetail
);

The problem is the WINBIO_IDENTITY struct because it contains a union:

typedef struct _WINBIO_IDENTITY {
    WINBIO_IDENTITY_TYPE Type;
    union {
        ULONG  Null;
        ULONG  Wildcard;
        GUID   TemplateGuid;
        struct {
            ULONG Size;
            UCHAR Data[SECURITY_MAX_SID_SIZE]; // the constant is 68
        } AccountSid;
    } Value;
} WINBIO_IDENTITY;

Here is what I tried:

[StructLayout(LayoutKind.Explicit, Size = 76)]
public struct WinBioIdentity
{
    [FieldOffset(0)]
    public WinBioIdentityType Type;

    [FieldOffset(4)]
    public int Null;

    [FieldOffset(4)]
    public int Wildcard;

    [FieldOffset(4)]
    public Guid TemplateGuid;

    [FieldOffset(4)]
    public int AccountSidSize;

    [FieldOffset(8)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 68)]
    public byte[] AccountSid;
}

[DllImport("winbio.dll", EntryPoint = "WinBioIdentify")]
private extern static WinBioErrorCode Identify(
    WinBioSessionHandle sessionHandle,
    out int unitId,
    out WinBioIdentity identity,
    out WinBioBiometricSubType subFactor,
    out WinBioRejectDetail rejectDetail);

public static int Identify(
    WinBioSessionHandle sessionHandle,
    out WinBioIdentity identity,
    out WinBioBiometricSubType subFactor,
    out WinBioRejectDetail rejectDetail)
{
    int unitId;
    var code = Identify(sessionHandle, out unitId, out identity, out subFactor, out rejectDetail);
    WinBioException.ThrowOnError(code, "WinBioIdentify failed");
    return unitId;
}

In this form it crashes with a TypeLoadException complaining that the WinBioIdentity struct contains a misaligned field at offset 8. If I leave out that last field it works, but then the most important data is missing, of course.

Any help how to handle this case is very much appreciated.

Upvotes: 1

Views: 332

Answers (1)

Hans Passant
Hans Passant

Reputation: 942089

The Guid in this structure is the trouble-maker. It is 16 bytes long and therefore overlaps the byte[]. The CLR disallows this kind of overlap, it prevents the garbage collector from identifying the array object reference reliably. Very important to know whether or not the array needs to be collected. The GC has no way to find out if the structure contains the Guid or the array.

You must give up on byte[] and substitute it with a value type so the GC does not have to deal with a possibly broken object reference. The C# language has the fixed keyword to declare such kind of value types:

[StructLayout(LayoutKind.Explicit)]
unsafe public struct WinBioIdentity {
    //...
    [FieldOffset(8)]
    public fixed byte AccountSid[68];
}

Note the required unsafe keyword. Project > Properties > Build > Allow unsafe code option. It is in fact pretty unsafe, you'll want to put an assert in your code before you start using the struct so you can be sure no memory corruption can occur:

System.Diagnostics.Debug.Assert(Marshal.SizeOf(typeof(WinBioIdentity)) == 76);

Upvotes: 3

Related Questions