C# 将IUnknowns的SAFEARRAY转换/强制转换为接口指针的iterable数组

C# 将IUnknowns的SAFEARRAY转换/强制转换为接口指针的iterable数组,c#,c++,com,safearray,iunknown,C#,C++,Com,Safearray,Iunknown,我在C#中有以下接口,其中有一个同名的类(没有I)实现了它 [ComVisible(true)] [Guid("B2B134CC-70A6-43CD-9E1E-B3A3D9992C3E")] public interface IOrder { long GetQuantity(); long GetOrderType(); long GetPositionType(); } 公共类Order:IOrder的实现只是三个私有字段和一个具有所需3个参数的构造函数 其他地方,

我在C#中有以下接口,其中有一个同名的类(没有I)实现了它

[ComVisible(true)]
[Guid("B2B134CC-70A6-43CD-9E1E-B3A3D9992C3E")]
public interface IOrder
{
    long GetQuantity();
    long GetOrderType();
    long GetPositionType();
}
公共类Order:IOrder的实现只是三个私有字段和一个具有所需3个参数的构造函数

其他地方,我有以下方法,我想在C++中使用一个C++非托管代码,通过COM和.TBB/.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++代码中使用了“导入”来获取C生成的接口和类指针以及其他我还不太理解的COM。
经过一个小时的研究,我正要到这里来寻求帮助>使用SAFEARRAYS是一件痛苦的事。这是没有办法的

因为SAFEARRAY是一种结构,所以您不能像在C#中那样将其转换为一个方便的IOrder*数组并处理这些项

以下是谷歌向我展示的一些东西。它们看起来很有用

如果你在C++项目中使用ATL,你有一个CCOM安全包包装器:


< P>不知道你是否在C++客户端中使用MFC、ATL或其他库,很难简化它,所以我将使用Win32 API(这些库为HaveLead的简单使用提供帮助类)

但是,我假设您通过Interop类型库的
\import
使用C#lib,因此您可以使用生成的智能指针类。我还假设您返回的是IUnknowns的SAFEARRAY,而不是包含IUnknowns的变体的SAFEARRAY-这可以通过在C#接口上指定适当的封送属性来修改,例如:

[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UNKNOWN)] 
// [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
IOrder[] GetOrders();
下面是C#type的实现(答案底部是指向示例解决方案的链接):

必须使用
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;
}
#导入“Server.tlb”无命名空间//您应该使用命名空间!这是一个演示
int _tmain(int argc,_TCHAR*argv[]
{
协同初始化(NULL);//初始化COM
IProxyPtr代理(uuidof(proxy));//实例化代理
SAFEARRAY*orders=proxy->GetOrders();//返回订单
LPUNKNOWN*朋克;
HRESULT hr=SafeArrayAccessData(orders,(void**)和punks);//直接访问SA内存
如果(成功(hr))
{
long lLBound,lUBound;//获取数组边界
SafeArrayGetBound(订单、1和lLBound);
SafeArrayGetUBound(订单、1和lUBound);
长芹菜=lUBound-lLBound+1;
for(int i=0;i获取数量();//瞧!

std::你说得对吗,我既不使用MFC也不使用ATL,我通过regasm生成C#lib,然后导入tlb文件。现在,我正在尝试使用你的代码,但调用proc GetQuantity时,我会遇到异常:(您能提供关于异常和HRESULT的详细信息吗?调用是否到达.NET组件端?我现在正在调试它-在IOrderPtr order(朋克);“order”行之后,指向{0x00000000}…是这样吗?根本不会调用.NET组件…procCall=order->GetQuantity((long-long*)q)==>这会引发异常,并且HRESULT procCall仍然未初始化。我在try{}catch(…){}中有它因为我甚至不知道我必须捕获哪种类型的异常…嗯,不是。在实例化
order
之前,
punk
的值是多少?cElements的值是多少?
cElements
?cElements=2;…在实例化order之前,punk的类型是IUnknown*并且指向一个名为u vfptr的东西,它指向我猜这三个指针就是IOrder中的三个函数…这给了我一点希望:)但是在调用IOrderPtr order(朋克)之后,朋克只指向零。我用另一个问题解决了这个问题-
[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; }
}
#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;
}