QT 开发COM(ActiveX)组件基础介绍和方案验证

一、COM简介

1.1 COM是什么?

COM,Component Object Model,即组件对象模型,是一种以组件为发布单元的对象模型,这种模型使各软件组件可以用一种统一的方式进行交互。COM 既提供了组件之间进行交互的规范,也提供了实现交互的环境,因为组件对象之间交互的规范不依赖于任何特定的语言,所以,COM也可以是不同语言协作开发的一种标准。

COM实际上是一种组件标准,COM不仅仅提供了组件之间的接口标准,它还引入了面向对象的思维。在COM标准中,对象是一个非常活跃的元素,常常被称为COM对象。组件模块为COM对象提供了活动的空间,COM对象以接口的方式提供服务,这种接口就被称为COM接口。COM组件、COM对象、COM接口三者的关系如下图所示:

在Windows系统平台上,一个COM组件可以是一个DLL(Dynamic Linking Library,动态链接库)文件,也可以是一个EXE(可执行程序)文件。一个组件程序可以包含多个COM对象,并且每个COM对象可以实现多个接口。

当另外的组件或者普通程序(即组件的客户程序)调用组件的功能时,它首先创建一个COM对象或者通过其他途径获得COM对象,然后通过该对象所实现的COM接口调用它所提供的服务。当所有的服务结束之后,如果客户程序不再需要该COM对象,那么它就应该释放掉对象所占用的资源,包括对象自身。

1.2 COM接口与API之间的区别

COM接口和经常说的API有点相似。通过API接口层,可以很好地把两个程序连接起来,但存在一些问题:1)、当API非常多时,使用会非常不方便,需要对函数进行组织。2)、API函数需要标准化,按照统一的调用方式进行处理,以适应不同编程语言的实现,包括参数传递顺序、参数类型、函数返回处理都需要标准化。而COM定义了一套完整的接口规范,不仅可以弥补以上API作为组件接口的补足,还充分发挥了组件对象的优势,并实现了组件对象的多态性。

1.3 .NET组件和COM组件的区别

.NET组件和COM组件之间的主要区别在于它们的设计目标、实现方式和运行环境。.NET组件是微软推出的新一代编程模型,用于构建Web应用、桌面应用和移动应用;而COM组件是Windows操作系统中基于二进制代码通信的机制,主要用于实现Windows系统中的各种组件之间的互操作。.NET组件和COM组件在实现方式、编程语言和运行环境上有所不同。

实现方式:

.NET组件:通过C#、VB.NET等.NET编程语言编写,以.NET框架为基础,运行在.NET运行时(CLR)上。

COM组件:通过C++、VB6等编程语言编写,以COM为基础,运行在COM运行时上。

编程语言:

.NET组件:使用C#、VB.NET等.NET编程语言编写,可以跨平台运行。

COM组件:使用C++、VB6等编程语言编写,只能在Windows操作系统中运行。

运行环境:

.NET组件:运行在.NET运行时(CLR)上,支持多语言、跨平台、面向对象和类型安全等功能。

COM组件:运行在COM运行时上,支持多语言、跨平台、面向对象和类型安全等功能。

生命周期:

.NET组件:具有短暂的生命周期,一旦被加载到内存中,就可以立即运行。

COM组件:具有较长的生命周期,需要经过加载、注册、卸载等步骤,需要更多的手动管理。

安全性:

.NET组件:提供了内存管理和类型安全等功能,可以避免缓冲区溢出等安全问题。

COM组件:由于手动管理,容易出现缓冲区溢出等安全问题。

总的来说,.NET组件和COM组件在设计目标、实现方式和运行环境上有所不同,但它们都是用于构建Windows应用程序的组件化编程模型。

因此COM组件并不依赖于.NET Framework的运行环境。

但两者之间是可以相互调用,见如下文章:

COM 互操作示例:.NET 客户端和 COM 服务器

COM 互操作示例:.NET 客户端和 COM 服务器 - .NET Framework | Microsoft Learn

1.4 类厂

在创建组件对象时,客户程序调用COM库中的函数进行组件对象的创建工作,COM库的创建函数根据注册表的信息并调用组件程序的入口函数来创建组件对象。所以组件程序需要提供一个标准的入口函数 DllGetObjectClass 函数,用于提供本组件的组件信息。而在 DllGetObjectClass 中,是以类厂的方式获取组件对象的。

类厂,顾名思义,就是COM类的工厂。如果对C++比较熟悉的话,应该会知道设计模式中的工厂设计模式,其实这个类厂的概念就和工厂设计模式很相似。确切的说,类厂应该成为“对象厂”,因为类厂是COM对象的生产基地,COM库通过类厂创建COM对象;COM规定,每一个COM类,对应的都要有一个类厂专门用于该COM类的对象的创建工作。

如果一个组件程序实现了多个COM对象类,则相应的有多个类厂。所以,上述关于字典组件的结构、和多个类厂的结构就如下所示:

1.5 COM库

COM除了定义了组件程序和客户程序交互的规范以外,它也提供了COM的实现部分即COM库,使得这些规范能够真正地应用起来。并且COM库也充当了组件程序和客户程序之间的桥梁,尤其是在组件对象的创建过程中,以及在对象管理、内存管理和一些标准化操作方面起着重要的作用。

COM库的一些常用函数:

客户程序调用COM库创建组件对象的顺序图:

1.6 COM实现过程

COM客户程序、COM库、COM组件程序三者之间的协作过程

1.7 QTActiveX介绍

Qt提供了QtActiveX模块来支持微软ActiveX的开发,Qt的ActiveX和COM的开发支持两种方式:

支持将已有的COM或者ActiveX空间引入到Qt的应用程序中

支持将Qt应用程序或者Qt的对象导出成COM对象或者ActiveX控件供他人使用

具体来说,Qt是通过ActiveXQt框架中的两个模块来支持上述所说的两种方式的:

使用QAxContainer模块,通过QAxObject和QAxWidget分别支持COM对象和ActiveX控件的开发,可以通过这两个对象将外部的COM或者ActiveX组件接入到Qt应用程序

使用QAxServer模块,通过QAxAggregated、QAxBindable和QAxFactory类,通过了进程内和可执行程序exe两种方式的COM Server模式,用来将Qt写的内容导出为COM或者ActiveX供他人使用。

二、基于VS+QT开发Com组件

Qt的windows商业版本提供了ActiveQt这个framework,使用这个组件我们可以在Qt中使用ActiveX控件,并且也开发基于Qt的ActiveX控件。

开源版本是没有的,需要依赖于VS的QT插件来做开发。

2.1 环境配置

2.1.1 VS+QT+vsaddin插件安装

操作参考:

QT - QT中配置MSVC编译环境 以及 VS中配置QT开发环境_qt msvc-CSDN博客

本次最终采用QT6.7 + VS2022版本。

2.1.2 安装相关问题

1.QT安装速度提升,避免各种网络超时报错:

安装QT时,更换镜像源,以带参数的方式启动:

.\qt-unified-windows-x64-4.4.2-online --mirror https://mirrors.tuna.tsinghua.edu.cn/qt

2.VS、QT、MSVC、qtaddin版本对应问题

下载qtaddin插件,与对应VS20xx版本对应即可。

QT5.12.12不支持MSVC2019,最高支持到MSVC2017

QT5.15.2可支持到MSVC2019,但是当前没有离线包的版本,在线安装也不支持;

QT6.x可支持到MSVC2022

MSVC20xx 一般要与对应的VS20xx相对应(参考的两篇文章分别都是对应的)

因此QT所支持的MSVC版本,一般需要跟VS20xx对应起来。

网上成功的:

QT5.15.2 + MSVC 2019 + VS2019

QT5.14.2 + MSVC 2017 + VS2017 

自测:

QT6.7 + MSVC2019 + VS2019  ,新建activex项目,点击生成各种报错,未找到有效解决方案;

QT5.14.2 + MSVC2017 + VS2017,但VS 2017的winform项目,看不到生成的com组件……

QT5.14.2 + MSVC2017 + VS2017(负责生成COM) + VS2022(C#负责调用com),预览界面可以看到组件UI,实际运行显示不了;并且VS2017生成com有不稳定的情况,后续编译不了了……

最终测试版本成功:

QT6.7 + MSVC2019 + VS2022 + QTaddin3.2

注意点:先用IE模式,用html测试生成的Activex控件可用,随后再用winform项目做测试,会好一些。

3. idl报错,生成出现错误“MSB3073”  

  需要使用管理员权限打开VS202软件。

4.ActiveQt/QAxBindable 找不到源文件

安装QT时,ActiveQT组件一定要安装,不然会出现项目找不到active相关头文件的问题:

要修改已有的qt组件,运行QT安装目录下的工具即可:

MaintenanceTool.exe

5."QtWidgets/QWidget”找不到源文件

VS2019 + Qt5.12 配置完成后,无法打开 Qt 源文件解决方案(非常实用)

VS2019 + Qt5.12 配置完成后,无法打开 Qt 源文件解决方案(非常实用)_无法打开qbuttongroup源文件-CSDN博客

2.2 COM(ActiveX)组件开发

QT - QT中的COM编程(dll进程内组件形式)

QT - QT中的COM编程(dll进程内组件形式)_qt com组件-CSDN博客

2.2.1 实际代码

ActiveQtServer1.h
#pragma once#include <QtWidgets/QWidget>
#include <ActiveQt/QAxBindable>#include "ui_ActiveQtServer1.h"class ActiveQtServer1 : public QWidget, public QAxBindable
{Q_OBJECTpublic:ActiveQtServer1(QWidget *parent = nullptr);public slots://定义两个槽函数,便于外部调用QString getVersion();QString getCurrentTime();private:Ui::ActiveQtServer1Class ui;
};
ActiveQtServer1.cpp
#include "ActiveQtServer1.h"
#include <ActiveQt/QAxFactory>ActiveQtServer1::ActiveQtServer1(QWidget *parent): QWidget(parent)
{ui.setupUi(this);
}
QAXFACTORY_DEFAULT(ActiveQtServer1,"{c5e4017e-73a4-47c2-ad5d-aba20c13a6ba}","{4c7d8024-69c9-4377-8a73-f163a00ad8d8}","{c46481e5-2702-476c-9cb2-e8dca9a23a47}","{484c7403-d8fa-4d7d-ac12-b75f20d6e60b}","{a3dd71cf-f57e-49ef-a6c3-939b4d2e7339}"
)
QString ActiveQtServer1::getVersion() {return "0.0.1";
}
QString ActiveQtServer1::getCurrentTime() {return ui.calendarWidget->selectedDate().toString();
}

2.2.2 生成dll

需要以管理员模式运行VS,才能够正常生成和注册:

2.2.3 发布(需要通过windeploy发布依赖的文件

 D:\Qt\Qt5.12.12\5.12.12\winrt_armv7_msvc2017\bin\windeployqt.exe .\ActiveQtServer1.dll

2.2.3 IE模式 Html测试

从注册表查询classid

编写html文件,替换classid,保存到本地(可以任一目录)

<html>
<head>
<title>activeQtDemo</title>
</head>
<body><object id="233432" width="80%" height="80%"classid="CLSID:869BDCDE-E935-432D-AC52-F66C8F1D27DD"> <PARAM NAME="_Version" VALUE="65536"><!-- 以下为入坑了 --><!--    classid="2F12BFB8-137D-4DC2-9A93-634EFE5A6DFC">  1D991CF8-6F9D-4574-9507-B526D699F4321D991CF8-6F9D-4574-9507-B526D699F432-->  [Object not available! Did you forget to build and register the server?]</object>
</body>
</html>

edge浏览器配置白名单,支持IE模式(需要支持Activex的浏览器)

通过IE浏览器打开:

点击允许加载插件

2.2.4 更新Com组件:重新生成*.dll无法打开问题

查看占用进程

dllhost.exe对应进程kill

devenv.exe 对应VS winform调用方关掉工程即可

二、基于QT Activex开发Com组件

参考文章

Qt开发Activex笔记(一):环境搭建、基础开发流程和演示Demo 

https://blog.51cto.com/hongpangzi/3612348

  • QT QtWidgetApp调用COM

4.1 参考文章        

Qt调用Com组件--QT调用COM组件DLL(dumpCPP工具)_qt dumpcpp dll-CSDN博客

4.2 实际代码

.main.cpp

#include <QApplication>
#include <QAxObject>
#include <QDebug>
#include <QFile>int main(int argc, char *argv[])
{QApplication a(argc, argv);QAxObject *mpAxObj;mpAxObj = new QAxObject();//指定调用的COM组件类ID(clsid\ClassID),这个ID要填正确,就是前面宏定义的 ClassID.mpAxObj->setControl("{c5e4017e-73a4-47c2-ad5d-aba20c13a6ba}");//导出支持调用的函数接口QString DOC = mpAxObj->generateDocumentation();QFile outFile("com_function.html");outFile.open(QIODevice ::ReadWrite|QIODevice ::Text);QTextStream TS(&outFile);TS<<DOC<<endl;//调用COM组件函数接口: 显示界面mpAxObj->dynamicCall("show()");//调用COM组件函数接口:获取版本QString result=mpAxObj->dynamicCall("getVersion()").toString();qDebug()<<"插件的版本号:"<<result;//调用COM组件函数接口:获取当前时间QString result2=mpAxObj->dynamicCall("getCurrentTime()").toString();qDebug()<<"当前时间:"<<result2;return a.exec();
}
  • VS winform调用COM

5.1 winform工程引用com组件

操作参考:

C#-winform调用COM组件(COM组件由Qt开发)-云社区-华为云

Qt开发Activex笔记(三):C#调用Qt开发的Activex控件_qt开发ocx给c#-CSDN博客

5.2 修改生成的目标平台为x64

5.3 运行最终效果

5.4 遇到的问题:

5.2.1 点击运行后,报错没有注册类

System.Runtime.InteropServices.COMException

  HResult=0x80040154

  Message=没有注册类 (异常来自 HRESULT:0x80040154 (REGDB_E_CLASSNOTREG))

  Source=System.Windows.Forms

  StackTrace:

   at System.Windows.Forms.UnsafeNativeMethods.CoCreateInstance(Guid& clsid, Object punkOuter, Int32 context, Guid& iid)

   at System.Windows.Forms.AxHost.CreateWithLicense(String license, Guid clsid)

   at System.Windows.Forms.AxHost.CreateInstanceCore(Guid clsid)

   at System.Windows.Forms.AxHost.CreateInstance()

   at System.Windows.Forms.AxHost.GetOcxCreate()

   at System.Windows.Forms.AxHost.TransitionUpTo(Int32 state)

   at System.Windows.Forms.AxHost.CreateHandle()

   at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)

   at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)

   at System.Windows.Forms.AxHost.EndInit()

   at WindowsFormsApp4.Form1.InitializeComponent() in D:\VisionProject\VSWorkSpace\WinformWS\WindowsFormsApp4\WindowsFormsApp4\Form1.Designer.cs:line 64

   at WindowsFormsApp4.Form1..ctor() in D:\VisionProject\VSWorkSpace\WinformWS\WindowsFormsApp4\WindowsFormsApp4\Form1.cs:line 17

   at WindowsFormsApp4.Program.Main() in D:\VisionProject\VSWorkSpace\WinformWS\WindowsFormsApp4\WindowsFormsApp4\Program.cs:line 19

项目属性,生成的目标平台修改为x64。

5.2.2 控件已经成功添加到工具箱中,但未在活动设计器中启用

问题描述:Visual studio 2022 添加com组件到工具箱错误提示:

下列控件已经成功添加到工具箱中,但未在活动设计器中启用 ,请确认要添加的控件能够兼容当前设计器和.net framework 版本。

修改方法:

要选择上面这个Windows窗体应用(.NET Framework)

【Windows 窗体应用】的窗体属性中还有其他信息,目标框架:.NET Core 3.1

而【Windows 窗体应用(.NET Framework)】,其框架则是.NET Framework

这个.NET Core与 .NET Framework是完全不一样的东西:

  • .NET framework框架开发出来的应用只能在windows上运行。
  • .netcore 是开源的,开发出来的应用可以跨平台运行,比如运行在MAC,Linux上 。

而我们添加的COM组件,实际上是只应用于windows环境的技术,在一个非windows 的底层技术以及上层环境肯定就是不行的了。

5.2.3 引入控件报错

1.在工具箱中,拖入控件到UI中,会弹窗报错:

直接重新生成项目,也会报错:

生成的dll确实是64位的:

2.修改为x64平台(上述第二章的qt com dll也是基于x64编译的)后,编译正常,且AxActiveQTServer2Lib不再报错

参考资料

COM简介

COM - COM的简单介绍_com组件结构-CSDN博客

windeployqt打包Qt应用程序(Com只注册了,还不够,需要通过windeploy发布依赖的文件):

windeployqt打包Qt应用程序_qt windeployqt 打包-CSDN博客

Qt的进程间通信,以Active服务器的形式,手把手教你VS上进行Qt的COM、ActivedQt Server的开发,比保姆还保姆

https://www.cnblogs.com/Leventure/p/16971934.html

VS+QT插件创建qt 的ActiveQT Server工程踩过的坑_qt activeqt server-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/4970.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

张大哥笔记:服务器有挖矿木马程序,该如何处理?

这篇文章发表于2021年&#xff0c;今天借这个平台再发布一下&#xff0c;希望对大家有所帮助&#xff01; 今天收到一个粉丝求助&#xff0c;说收到了阿里云官方短信通知提示有挖矿程序&#xff0c;要求立即整改&#xff0c;否则会关停服务器&#xff0c;以下是我和他的对话内…

ios CI/CD 持续集成 组件化专题五-(自动发布私有库-组件化搭建)

一&#xff1a;手动发布私有库总结 手动发布pod私有库&#xff0c;需要进行如下几步操作&#xff1a; 1、修改完代码之后&#xff0c;需要提交代码push到git仓库。 2、给代码打tag。 3、修改podspec文件的version值&#xff0c;使其和设置的tag一直。 4、命令行执行pod repo…

【多级缓存】多级缓存OpenResty,Canal,nginx本地缓存

多级缓存 安装OpenRestyOpenResty入门OpenResty获取请求参数OpenResty向tomcat服务器发送请求 在nginx与tomcat端之间添加redis缓存Redis本地缓存缓存同步缓存同步策略基于Canal的异步通知安装Canal Canal客户端 安装OpenResty OpenResty是一个基于 Nginx的高性能 Web 平台&am…

django序列化

path(get2/, views.HelloApiView().get), path(get2/, views.HelloApiView.as_view()) models class Student(models.Model):name models.CharField(max_length100,verbose_name姓名)age models.IntegerField(verbose_name年龄)sex models.BooleanField(default1,verbose_n…

【java超方便的导入导出工具类】SpringBoot操作Excel导入和导出

Excel导入和导出 一、前期准备 1、首先导入主要的依赖 <dependencies><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version></dependency><depende…

thinkphp6 workerman无法使用框架Db/model等类库方法解决方案

thinkphp6 workerman无法使用框架Db/model相关操作解决 执行安装相关扩展 composer require webman/gateway-worker引入成功后编辑服务类文件,直接展示代码 <?phpnamespace app\server\controller;use GatewayWorker\BusinessWorker; use GatewayWorker\Gateway; use Gate…

算法设计与分析 3.2 牛顿法及改进、迭代法、矩阵谱半径、雅可比迭代、高斯迭代

思考题1 改进cosx&#xff1f;优化算法 关键点在于cos计算过于麻烦&#xff0c;而每次都要求sinx的值 故直接简化为cosx的导数 -sinx 即&#xff1a; 原&#xff1a;//double daoshu(double x) { // return 18 * x - cos(x); //} 改&#xff1a;double daoshu(double x) {retu…

闲话 ASP.NET Core 数据校验(二):FluentValidation 基本用法

前言 除了使用 ASP.NET Core 内置框架来校验数据&#xff0c;事实上&#xff0c;通过很多第三方框架校验数据&#xff0c;更具优势。 比如 FluentValidation&#xff0c;FluentValidation 是第三方的数据校验框架&#xff0c;具有许多优势&#xff0c;是开发人员首选的数据校验…

仅1年!!影响因子10+飙升至30+,Springer旗下的潜力优刊,未来可期!

【SciencePub学术】今天小编给大家带来了一本医学类的高分优刊解读&#xff0c;隶属于Springer出版社&#xff0c;JCR1区&#xff0c;中科院1区TOP&#xff0c;创刊时间不长&#xff0c;但影响因子仅1年时间从10直接飙升至30&#xff0c;领域相符的学者可考虑&#xff01; Sign…

Java 面向对象—重载和重写/覆盖(面试)

重载和重写/覆盖&#xff1a; 重载&#xff08;overload&#xff09;&#xff1a; Java重载是发生在本类中的&#xff0c;允许同一个类中&#xff0c;有多个同名方法存在&#xff0c;方法名可以相同&#xff0c;方法参数的个数和类型不同&#xff0c;即要求形参列表不一致。重载…

Vue后台系统demo小计

创建项目 1.报错 Error: command failed: npm install --loglevel error --legacy-peer-deps 措施1&#xff1a;node.js文件夹属性 》高级 》选择第一个允许 Users(XXX\Users) &#xff08;对我无用&#xff09; 措施2&#xff1a;PowerShell(以管理员身份运行) 》 cd 想存…

12_Scala_package

文章目录 Scaal面向对象编程1.回顾Java2.package可以多次声明3.设置作用域&#xff0c;设置上下级4.包可以当作对象使用5.import6.Scala用_取代Java *7.导入多个包8.屏蔽类9.类起别名10.import的规则11.有些包无需导入 Scaal面向对象编程 Scala是一门完全面向对象语言&#xf…

Redisson - tryLock 函数参数分析

这里有三个参数&#xff1a; waitTime&#xff1a;等待时间leaseTime&#xff1a;超时施放时间TimeUnit&#xff1a;时间单位 等待时间 如果 ABC… 多个线程去抢夺一把锁&#xff0c;A 成功了&#xff0c;如果设置的是 -1&#xff0c;那么 BCD... 就不等待&#xff0c;直接返…

Linux服务器终端软件termius以及Xshell + WinSCP组合

1. termius 官网地址&#xff1a;https://termius.com/ Termius是一个跨平台的SSH客户端&#xff0c;它提供了一个便捷的方式来远程连接和管理服务器、虚拟机和网络设备。以下是Termius的一些特点和功能&#xff1a; 跨平台支持&#xff1a;Termius可在多个操作系统上运行&…

基于Guava的异步线程结果监听:ListenableFuture

1.ListenableFuture概述&#xff1a; ListenableFuture是对原有Future的增强&#xff0c;它可以监听异步执行的过程&#xff0c;执行完了&#xff0c;自动触发回调操作。 除此之外&#xff0c;可以分别针对成功或者失败的情况做后续处理。 2.使用场景 你想拿到异步处理的结果…

Mycat(三)读写分离双主双从

文章目录 搭建双主双从双主机配置双从机配置双从配置两个主机互相复制停止从服务复制功能重新配置主从 修改 Mycat 的集群配置实现多种主从双主双从集群角色划分增加两个数据源修改集群配置文件读写分离配置扩展&#xff08;1&#xff09;读写分离(一主一从,无备)(m是主,s是从)…

「C/C++ 01」scanf()与回车滞留问题

目录 〇、scanf()接收用户输入的流程 一、回车的缓冲区滞留问题是什么&#xff1f; 二、为什么&#xff1f; 三、四个解决方法&#xff1a; 1. 在前面的scanf()中加上\n 2. 在scanf("%c")中添加空格 3. 使用getchar()来吸收回车 4. 使用fflush()清空缓冲区 〇、scan…

【火柴题】如何移动一根火柴将2变成5

这里写目录标题 如何移动一根棍子将2变成51、变成第五个字母E2、变成罗马数字5 V3、变成手写体54、商用字体55、古尔穆基数字、藏文数字56、希伯来数字5 如何不动棍子将2变成5使用镜子使用装水的水杯小孔成像透视 如何移动一根棍子将2变成5 1、变成第五个字母E E 2、变成罗马…

力扣HOT100 - 207. 课程表

解题思路&#xff1a; class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {int[] inDegree new int[numCourses];//存每个结点的入度List<List<Integer>> res new ArrayList<>();//存结点之间依赖关系Queue<Integer>…

获客难题怎么破?揭秘那些企业高效拓客的背后秘密

在当今的商业环境中&#xff0c;客户资源的获取是企业生存和发展的关键。然而&#xff0c;传统的获客方式往往既耗时又费力&#xff0c;且效率不高。随着科技的进步和大数据的普及&#xff0c;一种新兴的获客工具——自动获客软件应运而生&#xff0c;它以其精准高效的特点&…