15 February, 2010

Выравнивание в CLR и в C/C++

Столкнулся вот с проблемой: нужно было установить однозначное соответствие структуры в C/C++ и в CLR, структурка небольшая вот такого вида:

#pragma pack(push, PPP)
struct XXX
{
    LPVOID m_AAA;
    ULONG m_BBB;
    ULONG m_CCC;
    ULONG m_DDD;
};
#pragma pack(pop)

Где PPP я установил в 4 для x86 и в 8 для x64. Другими словами каждое поле начинается со смещения кратного размеру указателя. С другой стороны, в CLR, я определил структуру примерно таким образом:

static mdToken const g_NilImplements[] = {mdTokenNil};
mdTypeDef stk;
 
if (hr = mde->DefineTypeDef(L"XXX", tdNotPublic | tdSequentialLayout | tdClass | tdSealed | tdBeforeFieldInit | tdAnsiClass, valueTypeToken, const_cast<mdtoken *>(g_NilImplements), &stk), FAILED(hr))
    throw _hr_error("Can't emit inject structure", hr);
 
if (hr = mde->SetClassLayout(stk, PPP, NULL, 0), FAILED(hr))
    throw _hr_error("Can't emit inject structure", hr);
 
static BYTE const g_fblobPtr[] =
{
    IMAGE_CEE_CS_CALLCONV_FIELD,
    ELEMENT_TYPE_PTR, ELEMENT_TYPE_VOID,
};
 
mdFieldToken ftkAAA;
 
if (hr = mde->DefineField(stk, L"AAA", fdPublic, g_fblobPtr, sizeof(g_fblobPtr) / sizeof(*g_fblobPtr), ELEMENT_TYPE_END, NULL, 0, &ftkAAA), FAILED(hr))
    throw _hr_error("Can't emit field structure", hr);
 
static BYTE const g_fblobUInt32[] =
{
    IMAGE_CEE_CS_CALLCONV_FIELD,
    ELEMENT_TYPE_U4,
};
 
mdFieldToken ftkBBB;
 
if (hr = mde->DefineField(stk, L"BBB", fdPublic, g_fblobUInt32, sizeof(g_fblobUInt32) / sizeof(*g_fblobUInt32), ELEMENT_TYPE_END, NULL, 0, &ftkBBB), FAILED(hr))
    throw _hr_error("Can't emit field structure", hr);
 
mdFieldToken ftkCCC;
 
if (hr = mde->DefineField(stk, L"CCC", fdPublic, g_fblobUInt32, sizeof(g_fblobUInt32) / sizeof(*g_fblobUInt32), ELEMENT_TYPE_END, NULL, 0, &ftkCCC), FAILED(hr))
    throw _hr_error("Can't emit field structure", hr);
 
mdFieldToken ftkDDD;
 
if (hr = mde->DefineField(stk, L"DDD", fdPublic, g_fblobUInt32, sizeof(g_fblobUInt32) / sizeof(*g_fblobUInt32), ELEMENT_TYPE_END, NULL, 0, &ftkDDD), FAILED(hr))
    throw _hr_error("Can't emit field structure", hr);

Мир казался прекрасным под x86, но стоило запуститься под x64 структуры кардинально разъехались. После внимательного изучения документации выяснилось, что в CLR в отличии от C/C++ для выравнивания выбирается меньшее из двух чисел: размер самого поля и установленного через SetClassLayout выравнивания. Для устранения проблемы пришлось установить PPP в 1 для всех платформ. Хотя в принципе есть и другие решения, но я выбрал самое простое.

No comments: