纯C++编写COM组件,不用ATL,不用MFC

19 篇文章 1 订阅
2 篇文章 0 订阅

参考文章:https://blog.csdn.net/shejiannan/article/details/26386271。 在VS2015和VS2019上运行,参考文章的程序需要稍微改动一下,有的地方做了补充。

首先看一下工程结构,VS2019的。

CompTest工程是服务端工程,是COM组件也是一个dll。创建Win32类型的dll工程,命名为CompTest。

CtrlTest是客户端工程,是一个Win32控制台工程。

下面通过客户端的运行逻辑来讲述整个运行流程,先看一下CtrlTest.cpp文件

// CtrlTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include "../CompTest/ICompTest.h"

int main()
{
	CoInitialize(NULL);
	IUnknown* pUnknown = NULL;
	GUID CLSID_CompTestClass;
	HRESULT hResult = CLSIDFromProgID(L"COMCTL.CompTest", &CLSID_CompTestClass);
	if (S_OK != hResult)
	{
		std::cout << "can't find CLSID\n";
		return -1;
	}
	else
	{
		LPOLESTR szCLSID;
		StringFromCLSID(CLSID_CompTestClass, &szCLSID);
		wprintf(L"find CLSID \"%s\"\n", szCLSID);
		CoTaskMemFree(szCLSID);
	}

	hResult = CoCreateInstance(CLSID_CompTestClass, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)& pUnknown);
	if (S_OK != hResult || NULL == pUnknown)
	{
		std::cout << "create failed\n";
		return -1;
	}
	ICompTest* pCompTest = NULL;
	hResult = pUnknown->QueryInterface(IID_ICompTest, (void**)& pCompTest);
	if (SUCCEEDED(hResult))
	{
		std::cout << pCompTest->HelloWorld()<<std::endl;
		pCompTest->Release();
	}
	pUnknown->Release();
	CoUninitialize();
	system("pause");
}

这是客户程序的主逻辑,主要就是通过COM库创建CompTestClass对象,这个对象在这里是不可见的,这里只能拿到ICompTest接口,通过该接口调用函数HelloWorld。

我们可以在 CtrlTest.cpp文件中看到CLSIDFromProgID和CoCreateInstance这两个函数,

第一个函数是要通过一个名字"COMCTL.CompTest"拿到一个CLSID,这个过程需要CLSID信息。

第二个函数是要通过这个CLSID找到我们的组件(dll),并加载这个dll,然后创建COM对象,这个过程需要dll的路径信息。

下面看一下接口的声明ICompTest.h文件,这个文件客户端和服务端是相同的。

#pragma once
#include <Unknwn.h>

// {506FA5EA-4359-4A3D-8D17-57F26D6094EF}
static const GUID IID_ICompTest =
{ 0x506fa5ea, 0x4359, 0x4a3d, { 0x8d, 0x17, 0x57, 0xf2, 0x6d, 0x60, 0x94, 0xef } };

class ICompTest : public IUnknown
{
public:
	virtual char* _stdcall HelloWorld() = 0;
};

这个文件中有一个GUID IID_ICompTest ,用于查询接口ICompTest 。

这个GUID是怎么来的呢?有两种方法,第一通过程序生成,使用CoCreateGuid函数。这个不详细介绍。第二通过软件生成。VS自带生成工具guidgen.exe,在VS的安装路径下查找。运行起来如下图。

下面我们看一下这个dll的注册过程。

用“regsvr32.exe dll路径”对dll进行注册,实际上regsvr32只是调用了dll中的DllRegisterServer引出函数。

管理员权限运行cmd提示符,切换目录到dll的所在目录,然后命令注册dll

regsvr32 CompTest.dll

下面我们看一下DllRegisterServer函数的实现,这个函数在CompTest.cpp中。

int myReg(LPCWSTR lpPath)	//use to register this component to registry, including CLSID, lpPath, ProgID
{
	HKEY thk, tclsidk;
	//open Key HKEY_CLASSES_ROOT\CLSID, create new CLSID which key is CompTestClass
	//under this key create InprocServer32, and set current component dll lpPath to default key value

	if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk))
	{
		if (ERROR_SUCCESS == RegCreateKey(thk, L"{A175900B-D6F9-4819-8361-0288CAB13B63}", &tclsidk))
		{
			HKEY tinps32k;
			if (ERROR_SUCCESS == RegCreateKey(tclsidk, L"InprocServer32", &tinps32k))
			{
				if (ERROR_SUCCESS == RegSetValue(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath) * 2))
				{
				}
				RegCloseKey(tinps32k);
			}
			RegCloseKey(tclsidk);
		}
		RegCloseKey(thk);
	}

	//under key HKEY_CLASSES_ROOT, create new key COMCTL.ComTest
	//create sub key under this new key, and set CLSID of CompTestClass as default value
	if (ERROR_SUCCESS == RegCreateKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk))
	{
		if (ERROR_SUCCESS == RegCreateKey(thk, L"CLSID", &tclsidk))
		{
			if (ERROR_SUCCESS == RegSetValue(tclsidk,
				NULL,
				REG_SZ,
				L"{A175900B-D6F9-4819-8361-0288CAB13B63}",
				wcslen(L"{A175900B-D6F9-4819-8361-0288CAB13B63}")*2))
			{
				cout << "register success" << endl;
			}
			RegCloseKey(tclsidk);
		}
		RegCloseKey(thk);
	}
	return 0;
}

extern "C" HRESULT _stdcall DllRegisterServer()
{
	WCHAR szModule[1024];
	DWORD dwResult = GetModuleFileName(g_hModule, szModule, 1024);
	if (0 == dwResult)
	{
		return -1;
	}
	MessageBox(NULL, szModule, L"path", MB_OK);
	myReg(szModule);
	return 0;
}

用“regsvr32.exe dll路径 -u”对dll进行反注册,同样,实际上regsvr32只是调用了dll中的DllUnregisterServer引出函数。

下面我们来看一下DllUnregisterServer函数的实现,这个函数在CompTest.cpp中。

int myDelKey(HKEY hk, LPCWSTR lp)
{
	if (ERROR_SUCCESS == RegDeleteKey(hk, lp))
	{
	}
	return 0;
}

int myDel()
{
	HKEY thk;
	if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk))
	{
		myDelKey(thk, L"{A175900B-D6F9-4819-8361-0288CAB13B63}\\InprocServer32");
		myDelKey(thk, L"{A175900B-D6F9-4819-8361-0288CAB13B63}");

		RegCloseKey(thk);
	}
	if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk))
	{
		myDelKey(thk, L"CLSID");
	}
	myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest");
	return 0;
}

extern "C" HRESULT _stdcall DllUnregisterServer()
{
	myDel();
	return 0;
}

我们继续分析客户端的代码CoCreateInstance(CLSID_CompTestClass, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown,(void **)&pUnknown);

这个函数是要调用CoGetClassObject函数,来获取CompTestClass的类厂,以此创建CompTestClass对象并获取IUnknown接口。其中,CoGetClassObject函数

实际上是调用了CompTest.cpp中的又一个引出函数DllGetClassObject来获取IClassFactory接口的。最终CoCreateInstance会调用IClassFactory接口的CreateInstance

函数去创建COM对象。

下面我们看一下DllGetClassObject函数的实现

extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv)
{
	if (CLSID_CompTestClass == rclsid)
	{
		CompTestFactory* pFactory = new CompTestFactory();
		if (NULL == pFactory)
		{
			return E_OUTOFMEMORY;
		}
		HRESULT result = pFactory->QueryInterface(riid, ppv);
		return result;
	}
	else
	{
		return CLASS_E_CLASSNOTAVAILABLE;
	}
}

接下来我们看一下组件中的最后一个引出函数DllCanUnloadNow,这样dll中的所有引出函数就都出现了

extern "C" HRESULT _stdcall DllCanUnloadNow()
{
	if (0 == g_num)
	{
		return S_OK;
	}
	else
	{
		return S_FALSE;
	}
}

其中ULONG g_num表示组件中CompTestClass对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载。

下面我们看一下CompTest.cpp的全貌。

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include "factory.h"
#include "CompTestClass.h"
using namespace std;

HMODULE g_hModule;	//dll process handler
ULONG g_num;		// the num of object in CompTestClass, use to judge if can unload component, if 0 can unload

int myReg(LPCWSTR lpPath)	//use to register this component to registry, including CLSID, lpPath, ProgID
{
	HKEY thk, tclsidk;
	//open Key HKEY_CLASSES_ROOT\CLSID, create new CLSID which key is CompTestClass
	//under this key create InprocServer32, and set current component dll lpPath to default key value

	if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk))
	{
		if (ERROR_SUCCESS == RegCreateKey(thk, L"{A175900B-D6F9-4819-8361-0288CAB13B63}", &tclsidk))
		{
			HKEY tinps32k;
			if (ERROR_SUCCESS == RegCreateKey(tclsidk, L"InprocServer32", &tinps32k))
			{
				if (ERROR_SUCCESS == RegSetValue(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath) * 2))
				{
				}
				RegCloseKey(tinps32k);
			}
			RegCloseKey(tclsidk);
		}
		RegCloseKey(thk);
	}

	//under key HKEY_CLASSES_ROOT, create new key COMCTL.ComTest
	//create sub key under this new key, and set CLSID of CompTestClass as default value
	if (ERROR_SUCCESS == RegCreateKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk))
	{
		if (ERROR_SUCCESS == RegCreateKey(thk, L"CLSID", &tclsidk))
		{
			if (ERROR_SUCCESS == RegSetValue(tclsidk,
				NULL,
				REG_SZ,
				L"{A175900B-D6F9-4819-8361-0288CAB13B63}",
				wcslen(L"{A175900B-D6F9-4819-8361-0288CAB13B63}")*2))
			{
				cout << "register success" << endl;
			}
			RegCloseKey(tclsidk);
		}
		RegCloseKey(thk);
	}
	return 0;
}

extern "C" HRESULT _stdcall DllRegisterServer()
{
	WCHAR szModule[1024];
	DWORD dwResult = GetModuleFileName(g_hModule, szModule, 1024);
	if (0 == dwResult)
	{
		return -1;
	}
	MessageBox(NULL, szModule, L"path", MB_OK);
	myReg(szModule);
	return 0;
}

int myDelKey(HKEY hk, LPCWSTR lp)
{
	if (ERROR_SUCCESS == RegDeleteKey(hk, lp))
	{
	}
	return 0;
}

int myDel()
{
	HKEY thk;
	if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk))
	{
		myDelKey(thk, L"{A175900B-D6F9-4819-8361-0288CAB13B63}\\InprocServer32");
		myDelKey(thk, L"{A175900B-D6F9-4819-8361-0288CAB13B63}");

		RegCloseKey(thk);
	}
	if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk))
	{
		myDelKey(thk, L"CLSID");
	}
	myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest");
	return 0;
}

extern "C" HRESULT _stdcall DllUnregisterServer()
{
	myDel();
	return 0;
}
							
extern "C" HRESULT _stdcall DllCanUnloadNow()
{
	if (0 == g_num)
	{
		return S_OK;
	}
	else
	{
		return S_FALSE;
	}
}

extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv)
{
	if (CLSID_CompTestClass == rclsid)
	{
		CompTestFactory* pFactory = new CompTestFactory();
		if (NULL == pFactory)
		{
			return E_OUTOFMEMORY;
		}
		HRESULT result = pFactory->QueryInterface(riid, ppv);
		return result;
	}
	else
	{
		return CLASS_E_CLASSNOTAVAILABLE;
	}
}

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;
}

下面是.def文件mydef.def用于声明dll的引出函数。在注册dll时候会用到。需要在工程“属性->配置属性->链接器->输入->模块定义文件”中添加mydef.def。内容如下:

LIBRARY "CompTest"
EXPORTS
DllCanUnloadNow        PRIVATE
DllGetClassObject    PRIVATE
DllUnregisterServer    PRIVATE
DllRegisterServer    PRIVATE

下面是剩下的文件,

CompTestClass.h, 这里面的GUID也是生成的。

#pragma once
#include "ICompTest.h"

// {A175900B-D6F9-4819-8361-0288CAB13B63}
static const GUID CLSID_CompTestClass =
{ 0xa175900b, 0xd6f9, 0x4819, { 0x83, 0x61, 0x2, 0x88, 0xca, 0xb1, 0x3b, 0x63 } };

class CompTestClass :
	public ICompTest
{
public:
	CompTestClass();
	~CompTestClass();

	virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject);
	virtual ULONG _stdcall AddRef();
	virtual ULONG _stdcall Release();

	virtual char* _stdcall HelloWorld();

	static int Init();
	ULONG ObjNum();
protected:
	ULONG m_Ref;
	static ULONG m_objNum;
};

CompTestClass.cpp

#include "pch.h"
#include "CompTestClass.h"
#include "MyLock.h"

ULONG CompTestClass::m_objNum = 0;
CompTestClass::CompTestClass() {
	m_Ref = 0;
	AutoLock tlock;
	m_objNum++;
}

CompTestClass::~CompTestClass() {
	AutoLock tlock;
	m_objNum--;
}

HRESULT _stdcall CompTestClass::QueryInterface(const IID& riid, void** ppvObject)
{
	if (IID_IUnknown == riid)
	{
		*ppvObject = (IUnknown*)this;
		((IUnknown*)(*ppvObject))->AddRef();
	}
	else if (IID_ICompTest == riid)
	{
		*ppvObject = (ICompTest*)this;
		((ICompTest*)(*ppvObject))->AddRef();
	}
	else {
		*ppvObject = NULL;
		return E_NOINTERFACE;
	}
	return S_OK;
}

ULONG _stdcall CompTestClass::AddRef()
{
	m_Ref++;
	return m_Ref;
}

ULONG _stdcall CompTestClass::Release()
{
	m_Ref--;
	if (0 == m_Ref)
	{
		delete this;
		return 0;
	}
	return m_Ref;
}

char* _stdcall CompTestClass::HelloWorld()
{
	return (char*)"hello world";
}

int CompTestClass::Init()
{
	m_objNum = 0;
	return 0;
}

ULONG CompTestClass::ObjNum()
{
	return m_objNum;
}

factory.h

#pragma once
#include <Unknwnbase.h>
class CompTestFactory :
	public IClassFactory
{
public:
	CompTestFactory();
	~CompTestFactory();

	virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject);
	virtual ULONG _stdcall AddRef();
	virtual ULONG _stdcall Release();

	virtual HRESULT _stdcall CreateInstance(IUnknown* pUnknown, const IID& riid, void** ppvObject);
	virtual HRESULT _stdcall LockServer(BOOL fLock);
protected:
	ULONG m_Ref;
};

factory.cpp

#include "pch.h"
#include "factory.h"
#include "CompTestClass.h"

CompTestFactory::CompTestFactory()
{
	m_Ref = 0;
}

CompTestFactory::~CompTestFactory()
{
}

HRESULT _stdcall CompTestFactory::QueryInterface(const IID& riid, void** ppvObject)
{
	if (IID_IUnknown == riid)
	{
		*ppvObject = (IUnknown*)this;
		((IUnknown*)(*ppvObject))->AddRef();
	}
	else if (IID_IClassFactory == riid)
	{
		*ppvObject = (IClassFactory*)this;
		((IClassFactory*)(*ppvObject))->AddRef();
	}
	else
	{
		*ppvObject = NULL;
		return E_NOINTERFACE;
	}
	return S_OK;
}

ULONG _stdcall CompTestFactory::AddRef()
{
	m_Ref++;
	return m_Ref;
}

ULONG _stdcall CompTestFactory::Release()
{
	m_Ref--;
	if (0 == m_Ref)
	{
		delete this;
		return 0;
	}
	return m_Ref;
}

HRESULT _stdcall CompTestFactory::CreateInstance(IUnknown* pUnkOuter, const IID& riid, void** ppvObject)
{
	if (NULL != pUnkOuter)
	{
		return CLASS_E_NOAGGREGATION;
	}
	HRESULT hr = E_OUTOFMEMORY;
	CompTestClass::Init();
	CompTestClass* pObj = new CompTestClass();
	if (NULL == pObj)
	{
		return hr;
	}
	hr = pObj->QueryInterface(riid, ppvObject);
	if (S_OK != hr)
	{
		delete pObj;
	}
	return hr;
}

HRESULT _stdcall CompTestFactory::LockServer(BOOL fLock)
{
	return NOERROR;
}

MyLock.h,原来的程序中用到了锁,推测类似这个样子。

#pragma once
#include <Windows.h>

class AutoLock
{
public:
	AutoLock()
	{
		InitializeCriticalSection(&m_cSection);
		EnterCriticalSection(&m_cSection);
	}
	~AutoLock()
	{
		LeaveCriticalSection(&m_cSection);
		DeleteCriticalSection(&m_cSection);
	}
private:
	CRITICAL_SECTION m_cSection;
};

运行步骤:生成dll后,使用命令注册dll,会提示注册成功。然后运行exe程序调用COM组件,得到如下的输出:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值