Pergunta

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();
}

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

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)
    };
}

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...

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.

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 >.<

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);

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()

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

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.

Foi útil?

Solução

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)

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();

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; }
}

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;
}

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

Outras dicas

Working with SAFEARRAYS is a pain in the neck. There's just no way around it.

Since a SAFEARRAY is a structure you can't just cast to a handy IOrder* array and work with the items as you would in C#.

Here's a few things google showed me. They look pretty helpful.

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

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

If you're using ATL in your C++ project you've got a CComSafeArray wrapper:

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top