Question

I want to pass some image data from C# code to unmanaged C++ using ATL/COM

From C# code side i do something like this:

void SendFrame([In, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_UI1)] ref byte[] frameData);

But I'm not sure how should I handle this function in my C++ code.

For now I have something like this:

_ATL_FUNC_INFO OnSendFrameDef = { CC_STDCALL, VT_EMPTY, 1, { VT_SAFEARRAY | VT_UI1 } };

void __stdcall OnSendFrame(SAFEARRAY* ppData)
{
   BYTE* pData;
   SafeArrayAccessData(ppData, (void **) &pData);

   // Doing some stuff with my pData

   SafeArrayUnaccessData(ppData);
}

Can anyone give me some suggestions how I can make this thing work?

Thanks.

Was it helpful?

Solution 2

I've managed to achieve my goal! For those who are interested:

My event handler descriptor looks like this:

_ATL_FUNC_INFO Stream::OnStreamFrameCallbackDef = { CC_STDCALL, VT_EMPTY, 1, { VT_DISPATCH } };

My C++ function:

void __stdcall Stream::OnStreamFrameCallback(IDispatch* pFrame)
{
   // NOTE that this "IStreamFramePtr" is COM's Ptr of my "IStreamFrame"
   MyCOM::IStreamFramePtr pStreamFrame = pFrame;

   // Thanks casperOne♦ for this:
   CComSafeArray<byte> array;
                   array.Attach(pStreamFrame->GetBuffer());

   // Now I can do stuff that I need...
   byte* pBuffer = &array.GetAt(0);
}

My "IStreamFrame" in my .tlh file looks like this:

struct __declspec(uuid("1f6efffc-0ac7-3221-8175-5272a09cea82"))
IStreamFrame : IDispatch
{
    __declspec(property(get=GetWidth))
    long Width;
    __declspec(property(get=GetHeight))
    long Height;
    __declspec(property(get=GetBuffer))
    SAFEARRAY * Buffer;

    long GetWidth ( );
    long GetHeight ( );
    SAFEARRAY * GetBuffer ( );
};

In my C# code I have something like this:

[ComVisible(true)]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
public interface IStreamFrame
{
    int     Width   { [return: MarshalAs(UnmanagedType.I4)]         get; }
    int     Height  { [return: MarshalAs(UnmanagedType.I4)]         get; }
    byte[]  Buffer  { [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_UI1)]  get; }
};


[ComVisible(false)]
public delegate void StreamFrameCallback(IStreamFrame frame);

[ComVisible(true)]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMyCOMStreamEvents
{
    [DispId(1)]
    void OnStreamFrameCallback([In, MarshalAs(UnmanagedType.IDispatch)] IStreamFrame frame);
}

Things seems to work just fine. But if anyone have any suggestions or noticed that I'm doing something wrong please let me know.

Thanks.

OTHER TIPS

Since the SAFEARRAY is already marshaled to your unmanaged code, and you're using ATL, you can use the CComSafeArray class, as it handles all the cleanup when working with SAFEARRAY instances:

_ATL_FUNC_INFO OnSendFrameDef = { CC_STDCALL, VT_EMPTY, 1, 
    { VT_SAFEARRAY | VT_UI1 } };

void __stdcall OnSendFrame(SAFEARRAY* ppData)
{
    // Wrap in a CComSafeArray.
    // On the stack means all calls to cleanup
    // will be cleaned up when the stack
    // is exited.
    CComSafeArray<byte> array;
    array.Attach(ppData);

    // Work with the elements, get the first one.
    byte b = array.GetAt(0);

    // And so on.  The destructor for CComSafeArray
    // will be called here and cleaned up.
}

I strongly recommend you to create an IDL file to design your COM interfaces.

Given your example in your answer, a rather minimal IDL file could be like this:

import "oaidl.idl"; 

[object,
 uuid(1f6efffc-0ac7-3221-8175-5272a09cea82),
 dual,
 oleautomation]
interface IStreamFrame : IDispatch {
    [propget]
    HRESULT Width([out, retval] long *pWidth);
    
    [propget]
    HRESULT Height([out, retval] long *pHeight);
    
    [propget]
    HRESULT Buffer([out, retval] SAFEARRAY(byte) *pBuffer);
};

[uuid(1f6efffc-0ac7-3221-8175-5272a09cea83)]
library ContosoStreamFrame {
    importlib("stdole32.tlb");
    
    interface IStreamFrame;
};

You then use midl.exe to generate a .h with C/C++ interfaces, a _i.c for the CLSID and IID constants for C/C++ linking, a dlldata.c for RPC registering, a _p.c with proxy and stub marshalling stuff and a .tlb which is in general terms the parsed representation of the .idl file. This is all better described in the documentation.

EDIT: There seems to be no way to avoid the C/C++ file generation.

EDIT2: I just found a workaround, use nul as the output file for what you don't want. For instance, the following command only generates file.tlb:

midl.exe /header nul /iid nul /proxy nul /dlldata nul file.idl

Note: if your IStreamFrame interface is not meant to be used across processes, add the local attribute to the interface.

In C/C++, you can use the specific files that are generated, or #import the TLB file. In .NET, you can run tlbimp.exe on the TLB file, which generates a .NET assembly.

You can also use tlbexp.exe if your project is .NET centric. However, that'll require you to know the .NET COM annotations and what they mean in terms of IDL, so I'm not sure if there's any gain on saving one extra source-file in another language at the expense of lots of decoration noise in your interface and class definitions. It's perhaps a good option if you want to have full control of the classes and interfaces at the source level and you want to make it as easy as possible (read, optimized for usability and maybe speed) on .NET code.

Finally, you can automate all of this in Visual Studio by creating a project. If you use the IDL approach, add a custom build step that invokes midl.exe and tlbimp.exeand make the dependant projects depend on this project for a correct build order. If you use the .NET approach, add a custom build step that invokes tlbexp.exe and make the dependant C/C++ projects depend on this project.

EDIT: If you don't need the generated C/C++ files from midl.exe, you may add del commands to your custom build step for the specific output files.

EDIT2: Or use the nul workaround described above.

A common approach used when the type library already exists is to use Visual Studio to import it into .NET. But this way, you'll have to remember to regenerate the TLB and import it again if you update your IDL file.

Have you considered C++/CLI? In your C++/CLI ref class you'd write a member function:

void SendFrame(cli::array<System::Byte> frameData)
{
    pin_ptr<System::Byte> p1 = &frameData[0];
    unsigned char *p2 = (unsigned char *)p1;

    // So now p2 is a raw pointer to the pinned array contents
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top