我在C#中使用以下具有相同名称的类(没有我)实现它的接口(interface)。
[ComVisible(true)]
[Guid("B2B134CC-70A6-43CD-9E1E-B3A3D9992C3E")]
public interface IOrder
{
long GetQuantity();
long GetOrderType();
long GetPositionType();
}
公共(public)类Order的实现:IOrder只是三个私有(private)字段,并且是一个带有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。我在C++代码中使用#import来获取C#生成的接口(interface)和类指针以及其他我不太了解的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 *或其他东西,以便可以迭代返回的整个数组并调用“Order”类型的方法
因此,对于第一个周期,我将获得值1,2,3,对于第二个周期,我将获得值4,5,6。
由于我是C++和C#库的作者,因此我可以跳过所有这些COM疯狂的知识,并使用方法来获取集合计数,而使用其他方法来获取特定索引上的属性值。
但这似乎并不好。我怀疑自己想要的机制很简单,但我在Google上找到的所有答案始终缺少某些内容。
最佳答案
不知道您是在C++客户端中使用MFC,ATL还是其他库,很难对其进行简化,因此我将使用Win32 API(这些库提供了用于简化safearrays的帮助器类)
但是,我将假定您通过Interop类型库的#import
使用C#库,因此可以使用生成的智能指针类。我还将假设您返回的是IUnknowns的SAFEARRAY而不是包含IUnknowns的Variants的SAFEARRAY-可以通过在C#接口(interface)上指定适当的封送处理属性来对其进行修改,例如:
[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;
}
示例项目can be found here。请注意,您必须在首次构建.tlb时手动注册它,但项目不会这样做,但是如果您愿意,可以添加一个构建后步骤。
关于c# - 将IUnknowns的SAFEARRAY转换/广播为接口(interface)指针的可迭代数组,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12412533/