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



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

谁能给我一些建议,如何使这个东西工作?

谢谢。

由于SAFEARRAY已封送到非托管代码,并且您使用的是 ATL,因此您可以使用CComSafeArray类,因为它在使用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.
}

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

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

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

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

谢谢。

我强烈建议您创建一个 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.exe生成具有 C/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 中,可以在生成 .NET 程序集的 TLB 文件上运行tlbimp.exe

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

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

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

编辑2:或使用上述nul解决方法。

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

您是否考虑过 C++/CLI?在 C++/CLIref 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
}

相关内容

  • 没有找到相关文章

最新更新