COM - COM编程的简单实现

本文主要记录一下COM编程的简单实现。关于COM的简单介绍可以参考文章 COM的简单介绍

目录

1、代码的主要结构

2、ComTest

2.1、接口类的定义

2.2、对象类的定义

2.3、类厂的定义

2.4、自注册模块

2.5、ComTest.cpp

2.6、dllmain.cpp

2.7、ComTest.def

3、ComCtrl

4、代码实现


1、代码的主要结构

代码结构主要分为两部分,其中 ComTest 为组件程序部分,ComCtrl 为客户部分(即组件的使用者),具体如下所示:

2、ComTest

ComTest 为 VS2012所创建的 Win32项目->DLL 类型的工程。代码的组成结构如下所示:

  1. IUnknown 是所有接口的基类;
  2. IComTest 为组件的接口类,在这个接口类中定义能提供的服务(即函数),所以用户只需要知道这个类就可以了,;
  3. CComTest 是对象类,继承自接口IComTest,用于实现服务;
  4. IClassFactory 为类厂类的接口类,不需要开发人员实现;
  5. CComTestFactory 为类厂类,用于生产CComTest对象;
  6. 还有一个自注册模块,用于将组件程序注册进注册表中;

具体实现可以参考以下进行开发。

2.1、接口类的定义

IComTest.h

#pragma once
#include <Unknwn.h>

// 接口 IComTest 的GUID标识,可以通过VS自带的工具自动生成
// {24EC0D3E-2C69-4C39-8AAB-59AFF1111982}
static const GUID IID_IComTest = 
{ 0x24ec0d3e, 0x2c69, 0x4c39, { 0x8a, 0xab, 0x59, 0xaf, 0xf1, 0x11, 0x19, 0x82 } };

class IComTest : public IUnknown{
public:
	virtual BOOL __stdcall SayHello() = 0;    //声明一个服务函数,用于测试
};

 在这个文件中,主要是声明了接口类的GUID, 即IID_IComTest;同时声明了组件程序的接口函数。接口类没有实现,所以没有对应的 IComTest.cpp 文件。另外这个 IComTest.h 头文件必须包含在用户程序中。

2.2、对象类的定义

CComTest.h

#pragma once

#include <stdio.h>
#include "icomtest.h"

// CComTest 对象的GUID标识,名称为 CLSID
// {CEF56010-936E-4F71-BC51-32F7ED3B3F73}
static const GUID CLSID_CComTest = 
{ 0xcef56010, 0x936e, 0x4f71, { 0xbc, 0x51, 0x32, 0xf7, 0xed, 0x3b, 0x3f, 0x73 } };

class CComTest : public IComTest
{
public:
	CComTest(void);
	~CComTest(void);

public:
	//IUnknown member functions
	virtual HRESULT __stdcall QueryInterface(const IID& iid, void **ppv);
	virtual ULONG __stdcall AddRef();
	virtual ULONG __stdcall Release();

	//IComTest member functions
	virtual BOOL __stdcall SayHello();

private:
	ULONG m_Ref;		//引用计数
};

 在这个文件中,主要是声明了对象类的GUID(也成为CLSID), 即 CLSID_CComTest;同时声明了对象类的成员函数,这些成员函数是必须要实现的,所以有对应的 CComTest.cpp文件。

CComTest.cpp

#include "stdafx.h"
#include "CComTest.h"

extern ULONG g_ObjNumber;	//在ComTest.cpp中定义

CComTest::CComTest(void){
	this->m_Ref = 0;		//引用计数初始化为0
	g_ObjNumber ++;        //对象计数 +1
}

CComTest::~CComTest(void){ }

//IUnknown member functions
HRESULT CComTest::QueryInterface(const IID& iid, void **ppv){
	if( iid == IID_IUnknown ){
		*ppv = (IUnknown *) this ;
		((IUnknown *)(*ppv))->AddRef(); //返回的是 IUnknown 接口
	}else if ( iid == IID_IComTest ){ //IID_IComTest 已经在 IComTest.h 中定义
		*ppv = (IComTest *) this ;
		((IComTest *)(*ppv))->AddRef();
	}
	else{
		*ppv = NULL;
		return E_NOINTERFACE ;
	}
	return S_OK;
}

ULONG CComTest::AddRef(){
	this->m_Ref ++;		//引用计数 +1
	return this->m_Ref;
}

ULONG CComTest::Release(){
	this->m_Ref --;		    //引用计数 -1
	if(this->m_Ref == 0){    //当引用计数为0时,则删除自身对象
		g_ObjNumber --;        //同时对象个数 -1
		delete this;
		return 0;
	}
	return this->m_Ref;
}

//IComTest member functions
BOOL CComTest::SayHello(){
	printf("Hello COM!\n");
	return TRUE;
}

2.3、类厂的定义

CComTestFactory.h

#pragma once
#include <Unknwn.h>    //包含头文件

class CComTestFactory : public IClassFactory
{
public:
	CComTestFactory(void);
	~CComTestFactory(void);

public:
	//IUnknown member functions
	virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
	virtual ULONG __stdcall AddRef();
	virtual ULONG __stdcall Release();

	//IClassFactory member functions
	virtual HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void** ppv);
	virtual HRESULT __stdcall LockServer(BOOL bLock);

private:
	ULONG m_Ref;		//引用计数
};

CComTestFactory.cpp

#include "stdafx.h"

#include "CComTest.h"
#include "CComTestFactory.h"

extern ULONG g_LockNumber;	//类厂对象 的锁计数器,在ComTest.cpp中定义,下同
extern ULONG g_ObjNumber;	//组件中 CComTest 对象的个数,用于判断是否可以卸载本组件,如果值为0则可以卸载

CComTestFactory::CComTestFactory(void){
	this->m_Ref = 0;		//引用计数初始化为0
}

CComTestFactory::~CComTestFactory(void){}

//IUnknown member functions
HRESULT CComTestFactory::QueryInterface(const IID& iid, void** ppv){
	if( iid == IID_IUnknown ){
		*ppv = (IUnknown *) this ;
		((IUnknown *)(*ppv))->AddRef();
	}else if ( iid == IID_IClassFactory ){ //IID_IClassFactory 已经在 Unknwn.h 中定义
		*ppv = (IClassFactory *) this ;
		((IClassFactory *)(*ppv))->AddRef();
	}
	else{
		*ppv = NULL;
		return E_NOINTERFACE ;
	}
	return S_OK;
}

ULONG CComTestFactory::AddRef(){
	this->m_Ref ++;		//引用计数 +1
	return this->m_Ref;
}

ULONG CComTestFactory::Release(){
	this->m_Ref --;		//引用计数 -1
	//当引用计数为0时,则删除自身对象
	if(this->m_Ref == 0){
		delete this;
		return 0;
	}
	return this->m_Ref;
}

//IClassFactory member functions
//创建 CComTest 对象,并返回 接口指针
HRESULT CComTestFactory::CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void** ppv){
   if (NULL != pUnknownOuter)
	   return CLASS_E_NOAGGREGATION;

   HRESULT hr = E_OUTOFMEMORY;
   //Create the object passing function to notify on destruction.
   CComTest* pObj = new CComTest();
   if (NULL == pObj)
      return hr;   
   
   //Obtain the first interface pointer (which does an AddRef)
   *ppv = NULL;
   hr=pObj->QueryInterface(iid, ppv);
   if (hr != S_OK) {
	   //Kill the object if initial creation or FInit failed.
	   g_ObjNumber --; // Reference count g_ObjNumber be added in CComTest constructor
	   delete pObj;
   }
   return hr; 
}

HRESULT CComTestFactory::LockServer(BOOL bLock){
	if(bLock){
		g_LockNumber++; 
	}else{
		g_LockNumber--;
	}
	return NOERROR;
}

 其中最主要的函数是 CreateInstance ,该函数用于创建对应对象类的接口指针。因为每个类厂能创建的对象类是已知的,所以返回的接口指针就是对应对象类的接口指针。CreateInstance函数 是当用户调用 CoCreateInstance函数创建类厂接口指针时,会自行调用,这个与 《COM的简单介绍》中介绍的不是很一致,请注意区分。

2.4、自注册模块

组件的自注册就是要在组件的引出函数中定义 DllRegisterServer 和 DllUnregisterServer 两个函数。其中 DllRegisterServer  函数是往注册表写入注册信息,DllUnregisterServer 函数是从注册表中删除注册信息。

DllRegisterServer 

extern "C" HRESULT __stdcall DllRegisterServer()
{
	char szModule[1024];		//For char
	wchar_t w_szModule[1024];		//For wchar_t
	DWORD dwResult = ::GetModuleFileName((HMODULE)g_hModule, w_szModule, 1024);
	if (dwResult == 0)
		return SELFREG_E_CLASS;

	Wchar2Char(szModule, w_szModule);	//在这里进行转换,防止对 Registry.cpp进行过多的修改
	return RegisterServer(CLSID_CComTest,	  //对象类 CComTest 的GUID
	                      szModule,				//组件程序的完整路径
						  "ComTest.Object",		//ProgID
						  "ComTest Component",	//Description
						  NULL);
}

DllUnregisterServer 

extern "C" HRESULT __stdcall DllUnregisterServer()
{
	return UnregisterServer(CLSID_CComTest, "ComTest.Object", NULL);
}

 其中,函数 RegisterServer 和 UnregisterServer 全都定义在 Registry.cpp文件中,在该文件中实现了一个比较通用的注册成员。由于该文件中代码比较多,可前往文末最后的下载链接中自行下载。

2.5、ComTest.cpp

在该文件中定义了组件程序必须提供的四个引出函数 DllGetClassObjectDllCanUnloadNowDllRegisterServer DllUnregisterServer ,详细代码如下所示:

// ComTest.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"
#include "olectl.h"
#include "IComTest.h"
#include "CComTest.h"
#include "CComTestFactory.h"
#include "Registry.h"

ULONG g_LockNumber = 0;	//类厂对象 的锁计数器
ULONG g_ObjNumber = 0;	//组件中 CComTest 对象的个数,用于判断是否可以卸载本组件,如果值为0则可以卸载
HANDLE g_hModule;		//DLL句柄

extern "C" HRESULT __stdcall DllGetClassObject(const CLSID& clsid, const IID& iid, void **ppv)
{
	if(clsid == CLSID_CComTest ){
		CComTestFactory *pFactory = new CComTestFactory;
		
		if (pFactory == NULL) {
			return E_OUTOFMEMORY ;
		}
		HRESULT result = pFactory->QueryInterface(iid, ppv);
		return result;
	}else{
		return CLASS_E_CLASSNOTAVAILABLE;
	}
}

extern "C" HRESULT __stdcall DllCanUnloadNow(void)
{
	//双重检查,只有当对象计数和锁计数都为0时,才可以卸载组件程序
	if ((g_ObjNumber == 0) && (g_LockNumber == 0))
		return S_OK;
	else
		return S_FALSE;
}

// Server registration
extern "C" HRESULT __stdcall DllRegisterServer()
{
	char szModule[1024];		//For char
	wchar_t w_szModule[1024];		//For wchar_t
	DWORD dwResult = ::GetModuleFileName((HMODULE)g_hModule, w_szModule, 1024);
	if (dwResult == 0)
		return SELFREG_E_CLASS;

	Wchar2Char(szModule, w_szModule);	//在这里进行转换,防止对 Registry.cpp进行过多的修改
	return RegisterServer(CLSID_CComTest,	  //对象类 CComTest 的GUID
	                      szModule,				//组件程序的完整路径
						  "ComTest.Object",		//ProgID
						  "ComTest Component",	//Description
						  NULL);
}

// Server unregistration
extern "C" HRESULT __stdcall DllUnregisterServer()
{
	return UnregisterServer(CLSID_CComTest, "ComTest.Object", NULL);
}

2.6、dllmain.cpp

dllmain.cpp定义了组件程序的入口函数,如下所示:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
extern HANDLE g_hModule;	//DLL句柄,在ComTest.cpp中定义
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	g_hModule = hModule;        //最重要的就是这条语句
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

2.7、ComTest.def

这个.def文件声明了组件需要引出的几个函数,如下所示:

; ComTest.def : Declares the module parameters for the DLL.
LIBRARY		"ComTest"
EXPORTS
    ; Explicit exports can go here
	DllGetClassObject	PRIVATE
	DllRegisterServer	PRIVATE
	DllUnregisterServer	PRIVATE
	DllCanUnloadNow		PRIVATE

3、ComCtrl

ComCtrl 为 VS2012所创建的 Win32控制台应用程序 类型的工程,主要用于模拟对组件的调用。在ComCtrl工程中,一定要包含ComTest工程中的 IComTest.h头文件,因为在该文件中声明了组件程序的接口。需要注意的是,在使用组件对象之前一定要使用 RegSvr32 命令进行组件的注册,当注册成功之后会有以下提示,否则会报错:

此时就会在注册表中看到的注册信息

用户详细代码如下所示:

#include "windows.h"
#include <stdio.h>
#include <comutil.h>

int TestCom(){
	//初始化COM库,使用默认的内存分配器
	if (CoInitialize(NULL) != S_OK) {
		printf("Initialize COM library failed!\n");
		return -1;
	}

	HRESULT hResult;
	GUID comTestCLSID;

	//获取 ProgID为 ComTest.Object 组件的CLISD,其中 ComTest.Object 是在注册注册表时确定的ID
	hResult = ::CLSIDFromProgID(L"ComTest.Object", &comTestCLSID);
	if (hResult != S_OK) {
		printf(">>> Can't find the ComTest CLSID!\n");
		return -2;
	}else{
		LPOLESTR szCLSID;     
        StringFromCLSID(comTestCLSID, &szCLSID);     //将其转化为字符串形式用来输出  
        wprintf(L">>> Find ComTest CLSID: \"%s\"\n",szCLSID);      
        CoTaskMemFree(szCLSID);						//调用COM库的内存释放  
	}
	
	IUnknown *pUnknown;
	//用此 CLSID 创建一个COM对象,并获取 IUnknown 接口,指向的是类厂对象
	hResult = CoCreateInstance(	comTestCLSID, 
								NULL, 
								CLSCTX_INPROC_SERVER, 
								IID_IUnknown, 
								(void **)&pUnknown);
	if (hResult != S_OK || NULL == pUnknown){
		printf("Create object failed!\n");
		return -2;
	}
	IComTest* pComTest;

	//通过此 IUnknown 接口查询 IComTest 接口
	hResult = pUnknown->QueryInterface(IID_IComTest, (void **)&pComTest);
	if (hResult != S_OK) {
		pUnknown->Release();
		printf("QueryInterface IComTest failed!\n");
		return -3;
	}

	BOOL re = FALSE;
	printf(">>> 调用组件接口函数:\n");
	re = pComTest->SayHello();	//调用 IComTest 接口中的函数
	if(re){
		printf(">>> 成功调用 COM 组件!\n");
	}else{
		printf(">>> ERROR!\n");
	}

	pComTest->Release();				//释放 IComTest 接口
	if (pUnknown->Release()== 0)		//释放 IUnknown 接口
		printf("The reference count of ComTest object is zero.\n");

	CoUninitialize();				//COM库反初始化
	return 0;
}

int main(int argc, char* argv[])
{
	int re = TestCom();
	system("pause");
	return 0;
}

 以上代码中,当用户调用 CoCreateInstance函数创建类厂接口指针时,会自行调用类厂的CreateInstance函数创建对象,所以不用再进行显示的调用了,这个与 前文《COM的简单介绍》中的最后介绍的协作流程的不是很一致,请注意区分。

4、代码实现

工程采用VS2012编写,全部代码的下载请访问:ComTest.zip下载

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值