从 C++ COM DLL 回调到 C# 应用程序

Posted

技术标签:

【中文标题】从 C++ COM DLL 回调到 C# 应用程序【英文标题】:Callback from a C++ COM DLL to a C# application 【发布时间】:2011-11-09 09:55:13 【问题描述】:

这将是一篇很长的文章,因为我想向您展示我尝试完成这项工作的所有步骤:)

我有 C++ COM dll,其中包含一个 VideoPlayer 类,该类使用 Media Foundation API 来显示视频。

VideoPlayer 类是使用 IDL 文件定义的:

[
    object,
    uuid(74FDBBB1-BFFB-4F7E-ACA3-ADB0C7232790),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IVideoPlayer : IDispatch 

    [id(1)] HRESULT Initialize([in] HWND* video_hwnd, [in] HWND* event_hwnd);
    [id(2)] HRESULT OpenUrl([in] BSTR url_path);
    [id(3)] HRESULT Play();
    [id(4)] HRESULT HandleEvent([in] INT pEventPtr);
    [id(5)] HRESULT Repaint(void);
    [id(6)] HRESULT Resize([in] LONG width, [in] LONG height);
;

此类在内部使用自定义演示器(基于 WPFMediaKit 项目),它在 IDirect3DSurface9 对象中输出视频帧。

自定义 Presenter 需要一个 IEVRPresenterCallback 类型的回调,定义如下:

MIDL_INTERFACE("B92D8991-6C42-4e51-B942-E61CB8696FCB")
IEVRPresenterCallback : public IUnknown

public:
    virtual HRESULT STDMETHODCALLTYPE PresentSurfaceCB(IDirect3DSurface9 *pSurface) = 0;
;

如您所见,它不是在 IDL 文件中定义的,而是在头文件中声明的。

我需要向 VideoPlayer 类添加一个新函数,该函数允许调用 C# 代码传递一个继承自 IEVRPresenterCallback 的类的实例,该实例将设置为自定义演示者。

我尝试将此行添加到 VideoPlayer 的 IDL 文件中:

[id(7)] HRESULT RegisterCallback2([in] IEVRPresenterCallback * p_PresenterCallback);

但我得到一个错误:

错误 MIDL2025:语法错误:期望类型规范接近 "IEVRPresenterCallback"

我想这很正常,因为我没有在 IDL 中导入任何内容。这是正常的,因为 IEVRPresenterCallback 是在头文件中定义的。

我尝试导入头文件,但是 IEVRPresenterCallback 定义的 MIDL_INTERFACE 宏产生错误:

错误 MIDL2025:语法错误:需要接口名称或 DispatchInterfaceName 或 CoclassName 或 ModuleName 或 LibraryName 或 ContractName 或“MIDL_INTERFACE”附近的类型规范

然后我尝试转发声明接口,但出现此错误:

错误 MIDL2011:未解析的类型声明:IEVRPresenterCallback [过程“RegisterCallback2”(接口“IVideoPlayer”)的参数“p_PresenterCallback”]

我最后一次尝试是更改 RegisterCallback 的定义,使用指向 IUnknown 而不是 IEVRPresenterCallback 的指针,并且在函数声明中,我将指针转换为正确的接口。

这使得 C++ dll 可以正确编译。

在 C# 应用程序中,我将回调设置如下:

[ComVisible(true), ComImport, SuppressUnmanagedCodeSecurity, Guid("B92D8991-6C42-4e51-B942-E61CB8696FCB"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IEVRPresenterCallback

    [PreserveSig]
    int PresentSurfaceCB(IntPtr pSurface);


internal class EVRPresenterCallback : IEVRPresenterCallback

    public int PresentSurfaceCB(IntPtr pSurface)
    
        return 0;
    


public partial class MainWindow : Window

    private EmideeMediaFoundationLib.IVideoPlayer videoPlayer = new EmideeMediaFoundationLib.VideoPlayer();
    private EVRPresenterCallback callback = new EVRPresenterCallback();

    public MainWindow()
    
        InitializeComponent();
    

    private void Button_Click(object sender, RoutedEventArgs e)
    
        videoHost.VideoPlayer.RegisterCallback(callback);
        videoHost.VideoPlayer.OpenUrl(@"C:\Users\Public\Videos\Sample Videos\wildlife.wmv");
    

我遇到的问题是,尽管自定义演示者调用了回调,但我再也没有回到 C# PresentSurfaceCB 函数中。

我现在完全卡住了,不知道问题出在哪里,也不知道如何解决:(

有什么想法吗?

提前致谢

【问题讨论】:

停止试图避免将接口放入 IDL,没有意义。你不能转换 COM 指针,你必须使用 QueryInterface()。 我试图在 IDL 中声明接口,但是我遇到了 ID3D9Surface 接口的问题...除非我将 IEVRCallback 的声明更改为使用 LONG 指针而不是 ID3D9Surface?我试试看。 【参考方案1】:

多亏了 Hans,我才能成功。

我在 IDL 文件中移动了接口,而不是在回调中返回一个 ID3D9Surface 指针,而是返回一个 DOWRD_PTR:

[
    uuid(B92D8991-6C42-4e51-B942-E61CB8696FCB),
]
interface IEVRPresenterCallback : IUnknown 
    [id(1)] HRESULT PresentSurfaceCB( DWORD_PTR pSurface);


[
    object,
    uuid(74FDBBB1-BFFB-4F7E-ACA3-ADB0C7232790),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IVideoPlayer : IDispatch 

    [id(1)] HRESULT Initialize([in] HWND* video_hwnd, [in] HWND* event_hwnd);
    [id(2)] HRESULT OpenUrl([in] BSTR url_path);
    [id(3)] HRESULT Play();
    [id(4)] HRESULT HandleEvent([in] INT pEventPtr);
    [id(5)] HRESULT Repaint(void);
    [id(6)] HRESULT Resize([in] LONG width, [in] LONG height);
    [id(7)] HRESULT RegisterCallback([in] IEVRPresenterCallback * p_PresenterCallback);
;

在我的 WPF 应用程序中,我创建了一个派生自 IEVRCallback 的类:

internal class EVRPresenterCallback : EmideeMediaFoundationLib.IEVRPresenterCallback

    public void PresentSurfaceCB(uint pSurface)
    
    

我将此实例提供给 VideoPlayer 对象。

【讨论】:

public void PresentSurfaceCB(uint pSurface) -- 这不应该是public int PresentSurfaceCB(uint pSurface),因为 PresentSurfaceCB 返回 HRESULT?我遇到的问题是,如果我将回调返回类型声明为 int 而不是 void,我在 x86 模式下会遇到访问冲突,但在 x64 下工作正常!

以上是关于从 C++ COM DLL 回调到 C# 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ 回调到 C#

在 C# 中找不到 C++ COM DLL 中的导出函数

如何从 C# 调用具有 void* 回调和对象参数的 C++ Dll 中的函数

C++ 回调函数到 C#

当结构仅在运行时已知时,将结构从 c++ 传递到 c#

创建可以从 c# 和 c++ 中使用的 c# dll