
Все чаще перед разработчиками стала вставать задача вызова 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.
