我想使用 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++ 中,可以使用生成的特定文件,也可以#import
TLB 文件。在 .NET 中,可以在生成 .NET 程序集的 TLB 文件上运行tlbimp.exe
。
如果项目以 .NET 为中心,也可以使用tlbexp.exe
。但是,这将需要您了解 .NET COM 注释以及它们在 IDL 方面的含义,因此我不确定以另一种语言保存一个额外的源文件是否有任何好处,而代价是界面和类定义中的大量装饰噪音。如果您希望在源代码级别完全控制类和接口,并且希望使其在 .NET 代码上尽可能简单(读取、针对可用性和速度进行优化),这可能是一个不错的选择。
最后,您可以通过创建项目在Visual Studio中自动执行所有这些操作。如果使用 IDL 方法,请添加调用midl.exe
和tlbimp.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
}