创建COM组件全过程(C++)

 一:创建一个接口

       

typedef  struct  Interface

Interface ISimpleMsgBox : public IUnknown
{
    // IUnknown
    STDMETHOD_(ULONG, AddRef)() PURE;
    STDMETHOD_(ULONG, Release)() PURE;
    STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE;

    // ISimpleMsgBox
    STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText) PURE;
};

替代方案:使用idl文件定义接口,其会生成相关头文件,但可读性很差

二.声明一个C++类实现该接口

   

class CSimpleMsgBoxImpl : public ISimpleMsgBox  
{
public:
    CSimpleMsgBoxImpl();
    virtual ~CSimpleMsgBoxImpl();

    // IUnknown
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();
    STDMETHOD(QueryInterface)(REFIID riid, void** ppv);

    // ISimpleMsgBox
    STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText);

protected:
    ULONG m_uRefCount;
};

实现部分,只关注QueryInterface 和DoSimpleMsgBox 部分

STDMETHODIMP CSimpleMsgBoxImpl::QueryInterface ( REFIID riid, void** ppv )
{
HRESULT hrRet = S_OK;

    TRACE(">>> SimpleMsgBoxSvr: In CSimpleMsgBoxImpl::QueryInterface()\n" );

    // Check that ppv really points to a void*.

    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    // Standard QI initialization - set *ppv to NULL.

    *ppv = NULL;

    // If the client is requesting an interface we support, set *ppv.

    if ( InlineIsEqualGUID ( riid, IID_IUnknown ))
        {
        *ppv = (IUnknown*) this;
        TRACE(">>> SimpleMsgBoxSvr: Client QIed for IUnknown\n" );
        }
    else if ( InlineIsEqualGUID ( riid, __uuidof(ISimpleMsgBox) ))
        {
        *ppv = (ISimpleMsgBox*) this;
        TRACE(">>> SimpleMsgBoxSvr: Client QIed for ISimpleMsgBox\n" );
        }
    else
        {
        hrRet = E_NOINTERFACE;
        TRACE(">>> SimpleMsgBoxSvr: Client QIed for unsupported interface\n" );
        }

    // If we're returning an interface pointer, AddRef() it.

    if ( S_OK == hrRet )
        {
        ((IUnknown*) *ppv)->AddRef();
        }

    return hrRet;
}

//
// ISimpleMsgBox methods

STDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )
{
_bstr_t bsMsg = bsMessageText;
LPCTSTR szMsg = (TCHAR*) bsMsg;         // Use _bstr_t to convert the string to ANSI if necessary.

    TRACE(">>> SimpleMsgBoxSvr: In CSimpleMsgBoxImpl::DoSimpleMsgBox()\n" );

    MessageBox ( hwndParent, szMsg, _T("Simple Message Box"), MB_OK );

    return S_OK;
}

三.为接口和类指定各自指定一个GUID

 使用VC++扩展方法

struct __declspec(uuid("{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}")) ISimpleMsgBox;
class  __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;

这样便可以使用__uuidof关键字了

__uuidof使用方法介绍

属于VC++扩展语法

 

class Demo
{
};
class __declspec(uuid("B372C9F6-1959-4650-960D-73F20CD479BA")) Demo; 

int main( void )
{
    CLSID clsid = __uuidof(Demo); 
}


 

四.创建类厂(必须创建)

  每个类必须配有一个类厂,关注CreateInstance 和LockServer 方法.
CoCreateInstance方法内部会生命周期依赖这个接口

//
// IClassFactory methods

STDMETHODIMP CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                                                         REFIID    riid,
                                                         void**    ppv )
{
HRESULT hrRet;
CSimpleMsgBoxImpl* pMsgbox;

    TRACE(">>> SimpleMsgBoxSvr: In CSimpleMsgBoxClassFactory::CreateInstance()\n" );

    // We don't support aggregation, so pUnkOuter must be NULL.

    if ( NULL != pUnkOuter )
        return CLASS_E_NOAGGREGATION;

    // Check that ppv really points to a void*.

    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    *ppv = NULL;

    // Create a new COM object!

    pMsgbox = new CSimpleMsgBoxImpl;

    if ( NULL == pMsgbox )
        return E_OUTOFMEMORY;

    // QI the object for the interface the client is requesting.

    hrRet = pMsgbox->QueryInterface ( riid, ppv );

    // If the QI failed, delete the COM object since the client isn't able
    // to use it (the client doesn't have any interface pointers on the object).

    if ( FAILED(hrRet) )
        delete pMsgbox;

    return hrRet;
}

STDMETHODIMP CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock )
{
    // Increase/decrease the DLL ref count, according to the fLock param.

    fLock ? g_uDllLockCount++ : g_uDllLockCount--;

    TRACE(">>> SimpleMsgBoxSvr: In CSimpleMsgBoxClassFactory::LockServer(), new DLL ref count = %d\n", g_uDllLockCount );
    
    return S_OK;
}


 

五.注册COM组件(手动注册)

万事俱备,现在要将其信息写入注册表,

1.实现DllRegisterServer和DllUnregisterServer方法,并将方法声明为STDAPI,表明属于导出函数

// DllRegisterServer() creates the registy entries that tells COM where our 
// server is located and its threading model.
STDAPI DllRegisterServer()
{
HKEY  hCLSIDKey = NULL, hInProcSvrKey = NULL;
LONG  lRet;
TCHAR szModulePath [MAX_PATH];
TCHAR szClassDescription[] = _T("SimpleMsgBox class");
TCHAR szThreadingModel[]   = _T("Apartment");

__try
    {
    // Create a key under CLSID for our COM server.

    lRet = RegCreateKeyEx ( HKEY_CLASSES_ROOT, _T("CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}"),
                            0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE | KEY_CREATE_SUB_KEY,
                            NULL, &hCLSIDKey, NULL );
    
    if ( ERROR_SUCCESS != lRet ) 
        return HRESULT_FROM_WIN32(lRet);

    // The default value of the key is a human-readable description of the coclass.

    lRet = RegSetValueEx ( hCLSIDKey, NULL, 0, REG_SZ, (const BYTE*) szClassDescription,
                           sizeof(szClassDescription) );

    if ( ERROR_SUCCESS != lRet )
        return HRESULT_FROM_WIN32(lRet);
    
    // Create the InProcServer32 key, which holds info about our coclass.

    lRet = RegCreateKeyEx ( hCLSIDKey, _T("InProcServer32"), 0, NULL, REG_OPTION_NON_VOLATILE,
                            KEY_SET_VALUE, NULL, &hInProcSvrKey, NULL );

    if ( ERROR_SUCCESS != lRet )
        return HRESULT_FROM_WIN32(lRet);

    // The default value of the InProcServer32 key holds the full path to our DLL.

    GetModuleFileName ( g_hinstThisDll, szModulePath, MAX_PATH );

    lRet = RegSetValueEx ( hInProcSvrKey, NULL, 0, REG_SZ, (const BYTE*) szModulePath, 
                           sizeof(TCHAR) * (lstrlen(szModulePath)+1) );

    if ( ERROR_SUCCESS != lRet )
        return HRESULT_FROM_WIN32(lRet);

    // The ThreadingModel value tells COM how it should handle threads in our DLL.
    // The concept of apartments is beyond the scope of this article, but for
    // simple, single-threaded DLLs, use Apartment.

    lRet = RegSetValueEx ( hInProcSvrKey, _T("ThreadingModel"), 0, REG_SZ,
                           (const BYTE*) szThreadingModel, 
                           sizeof(szThreadingModel) );

    if ( ERROR_SUCCESS != lRet )
        return HRESULT_FROM_WIN32(lRet);
    }   
__finally
    {
    if ( NULL != hCLSIDKey )
        RegCloseKey ( hCLSIDKey );

    if ( NULL != hInProcSvrKey )
        RegCloseKey ( hInProcSvrKey );
    }

    return S_OK;
}

// DllUnregisterServer() deleted the registy entries that DllRegisterServer() created.
STDAPI DllUnregisterServer()
{
    // Delete our registry entries.  Note that you must delete from the deepest
    // key and work upwards, because on NT/2K, RegDeleteKey() doesn't delete 
    // keys that have subkeys on NT/2K.

    RegDeleteKey ( HKEY_CLASSES_ROOT, _T("CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}\\InProcServer32") );
    RegDeleteKey ( HKEY_CLASSES_ROOT, _T("CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}") );

    return S_OK;
}


 

2.定义def文件

EXPORTS
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE


 

3.编译通过后使用regsvr32命令,注册此dll组件

在调用此com组件之前,必须通过cmd命令注册,很重要,DllRegisterServer将会成为入口点,

 

六.编写DllGetClassObject

只有通过全局函数DllGetClassObject,才可以创建类厂,这个方法com类库会去调用,其会根据CLSID返回一个类工厂(一个dll可能会有多个com类)

生命周期(非常重要):

CoCreateInstance->CoGetClassObject->DllGetClassObject->IClassFactory->CreateInstance

STDAPI DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv )
{
HRESULT hrRet;
CSimpleMsgBoxClassFactory* pFactory;

    TRACE(">>> SimpleMsgBoxSvr: In DllGetClassObject()\n");

    // Check that the client is asking for the CSimpleMsgBoxImpl factory.

    if ( !InlineIsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) ))
        return CLASS_E_CLASSNOTAVAILABLE;

    // Check that ppv really points to a void*.

    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    *ppv = NULL;

    // Construct a new class factory object.

    pFactory = new CSimpleMsgBoxClassFactory;

    if ( NULL == pFactory )
        return E_OUTOFMEMORY;

    // AddRef() the factory since we're using it.

    pFactory->AddRef();

    // QI() the factory for the interface the client wants.

    hrRet = pFactory->QueryInterface ( riid, ppv );
    
    // We're done with the factory, so Release() it.

    pFactory->Release();

    return hrRet;
}

七.调用com组件

void DoMsgBoxTest(HWND hMainWnd)
{
ISimpleMsgBox* pIMsgBox;
HRESULT hr;
    hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl), NULL, CLSCTX_INPROC_SERVER,
                            __uuidof(ISimpleMsgBox), (void**) &pIMsgBox );

    if ( FAILED(hr) )
        return;

    pIMsgBox->DoSimpleMsgBox ( hMainWnd, _bstr_t("Hello COM!") );

    pIMsgBox->Release();
}

以上步骤不可以省略,可以看到创建一个com组件是比较麻烦的一个流程,很容易出错

或者先常见一个类工厂

HRESULT hr;
IClassFactory *pc=NULL;
hr=::CoGetClassObject(__uuidof(CSimpleMsgBoxImpl), CLSCTX_INPROC_SERVER,NULL, 
    IID_IClassFactory, (void**) &pc );


 


 


 

 

 

 


 



 

 

组件基础 1 软件开发的阶段 1.1 结构化编程 采用自顶向下的编程方式,划分模块 和功能的一种编程方式。 1.2 面向对象编程 采用对象的方式,将程序抽象成类, 模拟现实世界,采用继承、多态的方式 设计软件的一种编程方式。 1.3 面向组件编程 将功能和数据封装成二进制代码,采用 搭积木的方式实现软件的一种编程方式。 2 组件和优点 2.1 组件 - 实际是一些可以执行的二进 制程序,它可以给其他的应用程序、操 作系统或其他组件提供功能 2.2 优点 2.2.1 可以方便的提供软件定制机制 2.2.2 可以很灵活的提供功能 2.2.3 可以很方便的实现程序的分布式 开发。 3 组件的标准 - COMComponent Object Model ) 3.1 COM是一种编程规范,不论任何开发语言 要实现组件都必须按照这种规范来实现。 组件和开发语言无关。 这些编程规范定义了组件的操作、接口的 访问等等。 3.2 COM接口 COM接口是组件的核心,从一定程度上 讲"COM接口是组件的一切". COM接口给用户提供了访问组件的方式. 通过COM接口提供的函数,可以使用组件 的功能. 4 COM组件 4.1 COM组件-就是在Windows平台下, 封装在动态库(DLL)或者可执行文件(EXE) 中的一段代码,这些代码是按照COM的 规范实现. 4.2 COM组件的特点 4.2.1 动态链接 4.2.2 与编程语言无关 4.2.3 以二进制方式发布 二 COM接口 1 接口的理解 DLL的接口 - DLL导出的函数 类的接口 - 类的成员函数 COM接口 - 是一个包含了一组函数指针 的数据结构,这些函数是由组件实现的 2 C++的接口实现 2.1 C++实现接口的方式,使用抽象类 定义接口. 2.2 基于抽象类,派生出子类并实现 功能. 2.3 使用 interface 定义接口 interface ClassA { }; 目前VC中,interface其实就是struct 3 接口的动态导出 3.1 DLL的实现 3.1.1 接口的的定义 3.1.2 接口的实现 3.1.3 创建接口的函数 3.2 DLL的使用 3.2.1 加载DLL和获取创建接口的函数 3.2.2 创建接口 3.2.3 使用接口的函数 4 接口的生命期 4.1 问题 在DLL中使用new创建接口后,在用户 程序使用完该接口后,如果使用delete 直接删除,会出现内存异常. 每个模块有自己的内存堆(crtheap) EXE - crtheap DLL - crtheap new/delete/malloc/free默认情况 下都是从自己所在模块内存堆(crtheap) 中分配和施放内存.而各个模块的 这个内存堆是各自独立.所以在DLL中 使用new分配内存,不能在EXE中delete. 4.2 引用计数和AddRef/Release函数 引用计数 - 就是一个整数,作用是 表示接口的使用次数 AddRef - 增加引用计数 +1 Release - 减少引用计数 -1, 如果 当引用计数为0,接口被删除 4.3 使用 4.3.1 创建接口 4.3.2 调用AddRef,增加引用计数 4.3.3 使用接口 4.3.4 调用Release,减少引用计数 4.4 注意 4.4.1 在调用Release之后,接口指针 不能再使用 4.4.2 多线程情况下,接口引用计数 要使用原子锁的方式进行加减 5 接口的查询 5.1 每个接口都具有唯一标识 GUID 5.2 实现接口查询函数 QueryInterface 6 IUnknown 接口 6.1 IUnknown是微软定义的标准接口 我们实现所有接口就是继承这个接口 6.2 IUnknown定义了三个函数 QueryInterface 接口查询函数 AddRef 增加引用计数 Release 减少引用计数 7 接口定义语言 - IDL(Interface Definition Language ) 7.1 IDL和MIDL IDL - 定义接口的一种语言,与开发 语言无关. MIDL.EXE - 可以将IDL语言定义接口, 编译成C++语言的接口定义 7.2 IDL的基础 import "XXXX.idl" [ attribute ] interface A : interface_base { } 7.2.1 Import 导入,相当于C++的 #include 7.2.2 使用"[]"定义区域,属性描述 关键字 1) object - 后续是对象 2) uuid - 定义对象GUID 3) helpstring - 帮助信息 4) version - 版本 5) point_default - 后续对象 中指针的默认使用方式 比如: uniqune - 表示指针可以 为空,但是不能修改 7.2.3 对象定义 1) 父接口是IUnknown接口 2) 在对象内添加函数,函数定义必须 是返回 HRESULT. HRESULT是32位整数,返回函数是否 执行成功,需要使用 SUCCESSED和 FAILED宏来判断返回值.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值