使用ATL / COM将字节[]托管到非托管字节数组

时间:2012-11-08 14:46:19

标签: c# c++ com unmanaged managed

我想使用ATL / COM

将一些图像数据从C#代码传递给非托管C ++

从C#代码方面我做这样的事情:

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

但是我不确定如何在C ++代码中处理这个函数。

现在我有这样的事情:

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

任何人都可以给我一些建议,让我能让这件事发挥作用吗?

感谢。

4 个答案:

答案 0 :(得分:2)

由于SAFEARRAY已经封送到您的非托管代码,您正在使用ATL,因此您可以使用CComSafeArray class,因为它在处理工作时处理所有清理工作SAFEARRAY个实例:

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

答案 1 :(得分:2)

我成功实现了目标!对于那些感兴趣的人:

我的事件处理程序描述符如下所示:

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

我的C ++功能:

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

我的.tlh文件中的“IStreamFrame”如下所示:

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

在我的C#代码中,我有类似的东西:

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

事情似乎工作得很好。但如果有人有任何建议或注意到我做错了,请告诉我。

感谢。

答案 2 :(得分:1)

我强烈建议您创建IDL filedesign your COM interfaces

在你的答案中给出你的例子,一个相当小的IDL文件可能是这样的:

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

然后使用midl.exe生成带有C / C ++接口的.h,用于C / C ++链接的CLSID和IID常量的_i.c,用于RPC注册的dlldata.c,_p.c使用代理和存根编组的东西和.tlb,一般来说是.idl文件的解析表示。这在documentation中有更好的描述。

编辑:似乎有no way to avoid the C/C++ file generation

EDIT2:我刚刚找到了解决方法,使用nul作为您不想要的输出文件。例如,以下命令仅生成file.tlb

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

注意:如果您的IStreamFrame接口不打算在进程间使用,请将local属性添加到接口。

在C / C ++中,您可以使用生成的特定文件,或#import TLB文件。在.NET中,您可以在TLB文件上运行tlbimp.exe,该文件生成.NET程序集。

如果您的项目是以.NET为中心,您也可以使用tlbexp.exe。但是,这将要求您了解.NET COM注释以及它们在IDL方面的含义,因此我不确定是否有任何收益以另一种语言保存一个额外的源文件而牺牲了大量的装饰接口和类定义中的噪声。如果您希望在源代码级别完全控制类和接口,并且希望在.NET代码上尽可能简单地(读取,优化可用性和可能的​​速度),这可能是一个不错的选择。

最后,您可以通过创建项目在Visual Studio中自动执行所有这些操作。如果使用IDL方法,请添加调用midl.exetlbimp.exe的自定义构建步骤,并使依赖项目依赖于此项目以获得正确的构建顺序。如果使用.NET方法,请添加一个调用tlbexp.exe的自定义构建步骤,并使依赖的C / C ++项目依赖于此项目。

编辑:如果您不需要midl.exe生成的C / C ++文件,您可以将del命令添加到特定输出文件的自定义构建步骤中。

EDIT2:或使用上述nul解决方法。

类型库已经存在时使用的常用方法是使用Visual Studio将其导入.NET。但是这样,你必须记住重新生成TLB并在你更新IDL文件时再次导入它。

答案 3 :(得分:0)

你考虑过C ++ / CLI吗?在C ++ / CLI ref class中,您将编写一个成员函数:

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
}