Маршаллинг массивов VARIANT с использованием P/Invoke

Ситуация: у меня есть управляемое (C#, .NET 2.0) приложение, которое использует неуправляемую (C++) DLL с помощью P/Invoke. Наряду с «простыми» методами (аргументы POD/возвращаемое значение) требуется передавать в код массивы значений boost::variant. Причина этого в том, что эти методы передают данные отчета (аналогично ячейкам Excel, которые могут быть любого типа). Код С# принимает их как «объекты» в штучной упаковке.

Предыдущая реализация требовала использования SafeArray COM VARIANT. Однако из-за плохого кодирования/отсутствия тестирования сортировка привела к утечке памяти. Теперь мне нужно найти другой вариант сортировки данных.

Предыдущая реализация выглядела так: C++:

extern "C" __declspec(dllexport) void GetReport(VARIANT& output) {
    // ... here a SafeArray of VARIANT values was created
    output.vt = VT_VARIANT | VT_ARRAY;
    output.parray = safeArray;
}

С#

[DllImport("CppLibrary.dll")]
private static extern void GetReport(out object output);

//....
object data;
GetReport(data);
object rows = data as object[];

Эта конкретная реализация приводит к утечке памяти, поскольку не освобождает структуры взаимодействия.

Я попытался изменить прототипы, включив директивы сортировки SafeArray:

С++

extern "C" __declspec(dllexport) void GetReport(SAFEARRAY output) { // Also tried SAFEARRAY*, SAFEARRAY&, VARIANT, VARIANT&, VARIANT*
    // Internal stuff
}

С#

private static extern void GetReport([Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]ref object[] output);

Однако единственное, чего я добился, это либо пустой результирующий объект(ы), либо сбой из-за повреждения памяти/переполнения стека.

Проблема: как правильно маршалировать такой тип данных (массив структур типа VARIANT) в C#? Я могу сделать C++ DLL COM-библиотекой, но для этого потребуется переписать довольно много кода. Есть ли более простой выход из ситуации? Может быть, я что-то упускаю.


person DarkWanderer    schedule 10.09.2013    source источник


Ответы (1)


Вот такой пример: http://limbioliong.wordpress.com/2011/03/20/c-interop-how-to-return-a-variant-from-an-unmanaged-function/

В конце концов, они используют непосредственно IntPtr (они используют его как возвращаемое значение, вам придется использовать его как out IntPtr), затем Marshal.GetObjectForNativeVariant(), VariantClear() и Marshal.FreeCoTaskMem() со стороны C#, в то время как на стороне C/C++ выделяется VARIANT с CoTaskMemAlloc().

[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
static extern void MyFunction(out IntPtr ptr);

[DllImport("oleaut32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
static extern Int32 VariantClear(IntPtr pvarg);

IntPtr pVariant;
MyFunction(out pVariant);

object objRet = Marshal.GetObjectForNativeVariant(pVariant);

VariantClear(pVariant);
Marshal.FreeCoTaskMem(pVariant);

pVariant = IntPtr.Zero;

Ясно, что вы могли бы предоставить другую функцию C в вашей dll, которая освобождает VARIANT (всегда правильно выставлять методы Free в вашей библиотеке, чтобы вызывающий мог использовать их, а не спрашивать себя: «Как я должен освободить эту память?»)

person xanatos    schedule 10.09.2013
comment
Хм. Вроде вариант приемлемый, попробую. Вы знаете, работает ли это с вложенными вариантами (vt = VT_VARIANT | VT_ARRAY, как в примере выше)? - person DarkWanderer; 10.09.2013
comment
@DarkWanderer VariantClear Safearrays of variant will also have VariantClear called on each member. Using VariantClear in these cases ensures that code will continue to work if Automation adds new variant types in the future. - person xanatos; 10.09.2013
comment
У-у-у! Это полностью прибивает его. Мне все равно придется немного переписать интерфейс, но это ничто по сравнению с переходом на COM. Большое тебе спасибо! - person DarkWanderer; 10.09.2013