Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例以及错误总结

ok,前面铺垫了那么多,现在来写一个开发实例,我会把其中隐藏的坑和陷阱简单谈谈,并在文章最后总结。

不愿意看长篇大论的可以直接看实例:CS_COM_Build

废话不多说直接起步。

先说场景,我这边是一个C#的DLL,然后让一个COM组件去加载这个DLL,然后再让Qt去调用这个C#的COM组件,也就是说有三个工程,如图所示:

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

其中FrameWork是一个窗体的DLL,如下图左边所示,右边是QtController的窗体,MiddleCOM则是充当了一个中间件,用于为普通的C#DLL提供COM服务。

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

如图所示,两个窗体之间可以进行交互,其中Qt应用程序是主程序,而C#的窗体程序则是以COM组件形式发布的。

一、写一个窗体

ok话不多说,先来写一个窗体:

using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FrameWork
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }
        //声明一个委托,可以向外部发送消息
        public delegate void SendMessageOutEventHandler(System.String strValue);
        public event SendMessageOutEventHandler SendMessageOut;

        private void button1_Click(object sender, EventArgs e)
        {
            this.SendMessageOut(this.textBox1.Text);
        }
        //接收消息,展示到窗体上
        public void getMessage(System.String strValue)
        {
            this.richTextBox1.AppendText(strValue);
        }
    }
}

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

二、写一个COM的中间件

现在我们来做套壳的COM组件。先添加一个C#的类库(这不会也要教吧)

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例
AssemblyInfo.cs中,讲这个[assembly:ComVisuble(false)] 改为true

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

右键这个COM工程,点击属性,找到为COM互操作注册

如果没有这一步,可能会导致Qt在调用的时候弹出报错提示CoCreateInstance failure(系统在找不到指定文件。),原因是如果没有互操作注册,在注册表中你去找到你的这个类,你会发现少了几行,比如BaseCode,就会导致COM在注册的时候找不到实际的代码文件,无法找到DLL文件进行加载。

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例
然后我们来写一下这个COM导出类,注意事项都在代码内,可以自己看看

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace MiddleCOM
{

    //Author:Leventure
    //DateTime:2022.12.22
    //Description:一个C# COM组件实例

    //注:C#中的类,无论是方法还是事件,都需要通过接口的形式向外公布,方法接口需要通过方法接口去提供服务,如果只是类内提供的函数或者事件,在外部可能无法正常使用。
    //注2:导出接口可以不需要设置ComVisible(true),这样可以使得导出的接口更加清晰
    //注3:这个Guid是唯一的,可以使用VS提供的Guid生成工具生成

    //方法接口,向外提供方法,注意[InterfaceType(ComInterfaceType.InterfaceIsDual)]的声明这样的接口是双向的
    [Guid("7EEDF2D8-836C-4294-90A0-7A144ADC93F9")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IOutClass
    {
        [DispId(1)]
        void getMessage(System.String strValue);
    }

    //事件接口,注意[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]这代表这个是用作事件处理
    [Guid("7FE32A1D-F239-45ad-8188-89738C6EDB6F")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IOutClass_Event
    {
        [DispId(11)]
        void SendMessageOut(System.String strValue);
    }

    [Guid("76BBA445-7554-4308-8487-322BAE955527")]
    [ClassInterface(ClassInterfaceType.None)] // 指示不为类生成类接口。如果未显式实现任何接口,则该类将只能通过 IDispatch 接口提供后期绑定访问。这是 System.Runtime.InteropServices.ClassInterfaceAttribute
                                              //     的推荐设置。要通过由类显式实现的接口来公开功能,唯一的方法是使用 ClassInterfaceType.None。
    [ComDefaultInterface(typeof(IOutClass))]        //     以指定的 System.Type 对象作为向 COM 公开的默认接口初始化 System.Runtime.InteropServices.ComDefaultInterfaceAttribute
                                                    //     类的新实例。
    [ComSourceInterfaces(typeof(IOutClass_Event))]  //使用要用作源接口的类型初始化 System.Runtime.InteropServices.ComSourceInterfacesAttribute 类的新实例。
    [ComVisible(true)] //提供COM的可访问性
    [ProgId("IOutClass")] //给这个导出类一个名称
    public class OutClass : IOutClass
    {
        private FrameWork.Form1 form1 = null;
        public OutClass()
        {
            if(form1 == null)
            {
                this.form1 = new FrameWork.Form1();
                this.form1.SendMessageOut += new FrameWork.Form1.SendMessageOutEventHandler(this.SendMessageReceived);
                this.form1.Show();
            }
        }
        //提供方法向C#DLL发送消息
        public void getMessage(System.String strValue)
        {
            if(this.form1 != null)
            {
                this.form1.getMessage(strValue);
            }
        }
        //这个是从方法类中继承来的向外发送消息事件
        public delegate void SendMessageEventHandler(System.String strValue);
        public event SendMessageEventHandler SendMessageOut;
        //从C#DLL中传来的消息,转发给COM服务器
        private void SendMessageReceived(System.String strValue)
        {
            //向外发送消息
            SendMessageOut(strValue);
        }
    }
}

ok,到这里我们的COM组件之旅几乎就已经完成了。编译这个DLL之后我们需要将其注册到我们的系统中去。这里.net的DLL和非托管的DLL的注册方式不一样。非托管的DLL注册可能是通过直接在cmd中输入regsvr32 xxx.dll进行注册,但是.net的DLL需要通过它.net自己的工具进行的注册,我们可以在菜单栏中找到vs的开发者工具,如图所示

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

(我这里是用的VS2019开发,其实这里用的只是regasm.exe这个工具,用哪个版本的无所谓)

调用命令如下:

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

注:如果你用的是系统提供的注册工具regsvr32注册的话,会提示你没有DllServerRegister入口点,请检查是否是dll或者ocx,这个是因为.net框架提供的COM组件是没有给定这两个东西的,所以需要用他们自己的工具!

OK,这个时候应该就已经注册完成了,为了检验成果我们可以去Windows系统注册表中查看,比如我们这里声明的导出类的名称为[ProgId(“IOutClass”)],就在注册表内查找 IOutClass,或者查找导出类对应的Guid,应该就能查找到对应的内容

比如这个路径下
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID{76BBA445-7554-4308-8487-322BAE955527}\ProgId

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例
如果出现了这一条,就说明你的注册成功了

三、编写Qt程序来调用COM组件

这部分内容比较简单,主要是提一下报错:

直接上界面和代码吧,这部分没有什么需要特别注意的:

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例
#pragma once

#include <qtwidgets qmainwindow>
#include "ui_QtController.h"
#include "qaxobject.h"
#include "qfile.h"
#include "qtextstream.h"
#include "qdebug.h"
class QtController : public QMainWindow
{
    Q_OBJECT

public:
    //&#x6784;&#x9020;&#x51FD;&#x6570;&#x91CC;&#x8C03;&#x7528;&#x4E86;&#x8FD9;&#x4E2A;Init&#xFF0C;&#x61D2;&#x5F97;&#x5199;&#x4E86;&#xFF0C;&#x5C06;&#x5C31;&#x7740;&#x770B;&#x5427;
    QtController(QWidget *parent = nullptr);
    ~QtController();
    QAxObject ax_test;
    void Init() {
        this->ax_test.setControl("IOutClass");
        //&#x83B7;&#x53D6;&#x63A5;&#x53E3;&#x6587;&#x6863;
        QString interfaces = ax_test.generateDocumentation();
        QFile docs("AX_Interfaces.html");
        docs.open(QIODevice::ReadWrite | QIODevice::Text);
        QTextStream TS(&docs);
        TS << interfaces << endl;

        qDebug() << QObject::connect(&this->ax_test, SIGNAL(SendMessageOut(QString)), this, SLOT(getMessageFromCS(QString)));
    }

private slots:
    void on_pushButton_clicked() {
        this->ax_test.dynamicCall("getMessage(QString)", this->ui.lineEdit->text());
    }
    void getMessageFromCS(QString strValue) {

            this->ui.textEdit->append(strValue);

    }
private:
    Ui::QtControllerClass ui;
};
</qtwidgets>

这样基本上就能保证调用了,这里提几个BUG,也是我们困扰了很久的地方:(凭借记忆写的,不一定全对哈)

四、查漏补缺、总结:

1.调用时提示 No Such Property、或者如图所示:

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

答:就我们目前的经验来看,有可能是你的COM组件中提供的属性有问题,你依赖了其他的DLL,但是这个被依赖的DLL它的依赖可能没被加载,也就是说缺少了部分依赖,需要你自己补全所有依赖。

2.调用时报错UnKnownError,如图:

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

答:这有可能是因为被调用的COM组件的位数大于调用方的位数导致的,比如32位的应用程序调用64位的进程,可能会导致UnKnown Error。

3.报错提示CoCreateInstance failure(系统找不到指定的文件。)

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

答:有可能是由于你在编写COM组件的时候没有在COM组件的属性中勾选上COM互操作选项,导致注册表内BaseCode行未注册成功。

4.注册DLL时,显示找不到入口点DllRegisterServer

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

答:需要用.net提供的COM注册服务软件regasm,具体使用方法见上方。

5.注册的C# COM 组件,为什么Event变成了 add_xxx(IDispatch value) 和remove_xxx(IDispatch value) 了?(注:xxx是事件的名称)

答:事件需要通过继承事件接口来继承暴露,函数需要通过函数接口来继承暴露,这样会变成另外一种形式,我没用过,我不能确保能不能用。

还有什么问题可以提问,我看到了就会回答,如果需要私聊可以联系我的Github提交issue

Original: https://www.cnblogs.com/Leventure/p/16998111.html
Author: 轩先生。
Title: Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/796239/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Mysql索引(究极无敌细节版)

    &#x53C2;&#x8003;&#x4E86;&#xFF1A; https://www.jianshu.com/p/ace3cd6526c4 &a…

    Python 2023年10月18日
    037
  • QQ机器人go-cqhttp保姆级配置与编程

    啊哦~你想找的内容离你而去了哦 内容不存在,可能为如下原因导致: ① 内容还在审核中 ② 内容以前存在,但是由于不符合新 的规定而被删除 ③ 内容地址错误 ④ 作者删除了内容。 可…

    Python 2023年8月9日
    048
  • PyTorch 基础

    import torch import numpy as np 定义一个三行两列给定元素的矩阵,并且显示出矩阵的元素和大小 torch.Tensor默认的是torch.FloatT…

    Python 2023年8月26日
    047
  • 浅谈python接口自动化测试框架Pytest

    Pytest作为一种自动化框架,由于是由unittest框架继承而来,因此它的易用性和扩展性比较好,并且向下兼容unittest,成为目前主流的测试框架之一原因如下: 简单灵活,容…

    Python 2023年9月9日
    035
  • 从Deepmind最新成果DreamerV3启发的通用AI技术分析

    一、背景 本文系个人观点:错漏在所难免,仅供参考 北京时间 1 月 12 日,DeepMind 官方推特发文,正式官宣 DreamerV3,这是首个能在游戏「我的世界」(Minec…

    Python 2023年11月6日
    032
  • 1. 科研绘图之 matplotlib 基本语法

    matplotlib 基本功能 matplotlib 是 Python 的一个绘图库,使用它可以很方便地绘制出版质量级别的图形图片。本节主要介绍的是 matplotlib 的基本绘…

    Python 2023年9月4日
    067
  • 前端交互、前后端交互、数据格式转换基础相关知识

    目录 前端交互简单基础 前后端交互、数据格式转换 快速上手第一个flask应用小应用 例2flask应用:将当前目录下的文件(文件或者文件夹)以文本形式显示出来 数据格式转换 前端…

    Python 2023年8月11日
    054
  • python游戏房间_python游戏中的房间/屏幕/菜单控制器的问题:旧房间不会从内存中删除…

    我的头撞到了墙上(就像,是的,身体上,在我现在的位置,我正在破坏我的头盖骨)。基本上,我有一个Python/Pygame游戏,有一些典型的游戏”房间”或&#…

    Python 2023年9月24日
    044
  • 快速替换dll命名空间

    时15年9月18日,闲来无事,更一博。 背景 三天前,Y公司为避免法律诉讼,需要将代码(包括dll)中有关老东家的命名空间全部改掉。现在我就将快速替换命名空间的方法一步步告诉大家,…

    Python 2023年6月12日
    069
  • Linux常用内核参数

    参数描述net.core.rmem_default默认的TCP数据接收窗口大小(字节)。net.core.rmem_max最大的TCP数据接收窗口(字节)。net.core.wme…

    Python 2023年9月26日
    045
  • 上线流程

    上线流程 上线前准备 首先将跑在本地版本的项目,上传至远端(gitee、github上) 重新复制一份项目的配置文件,可以命名为pro.py(dev为开发阶段的配置文件,pro为上…

    Python 2023年10月30日
    047
  • pandas基础入门之数据修改与基本运算

    *直接赋值, 直接赋值的话,只是复制的元数据(行列索引),但是元素还是存储在相同内存位置 对元素进行修改会影响另外一个。 import pandas as pd import nu…

    Python 2023年8月18日
    067
  • python图例显示中文_解决matplotlib图例中文乱码问题

    在 matplotlib图例中文乱码? 中看到了很多人的回答,但尝试了之后都发现有问题。经过自己的摸索发现了一个简单的方式。 Windows 10 + Python 3.6 安装字…

    Python 2023年9月4日
    028
  • 瑞吉外卖实战项目全攻略——第二天

    瑞吉外卖实战项目全攻略——第二天 该系列将记录一份完整的实战项目的完成过程,该篇属于第二天 案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容 该篇我们…

    Python 2023年10月18日
    057
  • 动手学数据分析 01

    课程现分为三个单元,大致可以分为:数据基础操作,数据清洗与重构,建模和评估。 1 第一章:数据载入及初步观察 第一部分:我们获得一个要分析的数据,我要学会如何加载数据,查看数据,然…

    Python 2023年8月7日
    046
  • 4-8 Matplotlib库 雷达图

    Matplobilb库数据分析常用图 1. 雷达图 2. 使用 plt.polar 绘制雷达图 3. 使用子图绘制雷达图 3.1 使用 plt.subplot 绘制子图 3.2 使…

    Python 2023年9月2日
    061
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球