Com 组件基础

Com 组件技术在Windows平台上应用十分广泛,虽然历史久远,但是仍然具有旺盛的生命力。下面提供一个最基础的Com组件供参考学习。

(详细资料见资源文件https://download.csdn.net/download/yuanshenqiang/90532743

一  组件类

// Interface.h
#pragma once
#include <Unknwn.h>
static const WCHAR* IID_ISayHelloStr = L"{213D1B15-9BBA-414A-BAB6-CA5B6CEF0006}";
// PowerShell生成GUID指令: '{'+[guid]::NewGuid().ToString().ToUpper()+'}'
static const GUID IID_ISayHello = { 0x213D1B15, 0x9BBA, 0x414A, { 0xBA, 0xB6, 0xCA, 0x5B, 0x6C, 0xEF, 0x00, 0x06 } };

// SayHello.h
#include "Interface.h"
class SayHelloInterface:public IUnknown
{
public:
    virtual int _stdcall SayHello() = 0;
};

class CSayHello :public SayHelloInterface
{
public:
    CSayHello();
    ~CSayHello();

    // 实现IUnknown接口
    // 查找接口
    // riid : 输入参数,接口id
    // ppvObject : 输出参数,返回相应的接口
    HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject) override;
    // 增加引用计数
    ULONG _stdcall AddRef() override;
    // 减少引用计数
    ULONG _stdcall Release() override;
    int _stdcall SayHello() override;
    //全局创建对象个数
    static ULONG g_ObjNum;
protected:
    //引用计数
    ULONG m_RefCount;
};

// SayHello.cpp --组件类实现
#include "SayHello.h"
#include <stdio.h>

ULONG CSayHello::g_ObjNum = 0;

CSayHello::CSayHello()
{
    m_RefCount = 0;
	InterlockedIncrement(&g_ObjNum);//对象个数+1
}

CSayHello::~CSayHello()
{
	InterlockedDecrement(&g_ObjNum);//对象个数-1
}

HRESULT _stdcall CSayHello::QueryInterface(const IID& riid, void** ppvObject)
{
    // 通过接口id判断返回的接口类型
    if (IID_IUnknown == riid) {
        *ppvObject = static_cast<IUnknown*>(this);
        ((IUnknown*)(*ppvObject))->AddRef();
    }
    else if (IID_ISayHello == riid) {
        *ppvObject = static_caast<SayHelloInterface*>(this);
        ((SayHelloInterface*)(*ppvObject))->AddRef();
    }
    else {
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }
    return S_OK;
}

ULONG _stdcall CSayHello::AddRef()
{
	return InterlockedIncrement(&m_RefCount);
}

ULONG _stdcall CSayHello::Release()
{
	ULONG refCount = InterlockedDecrement(&m_RefCount);
    if (0 >= refCount) {
        delete this;
    }
    return refCount;
}

int _stdcall CSayHello::SayHello()
{
    printf("hello COM\r\n");
    return 0;
}

二 工厂类

//ComFactory.h  -- 工厂类声明
#pragma once
#include <Unknwn.h>
class ComFactory:public IClassFactory
{
public:
    ComFactory();
    ~ComFactory();

    // 实现IUnknown接口
    HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject) override;
    ULONG _stdcall AddRef() override;
    ULONG _stdcall Release() override;

    // 实现IClassFactory接口
    HRESULT _stdcall CreateInstance(IUnknown* pUnkOuter, const IID& riid, void** ppvObject) override;
    HRESULT _stdcall LockServer(BOOL fLock) override;

protected:
    ULONG m_RefCount;//引用计数
    static ULONG g_ObjNum;//全局创建对象个数
};

//ComFactory.cpp  -- 工厂类实现
#include "ComFactory.h"
#include "SayHello.h"

ULONG ComFactory::g_ObjNum = 0;

ComFactory::ComFactory()
{
    m_RefCount = 0;
	InterlockedIncrement(&g_ObjNum);
}

ComFactory::~ComFactory()
{
	InterlockedDecrement(&g_ObjNum);
}

// 查询指定接口
HRESULT _stdcall ComFactory::QueryInterface(const IID& riid, void** ppvObject)
{
    if (IID_IUnknown == riid) {
        *ppvObject = static_cast<IUnknown*>(this);
        ((IUnknown*)(*ppvObject))->AddRef();
    }
    else if (IID_IClassFactory == riid) {
        *ppvObject = static_cast<IClassFactory*>(this);
        ((IClassFactory*)(*ppvObject))->AddRef();
    }
    else {
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }
    return S_OK;
}

ULONG _stdcall ComFactory::AddRef()
{
    return InterlockedIncrement(&m_RefCount);
}

ULONG _stdcall ComFactory::Release()
{
	ULONG refCount = InterlockedDecrement(&m_RefCount);
    if (0 >= refCount) {
        delete this;
    }
    return refCount;
}

// 创建COM对象,并返回指定接口
HRESULT _stdcall ComFactory::CreateInstance(IUnknown* pUnkOuter, const IID& riid, void** ppvObject)
{
    if (NULL != pUnkOuter)
        return CLASS_E_NOAGGREGATION;

    HRESULT hr = E_OUTOFMEMORY;
    CSayHello* pObj = new CSayHello();
    if (NULL == pObj) {
        return hr;
    }
    hr = pObj->QueryInterface(riid, ppvObject);
    if (S_OK != hr) {
        delete pObj;
    }
    return hr;
}

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

三 导出模块

// Server.h   -- 导出模块声明
#pragma once
#include <windows.h>
static const WCHAR* CLSID_CSayHelloStr = L"{4046FA83-57F0-4475-9381-8818BFC50DDF}";
static const GUID CLSID_CSayHello = { 0x4046FA83, 0x57F0, 0x4475, { 0x93, 0x81, 0x88, 0x18, 0xBF, 0xC5, 0x0D, 0xDF } };

extern "C"  HRESULT  __stdcall  DllRegisterServer();
extern "C"  HRESULT  __stdcall  DllUnregisterServer();
extern "C"  HRESULT  __stdcall  DllCanUnloadNow(void);
extern "C"  HRESULT  __stdcall  DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID * ppv);

// Server.cpp -- 导出模块实现
#include "SayHello.h"
#include "ComFactory.h"
#include <iostream>
#include <strsafe.h>
#include "Server.h"

HMODULE g_hModule;  //dll进程实例句柄
ULONG g_num;        //组件中Com对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载

LSTATUS myReg(LPCWSTR lpPath)   //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID
{
    HKEY thk = NULL, tclsidk = NULL;
    //打开键HKEY_CLASSES_ROOT\CLSID,创建新键为ComTest的CLSID,
    //在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值
    LSTATUS status = RegOpenKeyW(HKEY_CLASSES_ROOT, L"CLSID", &thk);
    if (ERROR_SUCCESS == status) 
    {
        status = ERROR_SUCCESS == status?RegCreateKeyExW(
            thk, // 父键句柄
            CLSID_CSayHelloStr,//子键名称
            0,                          // 保留,必须为 0
            nullptr,                    // 类名(通常为 nullptr)
            REG_OPTION_NON_VOLATILE,    // 选项(非易失性)
            KEY_ALL_ACCESS,             // 访问权限
            nullptr,                    // 安全属性(通常为 nullptr)
            &tclsidk,                   // 返回的子键句柄
            nullptr): status;
        HKEY tinps32k = NULL;
        status = ERROR_SUCCESS == status?RegCreateKeyExW(
            tclsidk,
            L"InprocServer32",
            0,                          // 保留,必须为 0
            nullptr,                    // 类名(通常为 nullptr)
            REG_OPTION_NON_VOLATILE,    // 选项(非易失性)
            KEY_ALL_ACCESS,             // 访问权限
            nullptr,                    // 安全属性(通常为 nullptr)
            &tinps32k,                   // 返回的子键句柄
            nullptr):status;
        status = ERROR_SUCCESS == status?RegSetValueW(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath)): status;
        RegCloseKey(tinps32k);
        RegCloseKey(tclsidk);
        RegCloseKey(thk);
    }
    //在键HKEY_CLASSES_ROOT下创建新键为COMCTL.CComTest,
    //在该键下创建子键,并将CCompTest的CLSID写为该键的默认值
    HKEY tprogk = NULL, tprogidk = NULL;
    status = status == ERROR_SUCCESS ? RegCreateKeyW(HKEY_CLASSES_ROOT, L"COMCTL.CSayHello", &tprogk) : status;
    status = status == ERROR_SUCCESS ? RegCreateKeyW(tprogk, L"CLSID", &tprogidk) : status;
    status = status == ERROR_SUCCESS ? RegSetValueW(tprogidk,
                NULL,
                REG_SZ,
                CLSID_CSayHelloStr,
                wcslen(CLSID_CSayHelloStr)) : status;
    RegCloseKey(tprogk);
    RegCloseKey(tprogidk);
    //这样的话一个客户端程序如果想要使用本组件,首先可以以COMCTL.CSayHello为参数调用CLSIDFromProgID函数
    //来获取CSayHello的CLSID,再以这个CLSID为参数调用CoCreateInstance创建COM对象
    return status;
}

extern "C" HRESULT  __stdcall  DllRegisterServer()
{
    WCHAR szModule[1024];
    //获取本组件(dll)所在路径
    DWORD dwResult = GetModuleFileNameW(g_hModule, szModule, 1024);
    if (0 == dwResult) {
        return -1;
    }
    //将路径等信息写入注册表
    return myReg(szModule);
}

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

//删除注册时写入注册表的信息
int myDel()
{
    HKEY thk;
    if (ERROR_SUCCESS == RegOpenKeyW(HKEY_CLASSES_ROOT, L"CLSID", &thk)) {
        wchar_t szKey[256];
        // 删除 CLSID 项
        StringCchPrintfW(szKey, ARRAYSIZE(szKey), L"%s\\InprocServer32", CLSID_CSayHelloStr);
        myDelKey(thk, szKey);
        myDelKey(thk, CLSID_CSayHelloStr);

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

extern "C" HRESULT  __stdcall  DllUnregisterServer()
{
    //删除注册时写入注册表的信息
    myDel();
    return 0;
}

// 用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用
extern "C" HRESULT  __stdcall  DllCanUnloadNow(void)
{
    //如果对象个数为0,则可以卸载
    if (0 == CSayHello::g_ObjNum)
    {
        return S_OK;
    }
    else
    {
        return S_FALSE;
    }
}

//用于创建类厂并返回所需接口,由CoGetClassObject函数调用
extern "C" HRESULT  __stdcall  DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR * ppv)
{
    //LPOLESTR szCLSID;
    //StringFromCLSID(rclsid, &szCLSID);     //将其转化为字符串形式用来输出
    //wprintf(L"rclsid CLSID \"%s\"\n", szCLSID);

    //szCLSID;
    //StringFromCLSID(riid, &szCLSID); 
    //wprintf(L"riid CLSID \"%s\"\n", szCLSID);

    if (CLSID_CSayHello == rclsid) {
        ComFactory* pFactory = new ComFactory();//创建工厂对象
        if (NULL == pFactory) 
            return E_OUTOFMEMORY;
        HRESULT result = pFactory->QueryInterface(riid, ppv);//获取工厂对象(内部校验一下)
		if (S_OK != result)
		{
			//pFactory->Release();
			delete pFactory;
		}
        return result;
    }
    else {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

// 提供DLL入口;对于动态链接库,DllMain是一个可选的入口函数,在COM组件中是必须有的
extern "C"  BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    //获取进程实例句柄,用于获取本组件(dll)路径
    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;
}

 导出符号定义:

// Module.def
LIBRARY SayHello
DESCRIPTION "SayHello Com DLL" 
EXPORTS
DllMain @1
DllRegisterServer @2
DllUnregisterServer @3
DllCanUnloadNow @4
DllGetClassObject @5

四 客户端调用(用作com组件)

#include <iostream>
#include "CSayHelloModule.h"
#include "SayHello.h"
#include <stdio.h>
//#pragma comment(lib,"SayHello.lib") 
int main()
{
    // 初始化COM库
    CoInitialize(NULL);

    SayHelloInterface* pComTest = NULL;
    HRESULT hResult = ERROR_SUCCESS;
    hResult = DllRegisterServer();//要求管理员权限。可以在安装软件的时候,拿到管理员权限,执行命令:regsvr32 SayHello.dll
    // 创建进程内COM组件,返回指定接口
    if(hResult == ERROR_SUCCESS)
    {
        hResult = CoCreateInstance(CLSID_CSayHello, NULL, CLSCTX_INPROC_SERVER, IID_ISayHello, (void**)&pComTest);
        //CoCreateInstance等效以下步骤
        //IClassFactory* pFac = NULL;
        //CoGeClassObject(CLSID_CSayHello,CLSCTX_INPROC_SERVER,NULL,IID_IClassFactory,(void**)&pFac);//会去加载dll库
        //pFac->CreateInstance(NULL, IID_ISayHello, (void**)&pComTest);
        //pFac->Release(); 
    }
    if (S_OK == hResult)
    {
        // 调用接口方法
        printf("%d\r\n", pComTest->SayHello());
        // 释放组件
        pComTest->Release();
    }
    else
        printf("Error Occurs: %d\r\n", hResult);
    // 释放COM库
    DllUnregisterServer();//要求管理员权限。等价于:regsvr32 /u SayHello.dll
    CoUninitialize();
    system("pause");
    return 0;
}

五. 客户端调用(用作普通组件)

#include <iostream>
#include "CSayHelloModule.h"
#include "SayHello.h"
#include <stdio.h>
#pragma comment(lib,"SayHello.lib") 

int main()
{
    IClassFactory* pComFac = NULL;
    ICalc* pComTest = NULL;
    HRESULT hResult = 0;
    hResult = DllGetClassObject(CLSID_CSayHello, IID_IClassFactory,(void**)&pComFac);
    if (S_OK == hResult)
    {
        pComFac->CreateInstance(NULL, IID_ISayHello, (void**)&pComTest);
        // 调用接口方法
        printf("Read Ret: %d\r\n", pComTest->SayHello());
        pComTest->Release();
        pComFac->Release();   
    }
    else
        printf("Error Occurs: %d\r\n", hResult);

    system("pause");
    return 0;
}

六. Reg Free Com 

上面小节4中,需要借助注册表存储GUID 和组件路径作为元数据,但是这种做法会污染操作系统注册表,在安装软件时需要获取管理员权限,将元数据信息写入注册表,对于一些敏感的用户,这种操作是不被允许的。因此,可以借助文件清单(.manifest)的方式将组件元数据嵌入到应用程序,在Com 运行时优先从文件清单加载Com组件,这种方式同时也兼容传统借助注册表实现的com 组件应用,无需做代码上的改动,很方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值