Recently I faced an interesting issue with unmanaged structure definitions that contains unions. Unions allow one portion of memory to be accessed as different data types. Here is a CERT_ID structure definition in C++ that uses an anonymous union (exact my issue):

typedef struct _CERT_ID {
  DWORD dwIdChoice;
  union {
    CERT_ISSUER_SERIAL_NUMBER IssuerSerialNumber;
    CRYPT_HASH_BLOB           KeyId;
    CRYPT_HASH_BLOB           HashId;
  };
} CERT_ID, *PCERT_ID;

actually, only two fields are used at the time: dwIdChoice field that indicates the field in the union to use. Ok, let’s create a C# translation of underlying types: CERT_ISSUER_SERIAL_NUMBER and CRYPT_HASH_BLOB:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CRYPTOAPI_BLOB {
	public UInt32 cbData;
	public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CERT_ISSUER_SERIAL_NUMBER {
	public CRYPTOAPI_BLOB Issuer;
	public CRYPTOAPI_BLOB SerialNumber;
}

KeyId and HashId directly uses CYRPTOAPI_BLOB structure as a value and IssuerSerialNumber field is a structure that contains two CYRPTOAPI_BLOB structures. Normally, you will do the following to translate CERT_ID structure to C#:

  1. Change layout type from Sequential to Explicit;
  2. Add data offset attributes to each subsequent field:

In order to calculate field offset, you will calculate the size of the previous field by calling Marshal.SizeOf method. You quickly ensure that UInt32 consumes 4 bytes. Since, dwIdChoice field is the first field, it is aligned at the zero offset. The rest fields are placed at offset: 0 + 4 = 4:

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CERT_ID {
	[FieldOffset(0)]
	public UInt32 dwIdChoice;
	[FieldOffset(4)]
	public CERT_ISSUER_SERIAL_NUMBER IssuerSerialNumber
	[FieldOffset(4)]
	public CRYPTOAPI_BLOB KeyId;
	[FieldOffset(4)]
	public CRYPTOAPI_BLOB KeyId;
}

Similar solutions you will find over internet and it will work. Sometimes. But sometimes will not.

The problem is that if you attempt to use this structure signature in an x64 process (usually by calling Marshal.PtrToStructure static method), you will get an AccessViolationException. Guess why? Yes, it is because in x64 field offset must be 8, instead of 4 (in x64 processes, the type consumes more bytes in memory). And vice versa. If you set offset to 8 and run the code in the x86 process, you will receive the same exception. It is not a big deal for applications (as they are platform-specific), but is a real problem for class libraries which are platform-independent.

One option is to create two structures, say, CERT_ID_X86 and CERT_ID_X64 and then select an appropriate structure at runtime by determining the process architecture. It can be very painful, because it adds an unnecessary complexity especially when structure is used more than once.

Another option is to move union fields to a separate structure and set their offset to 0, because only union fields will be there and all of them share the same memory block:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CERT_ID {
	public UInt32 dwIdChoice;
	public CERT_ID_DATA pIdChoice;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CERT_ID_DATA {
	[FieldOffset(0)]
	public CERT_ISSUER_SERIAL_NUMBER IssuerSerialNumber;
	[FieldOffset(0)]
	public CRYPTOAPI_BLOB KeyId;
	[FieldOffset(0)]
	public CRYPTOAPI_BLOB HashId;
}

I introduced a new structure (say, CERT_ID_DATA) and placed all union fields there. All field offset is zero. And replace union with the new structure in the source structure (CERT_ID). Note that CERT_ID structure uses sequential layout and do not use field offsets.

By using this trick, you can easily solve cross-platform issue with unmanaged structures and unions.

HTH


Share this article:

Comments:


Post your comment:

Please, solve this little equation and enter result below. Captcha