将 IUnknowns 的 SAFEARRAY 转换/转换为接口指针的可迭代数组 [英] Convert/cast SAFEARRAY of IUnknowns to an iterable array of interface pointers

查看:17
本文介绍了将 IUnknowns 的 SAFEARRAY 转换/转换为接口指针的可迭代数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have the following interface in C# with a class with a same name (without I) implementing it.

[ComVisible(true)]
[Guid("B2B134CC-70A6-43CD-9E1E-B3A3D9992C3E")]
public interface IOrder
{
    long GetQuantity();
    long GetOrderType();
    long GetPositionType();
}

公共类 Order 的实现:IOrder 只是三个私有字段和一个需要 3 个参数的构造函数.

The implementation of the public class Order : IOrder is just three private fields and a constructor with required 3 parameters.

在其他地方,我有以下方法,其结果是我想在 C++ 非托管代码中工作,通过 COM 和 .tlb/.tlh 文件传输到那里.

Somewhere else, I have the following method with a result with which I want to work inside a C++ unmanaged code, transferred there via COM and .tlb/.tlh files.

public ScOrder[] GetOrders()
{
    //constant return value for simplicity
    return new Order[] {    
        new Order(1, 2, 3),
        new Order(4, 5, 6)
    };
}

我已经设法在使用 C# 托管代码的 C++ 非托管代码之间进行基础工作.

I've already managed to get the basics work between the C++ unmanaged code using the C# managed code.

但是类数组被证明是一个不同的挑战......

But class arrays proved to be a different challange...

我承认,对我来说,COM 是新的,令人困惑,C++ 早已被遗忘......,但我正在开发这两个库,所以我不会放弃;我希望 C++ DLL 在某些程序和我的 C# 代码之间充当代理.

I admit that for me, the COM is new and brutally confusing and C++ long forgotten ... , but I'm developing both libraries so I'm not giving up; I want the C++ DLL to work as a proxy between some program and my C# code.

澄清:我既没有使用 MFC,也没有使用 ATL.我在 C++ 代码中使用 #import 来获取 C# 生成的接口和类指针以及其他我不太了解的 COM 内容.

Clarification: I'm using neither MFC nor ATL. I use #import in the C++ code for getting the C# generated interface and class pointers and other COM stuff I don't quite understand yet.

经过一个小时的研究,我只是去这里寻求帮助>.<

After hour of researching, I'm just going here and beg for help >.<

以下是我想要实现的 C++ 代码.

The following is the C++ code of what I'm trying to achieve.

//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"类型的方法

Now at this point, I need some COM magic I don't yet understand to convert that SAFEARRAY* to IOrderPtr* or something so I can iterate over the whole array returned and call the methods of the type "Order"

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

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

So for the first cycle, I'll get values 1,2,3 and for the second cycle, I'll get values 4,5,6.

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

Since I'm the author of both C++ and C# library, I can just skip all of this COM crazy stuff and make methods to get the collection count and other methods to get the value of property on certain index.

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

But that just doesn't seem nice. I suspect the mechanics of what I want are easy but all the answers I found on google are always missing something.

推荐答案

不知道你在 C++ 客户端中是使用 MFC、ATL 还是其他一些库,很难简化,所以我将使用 Win32 API(这些库提供用于更简单地使用安全数组的辅助类)

Without knowing whether you use MFC, ATL or some other library in your C++ client, it is hard to simplify it so I will use Win32 API (these libraries provide helper classes for simpler usage of safearrays)

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

However, I will assume that you use you C# lib through #import of Interop type library, so you can use generated smart pointer classes. I will also assume that you return a SAFEARRAY of IUnknowns and not a SAFEARRAY of Variants which contain IUnknowns - this can be modified by specifying appropriate marshaling attributes on you C# interface, e.g:

[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UNKNOWN)] 
// [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
IOrder[] GetOrders();

以下是 C# 类型的实现(答案底部有示例解决方案的链接):

Here are the implementations of C# types (link to a sample solution is at the bottom of the answer):

[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 中签名和注册.

The server must be built and registered with Regasm. For simplicity, I will do regasm /codebase /tlb $path to avoid signing and registering in GAC.

客户端代码应该是这样的:

The client code should like something like this:

#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,项目不会这样做,但您可以根据需要添加构建后步骤

The sample project can be found here. Please note that you must manually register the .tlb first time you build it, the project doesn't do it, but you can add a post-build step if you want

这篇关于将 IUnknowns 的 SAFEARRAY 转换/转换为接口指针的可迭代数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆