3

我想使用 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

4 回答 4

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

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

谢谢。

于 2012-11-09T11:51:20.543 回答
2

由于SAFEARRAY已经编组到您的非托管代码,并且您正在使用 ATL,因此您可以使用CComSafeArrayclassSAFEARRAY ,因为它在使用实例时处理所有清理工作:

_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.
}
于 2012-11-08T16:52:18.437 回答
1

我强烈建议您创建一个IDL 文件设计您的 COM 接口

鉴于您在回答中的示例,一个相当小的 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.exeC/C++ 接口生成 .h、用于 C/C++ 链接的 CLSID 和 IID 常量的 _i.c、用于 RPC 注册的 dlldata.c、带有代理和存根编组的 _p.c 以及.tlb 通常是 .idl 文件的解析表示。这在文档中有更好的描述。

编辑:似乎没有办法避免 C/C++ 文件生成

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

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

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

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

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

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

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

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

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

于 2012-11-10T16:29:56.373 回答
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
}
于 2012-11-08T16:33:55.317 回答