У меня есть следующий интерфейс в C # с классом с тем же именем (без I), реализующим его.
[ComVisible(true)]
[Guid("B2B134CC-70A6-43CD-9E1E-B3A3D9992C3E")]
public interface IOrder
{
long GetQuantity();
long GetOrderType();
long GetPositionType();
}
Реализация открытого класса Order: IOrder — это всего три приватных поля и конструктор с необходимыми 3 параметрами.
Где-то еще у меня есть следующий метод, в результате которого я хочу работать внутри неуправляемого кода C ++, передаваемого туда через файлы COM и .tlb / .tlh.
public ScOrder[] GetOrders()
{
//constant return value for simplicity
return new Order[] {
new Order(1, 2, 3),
new Order(4, 5, 6)
};
}
Мне уже удалось получить основы работы между неуправляемым кодом C ++ с использованием управляемого кода C #.
Но классовые массивы оказались другой проблемой …
Я признаю, что для меня COM является новым и жестоко запутанным, а C ++ давно забыт … но я разрабатываю обе библиотеки, поэтому я не сдамся; Я хочу, чтобы C ++ DLL работала как прокси между какой-то программой и моим C # кодом.
Разъяснение: Я не использую ни MFC, ни ATL. Я использую #import в коде C ++ для получения сгенерированного интерфейса C # и указателей классов и других вещей COM, которые я пока не совсем понимаю.
После часа исследований я просто иду сюда и прошу помощи>.<
Ниже приведен код C ++, который я пытаюсь достичь.
//this is how the instance of C# gets created, read it from the internets
//this type has the method GetOrders
IProxyPtr iPtr;
CoInitialize(NULL);
iPtr.CreateInstance(CLSID_Proxy);
IOrderPtr* ordArr;
//IOrderPtr is just a pointer to the interface type transferred
//right? So IOrderPtr* should represent the array of those pointers, right?
SAFEARRAY* orders;
iPtr->GetOrders(&orders);
Теперь на этом этапе мне нужна некоторая магия COM, которую я пока не понимаю, чтобы преобразовать этот SAFEARRAY * в IOrderPtr * или что-то подобное, чтобы я мог перебирать весь возвращаемый массив и вызывать методы типа «Порядок».
Итак, для первого цикла я получу значения 1,2,3, а для второго цикла получу значения 4,5,6.
Поскольку я являюсь автором библиотек C ++ и C #, я могу просто пропустить все эти безумные COM-вещи и создать методы, чтобы получить счетчик коллекций и другие методы, чтобы получить значение свойства для определенного индекса.
Но это не кажется хорошим. Я подозреваю, что механика того, что я хочу, проста, но все ответы, которые я нашел в Google, всегда что-то упускают.
Не зная, используете ли вы MFC, ATL или какую-либо другую библиотеку в своем клиенте C ++, трудно упростить ее, поэтому я буду использовать Win32 API (эти библиотеки предоставляют вспомогательные классы для более простого использования safearrays)
Тем не менее, я буду считать, что вы используете C # lib через #import
библиотеки типов взаимодействия, так что вы можете использовать сгенерированные классы интеллектуальных указателей. Я также предполагаю, что вы возвращаете SAFEARRAY IUnknowns, а не SAFEARRAY Variants, которые содержат IUnknowns — это можно изменить, указав соответствующие атрибуты маршалинга на вашем интерфейсе C #, например:
[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UNKNOWN)]
// [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
IOrder[] GetOrders();
Вот реализации типов C # (ссылка на образец решения находится внизу ответа):
[ComVisible(true)]
[Guid("F3071EE2-84C9-4347-A5FC-E72736FC441F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IProxy
{
[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UNKNOWN)]
IOrder[] GetOrders();
}
[ComVisible(true)]
[Guid("8B6EDB6B-2CF0-4eba-A756-B6E92A71A48B")]
[ClassInterface(ClassInterfaceType.None)]
public class Proxy : IProxy
{
public IOrder[] GetOrders() { return new[] {new Order(3), new Order(4)}; }
}
[ComVisible(true)]
[Guid("CCFF9FE7-79E7-463c-B5CA-B1A497843333")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOrder
{
long GetQuantity();
}
[ComVisible(true)]
[Guid("B0E866EB-AF6D-432c-9560-AFE7D171B0CE")]
[ClassInterface(ClassInterfaceType.None)]
public class Order : IOrder
{
private int m_quantity;
public Order(int quantity) { m_quantity = quantity; }
public long GetQuantity() { return m_quantity; }
}
Сервер должен быть собран и зарегистрирован Regasm
, Для простоты я сделаю regasm /codebase /tlb $path
чтобы избежать подписи и регистрации в GAC.
Код клиента должен выглядеть примерно так:
#import "Server.tlb" no_namespace // you should use namespaces! this is a demo
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL); // init COM
IProxyPtr proxy(__uuidof(Proxy)); // instantiate the proxy
SAFEARRAY* orders = proxy->GetOrders(); // to return orders
LPUNKNOWN* punks;
HRESULT hr = SafeArrayAccessData(orders, (void**)&punks); // direct access to SA memory
if (SUCCEEDED(hr))
{
long lLBound, lUBound; // get array bounds
SafeArrayGetLBound(orders, 1 , &lLBound);
SafeArrayGetUBound(orders, 1, &lUBound);
long cElements = lUBound - lLBound + 1;
for (int i = 0; i < cElements; ++i) // iterate through returned objects
{
LPUNKNOWN punk = punks[i]; // for VARIANTs: punk = punks[i].punkVal
IOrderPtr order(punk); // access the object via IOrder interface
long q = order->GetQuantity(); // and voila!
std::cout << "order " << i + 1 << ": Quantity " << q << std::endl;
}
SafeArrayUnaccessData(orders);
}
SafeArrayDestroy(orders);
return 0;
}
Пример проекта можно найти здесь. Обратите внимание, что вы должны вручную зарегистрировать .tlb при первой сборке, проект этого не делает, но вы можете добавить шаг после сборки, если хотите
Работа с SAFEARRAYS — это боль в шее. Там просто нет возможности обойти это.
Поскольку SAFEARRAY — это структура, вы не можете просто привести ее к удобному массиву IOrder * и работать с элементами так же, как в C #.
Вот несколько вещей, которые Google показал мне. Они выглядят довольно полезными.
http://edn.embarcadero.com/article/22016
http://digital.ni.com/public.nsf/allkb/7382E67B95238D2B862569AD005977F0
Если вы используете ATL в своем проекте C ++, у вас есть обертка CComSafeArray: