C# WPF调用 QT窗口

WPF 程序内嵌 QT 窗体

1、目标:将QT控件(Qwiget)(或则基于QWiget的控件)(或则任何第三方C++控件)封装为WPF可调用的用户控件。简单来说就是WPF程序调用QT窗体控件。

本人需要使用3D控件显示一些3D点云等功能。但是又找不到好的兼容WPF的控件(大点云和效率原因)。最后选用CloudConpare作为点云显示控件(该控件基于QT的QWiget)。
目前网络上这方面的内容并不是太多,而且多数还都是雷同。
个人感觉 C# 跨平台并不是太友好(或则我水平不够),特别是3D方面。

本人对QT不是很熟悉,但是CloudCompare是基于QT写的界面,所以没办法只能慢慢摸索,好在以前有MFC编程经验,对C++还有点积累,过程中关于配置方面的问题我尽量详细说明(对于C#编程者来说还是挺恶心的,QT的配置和CloudCompare的配置挺麻烦的)。

实现过程如下(可能需要稍微有点基础才能看懂,我认为有意思的会写多点 ,写的不是太系统),可能有点乱,有点啰嗦,想到哪里写到哪,见谅。

心路:
一开始查询资料想通过中间库 CLR链接C#和C++,简单demo也测试OK,后期碰到控件句柄传递问题调试也比较麻烦,遂放弃(现在想来应该也是可以实现的)。
最终直接使用 C形式函数导出 的形式来创建DLL,也挺方便的。专业点叫P/Invoke(推荐大家一本书《精通.NET互操作:P/Invoke,C++Interop和COM Interop》)。

大概逻辑:WPF控件句柄–>传递给C++生成的QT库–>动态库根据传递过来的句柄创建一个QWiget–>根据这个QWiget生成一个GLWindow(真实的3D显示窗体)–>C#保存这个指针方便下次访问。
数据传逻辑:主要是C#这边发给C++ Dll数据,主要涉及到托管和非托管资源的问题、字节对齐等。

实现过程中可能有几个主要问题:
1、C#和C++混合编程;
2、QT程序(QT事件循环QApplication.exec())如何封装为Dll;
3、QT的UI线程和WPF的UI线程冲突问题(目前本人也没完全弄懂,欢迎沟通);

工具:VS2019(Qt Visual Studio Tool 2.7.0)、QT5.14.2、CloudCompare2.6.12源码。。。。

上代码:
1、创建一个VS-QT的动态库。
选择Qtwidgets application,然后修改配置生成为dll。网上资料很多,不详细描述(大概流程:修改输出类型为dll,将没必要的文件删除即可(Main)(所有文件都可以删除,然后新增一个QtWidgets class也行))。如下:
在这里插入图片描述

我们这里界面就不需要设置(因为我们不是直接用QT界面),只需要一个空白的Qwidget。(同志们只需要用Qwiget就可以在这里绘制了)
右键 编译这个.ui文件。会生成一个ui_xxxx.h文件。添加这个文件到项目中,(可能在生成目录的uic文件夹下,如有必要还需要将这个文件路径加到包含目录中。若是不添加可能会生成失败)。
首次生成失败很正常,各种QT环境问题。不要心急。
在这里插入图片描述
这里我们就获得了一个带QT的界面的C++库。
2、WPF中引用它:
若是你们用的是Winform,那就比较方便了,直接拿到控件句柄(我这里使用的panel控件)传递给动态库就行了。
若是你用的是WPF,会麻烦一点(因为WPF控件获取不到“控件”句柄),这里在下试了很多,获得的都是窗体句柄。所以只能使用WindowsFormsHost。上代码
在这里插入图片描述
这里是本人在WPF创建了一个用户控件的xaml文件。上代码

public partial class Window3DControl : UserControl
    {
        public Window3DControl()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 3D显示 窗口
        /// </summary>
        public Window3DControl_Net Control3D = new Window3DControl_Net();


        /// <summary>
        /// 创建真实的3D窗体,GlWindow
        /// </summary>
        /// <returns></returns>
        public bool Create3DWindow()
        {
            int res = Control3D.Creat3DWindow_Net((long)Panel3D.Handle, Panel3D.Width, Panel3D.Height);

            return res == 0;
        }

        /// <summary>
        /// 控件发生变化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window3DControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if(Control3D.IsInitWindow)
            {
                int width = (int)(WindowForm3D.ActualWidth * 1.25);
                int Heigth = (int)(WindowForm3D.ActualHeight * 1.25);
                int res = Control3D.WindowSizeChanged_Net(width, Heigth);
            }
        }

        /// <summary>
        /// 3D窗体切换
        /// </summary>
        public void ChangeWindowModel()
        {
            if(WindowForm3D.Visibility == Visibility.Visible)
            {
                WindowForm3D.Visibility = Visibility.Collapsed;
                Button2D.Visibility = Visibility.Visible;
            }
            else
            {
                WindowForm3D.Visibility = Visibility.Visible;
                Button2D.Visibility = Visibility.Collapsed;
            }
        }

    }

上面是用户控件的代码部分。也很简单只有几个简单功能。其中Control3D对象是我封装的一个调用动态库的接口类(只是封装了一层),功能上直接调用C形式的函数接口也是可以的。上代码
在这里插入图片描述
这里是封装的3D对象的部分接口,C#这边就很简单了,都封装成用户控件了,那不是想怎么玩怎么玩。(关于C#定义函数接口就不描述了,C++那边导出函数接口也不描述了)。上代码
在这里插入图片描述
还有一点很重要(我感觉)如何显示QT窗口正好覆盖WPF中句柄对应的控件(可能会碰到直接显示QT窗口在主界面的左上角)。有两步很重要,通过如上图,可以看到我们将控件句柄和对应的尺寸传递过去,并且将生成的QT窗口的实例对应的指针传回来,为了下次能够直接拿到这个QT窗口。可以看下我的创建窗口函数是如何实现的,上代码
在这里插入图片描述
图中标记部分,很重要 。(忘记从哪个大神哪里偷学来的)
另外为了C# 那边能拿到C++这边的对象指针。使用了二级指针概念(这个不太好解释,要自己细细理解,)。
还有一个问题QT事件循环,(若是你调用过QT的dll,可能就会发现这个问题),我这边解决办法就是,在程序开始(你认为的合适的地方)调用InitQTEnvironment接口,上代码。

#include "qmfcapp.h"
int InitQtEnvironment()
{
	try
	{
		//if (QApplication::instance() == NULL)
		//{
		//	int argc = 0;
		//	g_app = new QApplication(argc, NULL);
		//	//QWidget* GroudWidget = new QWidget(NULL);
		//	//GroudWidget->setGeometry(0, 0,0, 1);//设置widget的大小
		//	//GroudWidget->showMinimized();
		//	//g_app->exec();
		//}
		if (QApplication::instance() == NULL)
		{
			return  QMfcApp::pluginInstance();
		}
		return 0;
	}
	catch(QException ex )
	{
		printf("Init Error");

		printf(ex.what());
		return -1;
	}
	
}

两种方式都行,目的是创建出来QApplication这个静态对象即可。(其中QMfcApp是偷学某个大佬)。
OK,其实到这里整个流程已经基本打通了,路走通了,其余的都好办了。
关于数据传递问题,这里也说一下。C#和C++的基础数据类型在内存中占位不尽相同,有兴趣可以看下对照表(网上一大堆)。
若是使用CLR包装C++dll,可以解决类型不一致问题(个人感觉CLR就是一次形参类型转换而已,可能是我太浅薄了)。
若是我们这种直接调用C形式的函数接口。需要传递参数需要注意几点。
1、基础类型也需要注意内存占位是否一致。
2、结构体类型,需要考虑字节对齐和托管资源问题。思路:两边定义相同类型的结构体。直接传递结构体指针(首地址)本质上还是内存要一样,不能乱。先上代码(C++)
在这里插入图片描述
C#中数据结构
![在这里插入图片描述](https://img-blog.csdnimg.cn/62431497e42241c18b2f6bb4bd98f5a3.png
仔细对比(字节对齐,还要考虑编译器可能优化)。
数组必须确定传递长度(内存分配才能连续),并且C#这边申请的内存必须是非托管内存(使用Marsh申请)。关于C++传递数据到C#,上文也提到了二级指针,基础类型或确定内存长度的可以直接传递,不确定的对象使用二级指针。关于内存释放问题。大家可以网上找下,有几种常用方法,此处不赘述。

按照惯例,上效果图。
在这里插入图片描述
遗留问题:
玩过WPF的都知道,WPF有一个隐藏的UI线程,我们的属性绑定控件和刷线界面都是
这个线程来更新的。QT也有一个类似的界面线程。因为我这边主程序是WPF,QWiget只是作为一个控件来用的。
目前发现他们两个界面线程会出现抢资源情况(我也是猜的,因为出现WPF的源已经改变了但是目标控件上没有刷新(及时刷新)的现象)。各位大佬若有这方面的见解,请一定联系我。抱拳!!!

写的不妥的地方,请大佬们不吝赐教,再次抱拳!!!!

<think>好的,我现在要解决用户的问题,即如何确保C#Qt的不同版本之间功能的一致性和兼容性。首先,我需要理解用户的需求。他们可能在开发一个跨平台项目,同时使用C#Qt,或者维护两个不同技术栈的项目,需要保持功能同步。接下来,我需要考虑版本管理、API兼容性、测试方法等方面。 首先,版本控制是关键。无论是C#的.NET版本还是Qt的版本,都需要使用版本控制工具,比如Git,来管理代码。为每个版本创建独立的分支,这样可以隔离不同版本的开发,避免冲突。另外,语义化版本控制(SemVer)可以帮助明确版本的变化,比如主版本号变化表示不兼容的API更改,次版本号表示向后兼容的功能增加,修订号表示问题修复。这样团队可以快速识别兼容性风险。 接下来是API抽象层。为跨平台或跨语言的功能设计统一的接口,例如使用适配器模式或门面模式。通过创建中间层,将核心逻辑与平台相关代码分离,这样在QtC#版本更新时,只需调整适配层,而不影响核心功能。同时,使用依赖注入来管理不同版本的依赖,确保各版本可以灵活切换实现。 自动化测试也是确保兼容性的重要手段。单元测试应该覆盖所有核心功能,每次版本更新后运行测试,验证兼容性。跨版本对比测试可以在不同版本间运行相同测试用例,检测行为差异。此外,持续集成(CI)系统如Jenkins或GitHub Actions可以自动化构建和测试流程,确保每次提交都经过验证。 文档管理同样重要。维护详细的变更日志,记录每个版本的API变动、新增功能和废弃内容。使用工具如Swagger生成API文档,保持文档与代码同步。团队应定期进行跨技术栈的代码审查,确保功能实现的一致性。 在依赖管理方面,C#可以使用NuGet包管理,而Qt可以使用vcpkg或Conan。锁定依赖版本,避免自动升级导致的不兼容。例如,在C#项目中指定.NET SDK的固定版本,Qt项目中使用特定版本的模块,避免使用已弃用的API。 关于跨语言通信,如果项目需要C#Qt组件交互,可以使用进程间通信(IPC)如gRPC或REST API,定义清晰的协议缓冲区或API规范。或者使用本地桥接技术,如C++/CLI桥接QtC#,但需注意版本兼容性。 最后,用户教育和回滚策略也很关键。培训团队成员遵循版本管理规范,建立快速回滚机制,当新版本出现兼容性问题时能迅速恢复。 总结以上方法,需要结合版本控制、抽象层设计、自动化测试、文档管理和依赖管理等多方面措施,才能有效确保C#Qt不同版本间的功能一致性和兼容性。同时,实际案例和最佳实践能帮助用户更好地理解和应用这些策略。</think>### C#Qt版本间功能同步的方法及最佳实践 为确保C#Qt不同版本之间的功能一致性和兼容性,需从代码管理、版本控制、测试策略等多方面入手。以下是系统化的解决方案: --- #### 一、版本管理策略 1. **语义化版本控制(SemVer)** - 对C#项目(如.NET SDK)和Qt框架均采用`主版本.次版本.修订号`格式。例如: - Qt 6.5.1:主版本6(重大更新),次版本5(功能增强),修订号1(问题修复) - .NET 7.0.3:主版本7,次版本0,修订号3 - 通过版本号明确标识兼容性:主版本变更表示存在不兼容的API变动[^1]。 2. **分支隔离开发** - 为每个主要版本创建独立Git分支(如`qt-v5`、`dotnet-v6`),避免代码污染。 - 使用`git cherry-pick`或`rebase`跨分支同步关键功能修复。 --- #### 二、API兼容性保障 1. **抽象层设计** - **适配器模式**:为跨平台功能(如文件操作、网络请求)定义统一接口,隔离Qt/C#的实现差异。 ```csharp // C#示例:文件系统接口 public interface IFileSystem { string ReadFile(string path); } // Qt适配器实现(通过C++/CLI桥接) public class QtFileSystem : IFileSystem { public string ReadFile(string path) { QFile file = new QFile(path); // Qt实现代码... } } ``` - **门面模式**:封装复杂子系统,例如将Qt Quick界面逻辑与C#业务逻辑解耦[^1]。 2. **依赖注入容器** - 使用Autofac(C#)或Qt IOC容器管理版本相关依赖: ```csharp // 注册不同版本的实现 builder.RegisterType<QtFileSystem_v6>().As<IFileSystem>(); ``` --- #### 三、自动化测试体系 1. **跨版本单元测试** - 使用NUnit(C#)和QTest(Qt)编写核心功能测试用例,覆盖以下场景: ```python # 伪代码示例:文件读取功能测试 def test_file_read(): assert read("test.txt") == "expected_content" ``` - 通过CI/CD工具(如GitHub Actions)在多个版本环境中并行运行测试[^3]。 2. **行为驱动开发(BDD)** - 使用SpecFlow(C#)和Cucumber定义跨语言功能规范: ```gherkin Scenario: 用户登录 Given 输入用户名 "test" When 点击登录按钮 Then 显示主页 ``` --- #### 四、文档与协作实践 1. **变更日志(Changelog)** - 维护`CHANGELOG.md`,记录版本间API变动: ```markdown ## Qt 6.5.1 (2023-10-01) ### 废弃 - `QWidget::showFullScreen()` 被 `QWindow::setVisibility()` 替代 ``` 2. **跨技术栈代码审查** - 使用Phabricator或GitLab Merge Request,确保C#Qt实现逻辑等价。例如: - C#的`System.IO.Ports.SerialPort`与Qt的`QSerialPort`需保证相同的超时处理机制。 --- #### 五、依赖管理工具 | 技术栈 | 工具 | 示例配置 | |--------|---------------|------------------------------| | C# | NuGet | `<PackageReference Version="7.0.0" />` | | Qt | vcpkg/Conan | `qtcore 6.5.1#branch=stable` | --- #### 六、实战案例:歌词编辑器同步开发 假设需同步开发C#版(引用[2])和Qt Quick版歌词工具: 1. **核心逻辑共享** - 将时间同步算法封装为C++库,通过C# P/Invoke和Qt C++调用。 2. **UI层隔离** - C#使用WinForms/WPFQt使用QML,通过共享JSON配置文件定义歌词格式。 ---
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值