Все чаще перед разработчиками стала вставать задача вызова native методов из managed кода. В большинстве случаев — вызов сделать достаточно просто, но иногда встречаются неприятные случаи, такие как метод, который содержит структуру с динамическим массивом структур, содержащих динамический массив структур.
Немного о задаче
Есть dll, написанная на С++, с исходным кодом в которой содержаться следующие декларации:
#pragma pack (push, 4)
struct vpsMSR
{
unsigned __int64 data;
unsigned int address;
};
#pragma pack (pop)
struct vpsConfCounter
{
int address;
int number;
vpsMSR *config;
unsigned int configCount;
};
struct vpsConfig
{
int processorsCount;
vpsConfCounter *counters;
unsigned int countersCount;
bool printToScreen;
std::wstring activityName;
};
extern "C"
VPS::ErrorCode InitConfig(vpsConfig conf);
extern "C"
VPS::ErrorCode ClearConfig(vpsConfig conf);
Задача — реализовать вызов методов из managed кода.
Возможные подходы
Нахрапом решить задачу не получилось. При поиске решений нашелся один HowTo . В нем кратко описывается возможность маршалинга динамического массива структур, но при нем на каждый указатель приходиться много «обслуживающего» кода. В случае, если вы выберете путь маршалинга, описанный в этой статье, то необходимо объявить массивы с ключевым словом fixed, иначе могут быть проблемы.
Суть проблемы в том, что GC может проигнорировать вложенность второго уровня и переместить ваши структуры, после этого действия указатели будут содержать некорректные значения.
Принятое решение
Так как метод, на который дана ссылка выше требует достаточно аккуратной работы и генерирует много лишнего кода, затрудняющего чтение. То было принято решение, использовать временные структуры:
namespace ManagedTemp
{
#pragma pack (push, 4)
struct vpsMSR
{
unsigned __int64 data;
unsigned int address;
};
#pragma pack (pop)
struct vpsConfCounter
{
int address;
int number;
vpsMSR config[10];
unsigned int configCount;
};
struct vpsConfig
{
int processorsCount;
vpsConfCounter counters[10];
unsigned int countersCount;
bool printToScreen;
wchar_t* activityName;
};
}
В этих структурах используются массивы — константной длины. Ограничения — продиктованы предметной областью и на практике не достигаются.
D dll были добавлены два метода, которые приинимают структуры с постоянной длиной массива и пробрасывают их в изначальные методы.
extern "C"
VPS::ErrorCode InitConfig2(ManagedTemp::vpsConfig* conf);
extern "C"
VPS::ErrorCode ClearConfig2(ManagedTemp::vpsConfig* conf);
Маршалинг
Приступаем к маршалингу структур, описанных выше:
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct MSR
{
[MarshalAs(UnmanagedType.U8)]
public System.UInt64 data;
public int adress;
}
[StructLayout(LayoutKind.Sequential)]
public struct ConfCounter
{
public int adress;
public int number;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public MSR[] config;
[MarshalAs(UnmanagedType.U4)]
public uint configCount;
}
[StructLayout(LayoutKind.Sequential), Serializable]
public struct Config
{
public int processorCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public ConfCounter[] counters;
[MarshalAs(UnmanagedType.U4)]
public uint countersCount;
[MarshalAs(UnmanagedType.Bool)]
public bool printToScreen;
[MarshalAs(UnmanagedType.LPWStr)]
public string activityName;
}
Маршалинг методов:
[DllImport(@"..\..\IConfigure.dll", EntryPoint = "InitConfig2")]
public static extern int InitConfig(ref Config conf);
[DllImport(@"..\..\IConfigure.dll", EntryPoint = "ClearConfig2")]
public static extern int ClearConfig(ref Config conf);
Так, как в dll на C++ по умолчанию выставлен stdcall, то нет необходимости явно указывать тип вызова через атрибут CallingConvention.
Возникавшие проблемы и как их решать
При реализации маршалинга получаю ошибку/предуреждение StackUnbalanced. Что делать?
Эту ошибку мне приходилось чаще всего видеть — она означает, что сигнатуры импортируемой функции и исходной — не совпадают.
Причины:
1. Некорректный машалинг типов.
2. Разная конвенция вызова.
Способы диагностики: Сначала проверить соответствие конвенций вызова, потом попробовать передавать структуру поле за полем и смотреть на каком поле вылетает ошибка.
Невозможность приведения int к __int64
PInvoke не приводит int к int64. Поэтому пришлось использовать тип System.Int64.
Этот материал основан на опыте работы в лаборатории ITLab при ННГУ. В процессе использовалась статья HowTo и информация о маршалинге на msdn.