【问题标题】:Convert/cast SAFEARRAY of IUnknowns to an iterable array of interface pointers将 IUnknowns 的 SAFEARRAY 转换/转换为接口指针的可迭代数组
【发布时间】:2012-09-06 21:52:06
【问题描述】:

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

[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。我在 C++ 代码中使用 #import 来获取 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* 或其他东西,这样我就可以遍历返回的整个数组并调用“Order”类型的方法

  • GetQuantity()
  • GetOrderType()
  • GetPositionType()

所以对于第一个周期,我将获得值 1、2、3,对于第二个周期,我将获得值 4、5、6。

由于我同时是 C++ 和 C# 库的作者,我可以跳过所有这些 COM 疯狂的东西,并创建方法来获取集合计数和其他方法来获取特定索引上的属性值。

但这似乎不太好。我怀疑我想要的机制很简单,但我在谷歌上找到的所有答案总是缺少一些东西。

【问题讨论】:

标签: c# c++ com safearray iunknown


【解决方案1】:

在不知道您在 C++ 客户端中是否使用 MFC、ATL 或其他库的情况下,很难简化它,因此我将使用 Win32 API(这些库提供帮助类以更简单地使用安全数组)

但是,我将假设您通过 Interop 类型库的#import 使用 C# lib,因此您可以使用生成的智能指针类。我还将假设您返回 IUnknowns 的 SAFEARRAY 而不是包含 IUnknowns 的 Variants 的 SAFEARRAY - 这可以通过在 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;
}

示例项目can be found here。请注意,您必须在第一次构建时手动注册 .tlb,项目不会这样做,但您可以根据需要添加构建后步骤

【讨论】:

  • 你说得对,我不使用 MFC 或 ATL,我通过 regasm 生成 C# lib,然后导入 tlb 文件。现在,我正在尝试使用您的代码,但是当调用 proc GetQuantity 时出现异常:(
  • 您能否提供有关异常和 HRESULT 的详细信息?调用是否到达 .NET 组件端?
  • 我现在正在调试它 - 在 IOrderPtr order(punk); 行之后,“订单”指向 {0x00000000} ...对吗? .NET 组件根本不会被调用.... procCall = order->GetQuantity((long long *)q) ==> 这会引发异常并且 HRESULT procCall 仍未初始化。我将它放在 try {} catch(...) {} 中,因为我什至不知道我必须捕获什么类型的异常...
  • 嗯,不是。在实例化order 之前punk 的值是多少? cElements 的值是多少?
  • cElements = 2; ...在订单被实例化之前,punk 是 IUnknown * 类型,并且指向一个叫做 __vfptr 的东西,它指向三个 * 类型的函数指针(我猜这只是意味着 void*)。我猜这三个指针是 IOrder 中的三个函数……这给了我一点希望:) 但是在使用 IOrderPtr order(punk); 调用之后,朋克只是指向零。
【解决方案2】:

与 SAFEARRAYS 合作令人头疼。只是没有办法解决它。

由于 SAFEARRAY 是一种结构,因此您不能像在 C# 中那样将其转换为方便的 IOrder* 数组并处理项目。

以下是 google 向我展示的一些内容。它们看起来很有帮助。

http://edn.embarcadero.com/article/22016

http://digital.ni.com/public.nsf/allkb/7382E67B95238D2B862569AD005977F0

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

http://msmvps.com/blogs/gdicanio/archive/2011/02/04/simplifying-safearray-programming-with-ccomsafearray.aspx

【讨论】:

    猜你喜欢
    • 2015-11-03
    • 2010-10-19
    • 2023-04-04
    • 1970-01-01
    • 1970-01-01
    • 2016-09-17
    • 2021-08-01
    • 2014-08-16
    • 1970-01-01
    相关资源
    最近更新 更多