参考文章: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组件,得到如下的输出: