天天看點

COM - COM程式設計的簡單實作1、代碼的主要結構2、ComTest3、ComCtrl4、代碼實作

本文主要記錄一下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 為客戶部分(即元件的使用者),具體如下所示:

COM - COM程式設計的簡單實作1、代碼的主要結構2、ComTest3、ComCtrl4、代碼實作

2、ComTest

ComTest 為 VS2012所建立的 Win32項目->DLL 類型的工程。代碼的組成結構如下所示:

COM - COM程式設計的簡單實作1、代碼的主要結構2、ComTest3、ComCtrl4、代碼實作
  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

在該檔案中定義了元件程式必須提供的四個引出函數 DllGetClassObject、DllCanUnloadNow、DllRegisterServer 、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 指令進行元件的注冊:

COM - COM程式設計的簡單實作1、代碼的主要結構2、ComTest3、ComCtrl4、代碼實作

,當注冊成功之後會有以下提示,否則會報錯:

COM - COM程式設計的簡單實作1、代碼的主要結構2、ComTest3、ComCtrl4、代碼實作

此時就會在系統資料庫中看到的注冊資訊

COM - COM程式設計的簡單實作1、代碼的主要結構2、ComTest3、ComCtrl4、代碼實作

使用者詳細代碼如下所示:

#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下載下傳

繼續閱讀