vs、c++环境下开发使用com组件

49 篇文章 2 订阅

com简介

COM组件是一组接口的集合,由Win32动态链接库(DLL)或可执行文件(EXE)的形式发布的可执行二进制代码组成。

com是独立于语言的组件体系结构,可以用C++、Java和VB等任意一种语言编写com对象。

COM组件的优点有:

通过接口对功能分类;

功能扩充,只需添加接口就行;

可轻松实现进程间调用、分布式调用;

具有封装、继承、多态的面向对象特征。

COM是规范,以该规范实现的DLL可视为COM组件。COM组件接口实现是统一的虚拟函数表(VTBL)形式,其包含组件函数的一组指针,由此可获取想要通信的组件函数的内存地址.

# define interface struct

使用 C++ 的纯抽象基类来实现 COM 接口

在 C++ 里, COM 接口被定义成一个纯虚类. 只有虚函数表的类. 但他仍然是类, 就像普通的继承方法一样继承就可以. 这些类的声明, 你包含 ole2.h 头文件, 基本的COM 接口就差不多了. 通常你的接口会继承 IUnknown, 或 IDispatch ,然后再继承自己的接口, 实现自己的接口. 自己重头写是很慢的, VC 的ATL, MFC 都对COM/ACTIVEX/OLE 的使用开发有很多支持. 当然, 明白底层也很重要的. 你要找书看, 推荐看 <<COM 本质论>>. 然后看 ATL 相关的, 如<<ATL深入解析>>等.

Vs和C++开发com组件

com组件编译会生成_i.c、_i.h文件,该文件由IDL接口声明文件生成而来。使用com包含该_i.h文件即可,头文件中包含com组件的所有声明。

1.新建ATL项目(你也可以是其他项目,只要是dll就行,可以支持MFC、ATL、COM等)

2.添加接口类 (ATL简单对象),注意要添加ProgID,目的是为了后面通过CLSIDFromProgID函数调用com

3.添加接口方法,设置参数和返回值 (在类视图中,对接口类添加方法)

4.实现接口方法

延伸:

1. 在调用的程序中怎使用com组件的自定义结构?

   在com组件程序的idl文件的library中定义结构,同时需要加上uuid,如

    [uuid(73B97ACE-1F6F-427c-9800-E904F97D39B0)]

struct Station

{

int StationType;

int StationID;

int CPUNum;

};

然后就可以在调用方直接使用Station结构了。

Vs和C++调用C++com组件

在c++调用com组件前,需要注册com组件,有手动注册和代码自动注册两种办法。

手动注册:

在注册com组件后,我们程序以uuid为唯一标识创建com组件实例,而uuid是com开发者编写时确定,uuid保证了其自身的唯一性,一定不会与别的组件撞车。

前提是管理员身份运行cmd窗口注册com.即regsvr32 comtest.dll

C:\windows\system32> e:

E:\> cd E:\study\qt\fstest\test\test

E:\study\qt\fstest\test\test> regsvr32 comtest.dll

代码自动注册
  1. 打开VC的属性界面进行设置表示以管理员身份运行。

2.编写代码注册dll到本地电脑,执行WinExec函数

WinExec("regsvr32   E:\\study\\com\\testdll\\testdll\\ATLCom.dll", SW_SHOW);

获得接口声明

即先把dll复制过来,并在使用#import 方式导入文件。

第一种办法:com提供者编译生成com组件的dll/exe时生成的_i.h 文件   测试会报错

        com组件编译会生成_i.c、_i.h文件,该文件由IDL接口声明文件生成而来。使用com包含include该_i.h文件即可,头文件中包含com组件的所有声明。

如:#include"../ATL Dll/ATLDll_i.h"

第二种办法:使用.tlb 文件生成   实测可行

编译com组件生成EXE/DLL文件时,除了会生成_i.h和_i.c以外还会额外生成一个.tlb文件。而该文件可以使用#import 方式作为模块导入,之后直接编译,ms会为我们生成带有声明的.tli和.tlh文件,在使用的地方同样包含tlh文件即可。

具体做法如下:

 首先将tlb文件复制到我们需要使用的项目下,使用import包含,代码如下:

#include<atlcomcli.h>

//生成的tlh文件会自己加上namespace因此使用no_namesapce

//named_guids 指示编译器以旧样式定义和初始化 GUID 变量,格式为 LIBID_MyLib 、、 CLSID_MyCoClass IID_MyInterface 和 DIID_MyDispInterface

#import "ATLDll.tlb" no_namespace

int _tmain(int argc, _TCHAR* argv[])

{

return 0;

}

之后直接编译生成一次项目,会在中间目录得到.tli和.tlh文件,在使用的地方同样包含tlh文件即可。

第三种办法:使用com组件的二进制dll/exe生成:实测可行,推荐使用这种办法

 以上两种情况都需要由com组件的提供者提供除开二进制执行模块以外的文件,这当然是不够好的,因此com提供直接从二进制生成接口相关声明的特性。

直接复制dll/exe到你的项目目录下,并且使用import导入,之后直接生成一次。 生成后在中间目录得到组件的tli和.tlh文件,再在使用的地方同样包含tlh文件即可。

#include <iostream>

#import "ATL Dll.dll" no_namespace

#include"./x64/Debug/atl dll.tlh"

int main()

{

    return 0;

}

调用创建com组件实例

创建时获取该ID的办法有两种

第一种办法:

直接使用"一"中生成的头文件声明中具有,直接使用,但需要使用__uuidof()关键字,其中uuid的名字来源于atl对象名,在头中声明如:

CLSID clsid = __uuidof(Simple); //对应libraryuuid

延伸:IID iid = __uuidof(ISimple);//对应interfaceuuid

#import  " comtest.dll"  no_namespace

#include"./Debug/comtest.tlh"

 CoInitialize(NULL);

 CComPtr<Ione>  m_FirstClass;  //Ione 为com组件的接口

 HRESULT hr = S_OK;

 hr = m_FirstClass.CoCreateInstance(__uuidof(one));//one为com组件的library

 long result = 0;

 result = m_FirstClass->add(4, 5 );

 m_FirstClass.Release();//释放资源,不能省

 m_FirstClass = NULL;

 CoUninitialize();

建议这种

或者

#import  " comtest.dll"  no_namespace

#include"./Debug/comtest.tlh"

 CoInitialize(NULL);

 IonePtr   m_FirstClass;

 HRESULT hr = S_OK;

 hr = m_FirstClass.CreateInstance(__uuidof(one));

 long result = 0;

 result = m_FirstClass->add(4, 5 );

 m_FirstClass.Release();//释放资源

 m_FirstClass = NULL;

 CoUninitialize();

第二种办法:

使用 ProgID 查找出uuid,该名字为组件接口的别名,可以用于查找

HRESULT hr = CLSIDFromProgID(OLESTR("Sim"), &clsid);

HRESULT hr1 = CLSIDFromString(OLESTR("{5e5c1802-d1fd-44af-8ac3-4453401b39db}"), &clsid1);

HRESULT hr = CLSIDFromProgID(OLESTR("ATLCom.Simple"), &clsid);//实测失败

第一个参数:

可以是ProgID OLESTR("Sim")

也可以是Dll名+Com对象名 HRESULT hr = CLSIDFromProgID(OLESTR("ATLCom.Simple"), &clsid);//但实测失败

延伸

HRESULT hr1 = CLSIDFromString(OLESTR("{5e5c1802-d1fd-44af-8ac3-4453401b39db}"), &clsid1);

#import  " comtest.dll"  no_namespace

#include"./Debug/comtest.tlh"

CoInitialize(NULL);

CLSID clsid;

CLSIDFromProgID(OLESTR("ggg"), &clsid);//gggcsharp接口类的ProgId,创建csharp记得写上ProgId

CComPtr<Icsharp> pGetRes;//智能指针

pGetRes.CoCreateInstance(clsid);

float c = pGetRes->add(1,2);

pGetRes.Release();//小心哦!

CoUninitialize();

或者建议这种

或者

CoInitialize(NULL);

CLSID clsid;

HRESULT hr = CLSIDFromProgID(OLESTR("ggg"), &clsid);

Icsharp *ptr;

hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,__uuidof(Icsharp), (LPVOID*)&ptr);

float c = ptr->add(1, 2);

ptr->Release();

 CoUninitialize();

延伸:

CoCreateInstance的声明: 用指定的类标识符创建一个Com对象

HRESULT __stdcall CoCreateInstance(

  const CLSID&  clsid,    

  IUnknown*  pIUnknownOuter,

  DWORD  dwClsContext,

  const IID&  iid,        

  void**  ppv

);

CoCreateInstance有四个输入参数和一个输出参数。第一个参数是待创建组件的CLSID。第二个参数是用于聚合组件的。第三个参数的作用是限定所创建的组件的执行上下文。第四个参数iid为组件上待使用的接口的IID。CoCreateInstance将在最后一个参数中返回此接口的指针。

CoCreateInstance的第三个参数dwClsContext(类上下文)可以控制所创建的组件是在与客户相同的进程中运行,还是在不同的进程中运行,或者是在另外一台机器上运行。其取值为下列各值的组合:

CLSCTX_INPROC_SERVER

 客户希望创建在同一进程中运行的组件。为能够同客户在同一进程中运行,组建必须是在DLL中实现的。

CLSCTX_INPROC_HANDLER

 客户希望创建进程中处理器。一个进程中处理器实际上是一个只实现了某个组件一部分的进程中组件。该组件的其他部分将由本地或远程服务器上的某个进程外组件实现。

CLSCTX_LOCAL_SERVER

 客户希望创建一个在同一机器上的另外一个进程中运行的组件。本地服务器是由EXE实现的。

CLSCTX_REMOTE_SERVER

 客户希望创建一个在远程机器上运行的组件。此标志需要分布式COM正常工作。

数据结构交互

调用方和com组件的数据结构交互,com组件是通过使用VARIANT结构体进行传递数据的。

VARIANT结构体

普通的结构,如int

VARIANT varChunk;

VariantInit(&varChunk);

varChunk.vt = VT_INT;

varChunk.intVal = 1;

VariantClear(&varChunk);

如果是指针

int *ai = new int(1);

VARIANT varChunk;

VariantInit(&varChunk);

varChunk.vt = VT_INT;

varChunk.pintVal = ai;

VariantClear(&varChunk);

delete ai;

字符串

BSTR bstr = SysAllocString(_T("中文"));

VARIANT varChunk;

VariantInit(&varChunk);

varChunk.vt = VT_BSTR;

varChunk.bstrVal = bstr;

VariantClear(&varChunk);

SysReleaseString(bstr);

普通类型的数组

建新的数组描述符,分配和初始化数组的数据,并返回指向新数组描述符的指针

SAFEARRAY * SafeArrayCreate(

  [in] VARTYPE        vt,

  [in] UINT           cDims,

  [in] SAFEARRAYBOUND *rgsabound

);

[in] vt数组的基类型 (数组) 的每个元素的 VARTYPE

[in] cDims数组的维数

[in] rgsabound一个边界向量 (每个维度) 为数组分配的一个

在VARIANT的vt成员的 值如果包含VT_ARRAY|...,那么它所封装的就是一个SafeArray.也说明vt可以通过逻辑运算

用于创建一维数组

SAFEARRAY * SafeArrayCreateVector(

  [in] VARTYPE vt,

  [in] LONG    lLbound,

  [in] ULONG   cElements

);

SAFEARRAY

就是所谓的安全数组

typedef struct tagSAFEARRAY

    {

    USHORT cDims;

    USHORT fFeatures;

    ULONG cbElements;

    ULONG cLocks;

    PVOID pvData;

    SAFEARRAYBOUND rgsabound[ 1 ];

} SAFEARRAY;

typedef struct tagSAFEARRAYBOUND

    {

    ULONG cElements;

    LONG lLbound;

    } SAFEARRAYBOUND;

ULONG cElements表示的是元素的数目(更准确的说是在本维中的数目);LONG lLbound表示的是一个逻辑起点序号,实际访问内存的时候,安全数组会将程序指定的序号减去lLbound,比如你将其设置为10000,a[10000]这相当于A[0],a[999]数组越界,所以在没有特殊要求的情况下,lLbound一般为0。rgsaBound[1]表示的是一维数组,二维数组要定义为rgsaBound[2]

将数据元素存储在数组中的指定位置

HRESULT SafeArrayPutElement(

  [in] SAFEARRAY *psa,

  [in] LONG      *rgIndices,

  [in] void      *pv

);

[in] psa:SafeArrayCreate 创建的数组描述符

[in] rgIndices:数组的每个维度的索引向量,最右 (最小有效) 维度为 rgIndices[0]。 最左侧的维度存储在 rgIndices[psa->cDims – 1]。

[in] pv:要分配给数组的数据

检索数组的单个元素,一个一个地读

HRESULT SafeArrayGetElement(

  [in]  SAFEARRAY *psa,

  [in]  LONG      *rgIndices,

  [out] void      *pv

);

[in] psa:SafeArrayCreate 创建的数组描述符

[in] rgIndices:数组的每个维度的索引向量

[out] pv:数组的元素。

递增数组的锁计数,并检索指向数组数据的指针,一次性直接读到缓冲区

HRESULT SafeArrayAccessData(

  [in]  SAFEARRAY  *psa,

  [out] void HUGEP **ppvData

);

递减数组的锁计数,使 SafeArrayAccessData检索的指针失效,与SafeArrayAccessData配对使用,使用了SafeArrayAccessData,需要调SafeArrayUnaccessData使SAFEARRAY 指针失效

HRESULT SafeArrayUnaccessData(

  [in] SAFEARRAY *psa

);

[in] psa:SafeArrayCreate 创建的数组描述符

例子

在调用方

//(1). 定义变量

int uIsRead = 10;

DOUBLE bVal[] = { 0,1,2,3,4,5,6,7,8,9 };

//(2). 创建SafeArray描述符:

SAFEARRAY *psa;

SAFEARRAYBOUND rgsabound[1];

rgsabound[0].cElements = uIsRead;

rgsabound[0].lLbound = 0;

psa = SafeArrayCreate(VT_R8, 1, rgsabound);

//(3). 放置数据元素到SafeArray:

for (long index = 0; index <= uIsRead; index++)

{

SafeArrayPutElement(psa, &index, &bVal[index]);

}

//(4). 封装到VARIANT内:

VARIANT varChunk;

VariantInit(&varChunk);

varChunk.vt = VT_R8;

varChunk.parray = psa;

int iRet = ptr->InitializeOMCC(bstrText, varChunk);

VariantClear(&varChunk);

在com实现

STDMETHODIMP CSimple::InitializeOMCC(BSTR strProjectFilePath, VARIANT stnInfoList, LONG *ret)

{

DOUBLE buf[10];

SAFEARRAY *psa = stnInfoList.parray;

for (long ind = 0; ind < 10; ind++)

{

::SafeArrayGetElement(psa, &ind, buf + ind);

}

return S_OK;

}

自定义结构体数组

在c++中自定义结构体可以用字节数组CByteArray来存储到COleVariant类型中。COleVariant类型可以和VARIANT类型通用。

在调用方

CoInitialize(NULL);

CLSID clsid  = __uuidof(Simple);

IID iid = __uuidof(ISimple);

ISimple *ptr;

HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, iid, (LPVOID*)&ptr);

vector<StnInfo> stnInfoList;

StnInfo info;

for (size_t i = 0; i < 3; i++)

{

info.StationType = i;

info.StationID = i;

info.CPUSlotNum = i;

stnInfoList.push_back(info);

}

int nCount = stnInfoList.size();

int nsize = nCount * sizeof(StnInfo);

CByteArray  baInput;

baInput.SetSize(nsize);

char *szdata = new char[nsize];

for (size_t i = 0; i < nCount; i++)

{

memcpy(szdata+i*sizeof(StnInfo), &stnInfoList[i], sizeof(StnInfo));

}

for (size_t i = 0; i < nsize; i++)

{

baInput.SetAt(i,szdata[i]);

}

delete[] szdata;

COleVariant oleva(baInput);

CString  strPrjPath =_T( "E:\\中文\\com\\testdll\\testdll\\Debug");

BSTR bstrText = strPrjPath.AllocSysString();

int iRet = ptr->InitializeOMCC(bstrText, oleva);

SysReleaseString(bstrText);

CoUninitialize();

在com组件

typedef struct  tagStnInfo

{

int StationType;

int StationID;

int CPUSlotNum;

}StnInfo;

STDMETHODIMP CSimple::InitializeOMCC(BSTR strProjectFilePath, VARIANT stnInfoList, LONG *ret)

{

// TODO: 在此处添加实现代码

VARIANT HUGEP *pbstr;

SAFEARRAY *psa = stnInfoList.parray;

SafeArrayAccessData(psa, (void HUGEP**)&pbstr);

int uCount = (psa->rgsabound->cElements) /sizeof(StnInfo);

for (int i = 0; i < uCount; i++)

{

int StationID = ((StnInfo*)((char*)(&pbstr[i]) - 4 * i))->StationID;

int CPUSlotNum = ((StnInfo*)((char*)(&pbstr[i]) - 4 * i))->CPUSlotNum;

int StationType = ((StnInfo*)((char*)(&pbstr[i]) - 4 * i))->StationType;

}

SafeArrayUnaccessData(psa);

return S_OK;

}

Com接口继承及实现

  1. 添加IFun2接口,继承ISimple接口

在idl文件interface ISimple : IDispatch后面添加

[

object,

uuid(B9D75722-1950-405b-8BB6-9788A6DB1DB2),

helpstring("IFun2 Interface"),

pointer_default(unique)

]

interface IFun2: ISimple

{

};

  1. library ATLComLib中的coclass Simple函数改为

coclass Simple

{

interface ISimple;

[default] interface IFun2;

};

3.在Simple.h头文件

class ATL_NO_VTABLE CSimple :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CSimple, &CLSID_Simple>,

public IDispatchImpl<ISimple, &IID_ISimple, &LIBID_ATLComLib, /*wMajor =*/ 1, /*wMinor =*/ 0>

改成

class ATL_NO_VTABLE CSimple :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CSimple, &CLSID_Simple>,

public IDispatchImpl<IFun2, &IID_ISimple, &LIBID_ATLComLib, /*wMajor =*/ 1, /*wMinor =*/ 0>

BEGIN_COM_MAPEND_COM_MAP中间加上COM_INTERFACE_ENTRY(IFun2)

BEGIN_COM_MAP(CSimple)

COM_INTERFACE_ENTRY(ISimple)

COM_INTERFACE_ENTRY(IFun2)

COM_INTERFACE_ENTRY(IDispatch)

END_COM_MAP()

4.重新添加新的接口函数即可。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用VS2019 C++开发工具搭建开发环境是一种常见的做法。VS2019是微软公司开发的一款集成开发环境(IDE),在C++开发领域有着广泛的应用。 首先,我们需要下载并安装VS2019,可以从微软官网下载安装程序。安装程序会引导我们完成一些初始化设置和配置,如选择合适的安装组件和目标平台等。 安装完成后,我们可以打开VS2019,并创建一个新的C++项目。在创建项目时,我们可以选择不同的项目类型,如控制台应用程序、Windows桌面应用等,根据自己的需求选择合适的项目类型。 创建项目后,我们就可以开始编写代码了。VS2019提供了强大的代码编辑和调试功能,能够帮助我们更高效地开发C++程序。我们可以在编辑器中编写代码,并使用智能提示、语法检查等功能来提高代码质量。 在编写代码的过程中,VS2019还提供了一些常用的工具,如代码管理工具、调试工具和性能分析工具等,这些工具可以帮助我们更好地管理和调试代码,提高开发效率。 最后,我们可以使用VS2019的编译工具将我们的代码编译成可执行文件。VS2019支持多种编译选项和调试选项,可以根据需要进行配置。编译完成后,我们就可以运行和测试我们的程序了。 总之,使用VS2019 C++开发工具搭建开发环境可以为我们提供一套完整的开发工具链,帮助我们更好地开发和调试C++程序。无论是初学者还是专业开发者,都可以通过这个工具来提高开发效率和代码质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值