Managed byte [] into an unmanaged byte array using ATL / COM

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

From the side of C # code, I am doing something like this:

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

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

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 someone give me some advice on how I can make this work work?

Thanks.

+4
source share
4 answers

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

My event handler descriptor is as follows:

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

Everything seems to be working fine. But if someone has any suggestions or I noticed that I am doing something wrong, let me know.

Thanks.

+3
source

Since SAFEARRAY already configured for your unmanaged code and you are using ATL, you can use the CComSafeArray class as it handles all 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. } 
+2
source

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

Given your example in your answer, a pretty minimal IDL file might 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; }; 

Then you use midl.exe to create .h with the interfaces C / C ++, _i.c for the CLSID constants and IID for binding C / C ++, dlldata.c for registering RPC, _p.c with the proxy server and marking material of stub and .tlb, which in the general case is an analyzed representation of the .idl file. This is better described in the documentation .

EDIT: It seems impossible to avoid generating C / C ++ files .

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

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

Note. If your IStreamFrame interface IStreamFrame not intended for use in different processes, add the local attribute to the interface.

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

You can also use tlbexp.exe if your project is .NET-oriented. However, this will require you to know the .NET COM annotations and what they mean in terms of IDL, so I'm not sure if there is any profit when saving one additional source file in another language due to the large amount of noise decorations in your interface and class definitions. Perhaps this is a good option if you want to have full control over the classes and interfaces at the initial level, and you want to make it as simple as possible (read, optimize for ease of use and, possibly, speed) in .NET code.

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

EDIT: if you do not need the generated C / C ++ files from midl.exe , you can add del commands to your custom build step for specific output files.

EDIT2: Or use the nul workaround described above.

The general approach used when a type library already exists is to use Visual Studio to import into .NET. But you will need to remember to restore the TLB and import it again if you update your IDL file.

+1
source

Have you considered C ++ / CLI? In your C ++ / CLI ref class you should 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 } 
0
source

Source: https://habr.com/ru/post/1444821/


All Articles