You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将PJSIP生成的SDL窗口HWND嵌入C# WPF应用?

解决方案:将SDL预览窗口HWND嵌入WPF应用

结合你这套pjsip+C++/CLI+WPF的架构,其实完全不用绕弯子把HWND转成string再封送——直接用IntPtr传递句柄是最稳妥的,然后借助WPF的HwndHost类就能完美嵌入Win32窗口(SDL窗口本质就是Win32窗口)。下面是具体步骤:

1. 优化C++/CLI桥接层的句柄封送

首先改掉你之前用stringstream转string的方式,在C++/CLI里直接把void*类型的HWND转换成.NET的System::IntPtr,这样WPF端能直接使用,避免字符串解析的风险:

// C++/CLI桥接库代码
#include <pjsua2.hpp>
using namespace System;

namespace PJSUABridge {
    public ref class VideoManager {
    public:
        IntPtr GetSDLPreviewWindowHandle() {
            // 假设你已经从pjsip获取到SDL窗口的HWND(void*类型)
            void* sdlHwnd = your_pjsip_instance->getVideoPreviewHwnd();
            // 直接转成IntPtr返回给WPF
            return IntPtr(sdlHwnd);
        }
    };
}

2. 自定义WPF控件托管Win32窗口

WPF原生不支持直接嵌入Win32窗口,需要继承HwndHost类来实现托管。创建一个自定义控件,负责把SDL窗口作为子窗口嵌入到WPF界面中:

// WPF项目中的C#代码
using System;
using System.Windows;
using System.Windows.Interop;

namespace YourWPFApp {
    public class SDLPreviewHost : HwndHost {
        private IntPtr _sdlHwnd;

        // 传入从桥接层拿到的SDL窗口句柄
        public SDLPreviewHost(IntPtr sdlHwnd) {
            _sdlHwnd = sdlHwnd;
        }

        protected override IntPtr BuildWindowCore(IntPtr hwndParent) {
            // 设置SDL窗口为WPF控件的子窗口
            SetParent(_sdlHwnd, hwndParent);
            
            // 去掉SDL窗口的标题栏、边框,让它完全嵌入
            SetWindowLongPtr(_sdlHwnd, GWL_STYLE, GetWindowLongPtr(_sdlHwnd, GWL_STYLE) & ~(WS_CAPTION | WS_THICKFRAME));
            
            // 返回SDL窗口句柄
            return _sdlHwnd;
        }

        protected override void DestroyWindowCore(IntPtr hwnd) {
            // 释放窗口资源时,解除父子关系并销毁SDL窗口(如果需要)
            SetParent(_sdlHwnd, IntPtr.Zero);
            // 这里可以调用pjsip的接口销毁SDL预览窗口
            // PJSUABridge.VideoManager.DestroyPreviewWindow();
        }

        // 重写OnRenderSizeChanged,同步SDL窗口大小和WPF控件大小
        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {
            base.OnRenderSizeChanged(sizeInfo);
            if (_sdlHwnd != IntPtr.Zero) {
                var newSize = sizeInfo.NewSize;
                SetWindowPos(_sdlHwnd, IntPtr.Zero, 0, 0, (int)newSize.Width, (int)newSize.Height, SWP_NOZORDER | SWP_NOACTIVATE);
            }
        }

        // 导入必要的Win32 API
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        private const int GWL_STYLE = -16;
        private const uint WS_CAPTION = 0x00C00000;
        private const uint WS_THICKFRAME = 0x00040000;
        private const uint SWP_NOZORDER = 0x0004;
        private const uint SWP_NOACTIVATE = 0x0010;
    }
}

3. 在WPF界面中使用自定义控件

在你的WPF窗口XAML里,先引入自定义控件的命名空间,然后添加一个容器(比如Grid)来放置SDL预览窗口,最后在代码后台初始化并添加自定义控件:

XAML部分

<Window x:Class="YourWPFApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:YourWPFApp"
        Title="Video Call" Height="600" Width="800">
    <Grid x:Name="PreviewContainer">
        <!-- 这里会嵌入SDL预览窗口 -->
    </Grid>
</Window>

代码后台部分

// MainWindow.xaml.cs
using System;
using System.Windows;
using PJSUABridge;

namespace YourWPFApp {
    public partial class MainWindow : Window {
        private VideoManager _videoManager;

        public MainWindow() {
            InitializeComponent();
            _videoManager = new VideoManager();
            
            // 打开捕获设备并获取SDL窗口句柄
            _videoManager.OpenCaptureDevice();
            IntPtr sdlHwnd = _videoManager.GetSDLPreviewWindowHandle();
            
            // 创建自定义控件并添加到容器
            var sdlPreview = new SDLPreviewHost(sdlHwnd);
            PreviewContainer.Children.Add(sdlPreview);
        }

        protected override void OnClosed(EventArgs e) {
            base.OnClosed(e);
            // 释放资源
            _videoManager.CloseCaptureDevice();
        }
    }
}

关键注意事项

  • 窗口样式调整:一定要去掉SDL窗口的标题栏和边框,否则嵌入后会有突兀的Win32窗口样式,破坏WPF界面的一致性。
  • 大小同步:必须重写OnRenderSizeChanged同步窗口大小,否则WPF控件缩放时SDL窗口不会跟着变。
  • 资源释放:在WPF窗口关闭时,要正确调用pjsip的接口销毁SDL预览窗口,避免内存泄漏。
  • 消息循环:确保SDL窗口能正常接收Win32消息,pjsip通常会处理这部分,但如果遇到卡顿或无响应,可以检查是否有消息阻塞的情况。

内容的提问来源于stack exchange,提问作者csguth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:20:43