手把手教你写Com(Step by Step COM Tutorial)

Step by Step COM Tutorial

转载地址: http://www.codeguru.com/cpp/com-tech/activex/tutorials/article.php/c5567/Step-by-Step-COM-Tutorial.htm

Introduction(简介)

For me , understanding COM (Component Object Model) has been no less than an odyssey. I believe that every programmer who wishes to understand the basic principles behind COM, must write atleast one simple COM object using plain C++ , i.e. without the aid of templates and macros that comes along with MFC/ATL. In this article I present the guidelines for creating simple COM objects from first principles. The components should be usable by both VC/VB clients.

对于我来说,理解Com组件对象模型,不亚于一场长途冒险的行程。我相信,每一个程序猿,都希望能理解Com背后的基本原理,使用C++以简单直白的方式写一个Com的例子。比如,不借助于MFC和ATL中的模板类和宏定义。在这篇文章中,我将向你展示创建简单的Com对象模型。本Com组件可以使用VC和VB调用。



As an exercise we will attempt to design a COM component that will implement a hypothetical super-fast addition algorithm. The component must take in two parametes of long data type, and return to the user another long parameter that will be an outcome of our addition algorithm.We will begin with designing the interface.

我们尝试实现一个假想的超级加法计算器功能的Com组件。该组件传入两个Long型的参数,返回一个计算结果的参数。接下来我们来设计接口。


Interface(接口)

The interface of a COM object does not talk about the actual implementation, but the method signatures that will be used by others to communicate with the COM object. We will name our interface as IAdd. The declaration for this interface will be done using the Interface Definition Language (IDL). IDL is a language used for defining function signatures, in a format that is independent of the programming language, and this helps the RPC infrastructure to pack, ship and unpack parameters from one computer to another. In our interface IAdd, we will have methods SetFirstNumber and SetSecondNumber which will be used for passing the parameters for addition. There will be another method , DoTheAddition, that will actually do the addition and give back to the client the result.

我们的Com接口并不是讨论真实的实现功能,而是函数声明,这样就可以被其他Com客户端调用。我们将接口命名为IAdd。接口的声明是在IDL( Interface Definition Language)文件中实现的。IDL是一种独立于编程语言的函数的声明文件,它能够实现从一台计算机到另一台计算机的远程过程调用协议结构参数的打包,传输和解包。在我们的IAdd接口中,我们有两个方法SetFirstNumber和SetSecondNumber为加法器传参数。还有另一个DoTheAddition方法做加法运算并向Com客户端返回加法的运算结果。


Step 1:

Create a new Win32 DLL project (AddObj say). Create all the subsequent files in this folder. Create a blank file and keyin the following contents. Save it as IAdd.idl. The interface identifiers have been generated using the tool uuidgen.exe。

我们创建一个名称为AddObj的Win32 DLL工程,接下来所有的文件都在这个目录中创建。创建一个空白的文件,用下面的并输入下面的内容,保存为IAdd.idl。接口中的标识符使用uuidgen.exe工具创建。


import "unknwn.idl";
 
[
object,
uuid(1221db62-f3d8-11d4-825d-00104b3646c0),
helpstring("interface  IAdd is used for implementing a super-fast addition Algorithm")
]
 
interface IAdd : IUnknown
    {
    HRESULT     SetFirstNumber(long nX1);
 
    HRESULT     SetSecondNumber(long nX2);
    
    HRESULT     DoTheAddition([out,retval] long *pBuffer);
    };
 
[
uuid(3ff1aab8-f3d8-11d4-825d-00104b3646c0),
helpstring("Interfaces for Code Guru algorithm implementations .")
]
library CodeGuruMathLib
    {
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");
 
    interface IAdd;
    }

Step 2:

Compile the file IAdd.idl using the command line compiler MIDL.exe ( note:midl.exe ships with VC++ and incase of any path problems for midl, you may need to fix your path variable settings )

Upon compilation the following files will be generated:

使用VS的命令行编译工具,MIDL.exe(此工具是在VC工具目录下,如果命令行不能运行,可能需要修复下系统的环境变量设置)编译IAdd.idl。通过编译,生成如下文件。


IAdd.h Contains the C++ style interface declarations.
dlldata.c Contains code for proxy DLL. Useful when invoking the object on a different process/computer.
IAdd.tlb Binary file , with a well defined format that completely describes our interface IAdd along with all it's methods. This file is to be distributed to all the clients of our COM component.
IAdd_p.c Contains marshalling code for proxy DLL. Useful while invoking the object on a different process/computer.
IAdd_i.c Contains the interface IIDs

Step 3:

We will create the COM object. Create a new file (AddObj.h), delclare a C++ class , name this class CAddObj. Derive this class from the interface IAdd (file IAdd.h). Remember that , IAdd derives from IUnknown, which is also a abstract base class. Therefore we will have to declare all the methods for the abstract base classes IAddas well as IUnknown.

现在我们来创建COM对象。创建一个AddObj.h文件,用来声明C++类,类名为CAddObj,该类继承IAdd接口。别忘记了,IAdd接口继承于IUnknown抽象基类。因此,我们也需要声明基类IUnknown中的方法。

///
//
//AddObj.h
//Contains the C++ class declarations for implementing the IAdd
//interfaces
//
#include    "IAdd.h"
extern long g_nComObjsInUse;
class CAddObj : 
        public IAdd
    {
    public:
 
    //IUnknown interface 
    HRESULT __stdcall QueryInterface(
                                REFIID riid , 
                                void **ppObj);
    ULONG   __stdcall AddRef();
    ULONG   __stdcall Release();
 
    //IAdd interface
    HRESULT __stdcall SetFirstNumber( long nX1);
    HRESULT __stdcall SetSecondNumber( long nX2);
    HRESULT __stdcall DoTheAddition( long *pBuffer);
 
    private:
 
    long m_nX1 , m_nX2; //operands for addition
    long m_nRefCount;   //for managing the reference count
    };
 
///

Step 4:

We will provide implementations for the all methods of the IAdd interface. Create a new file (AddObj.cpp) and implement the method code here.

我们还需要提供IAdd接口方法的所有实现。新建一个AddObj.cpp文件,实现接口的方法。

///
//
//AddObj.cpp
//Contains the method  implementations of the IAdd interface
//interfaces
//
 
#include    <objbase.h>
 
#include    "AddObj.h"
#include    "IAdd_i.c"
 
HRESULT __stdcall CAddObj::SetFirstNumber( long nX1)
    {
    m_nX1=nX1;
    if (m_bIsLogEnabled) WriteToLog("Junk");
    return S_OK;
    }
 
HRESULT __stdcall CAddObj::SetSecondNumber( long nX2)
    {
    m_nX2=nX2;
    return S_OK;
    }
 
HRESULT __stdcall CAddObj::DoTheAddition( long *pBuffer)
    {
    *pBuffer =m_nX1 + m_nX2;
 
    return S_OK;
    }

Step 5:

IUnknown methods need to be implemented. We will implement the 3 mandatory methods (AddRef, Release and QueryInterface) in the same file AddObj.cpp. The private member m_nRefCount is used for maintainig the object life time. m_nRefCount is not decremented/incremented directly, instead we do it in a thread safe way, using the API InterlockedIncrement and InterlockedDecrement

IUnknown接口中的方法需要被实现。我们在AddObj.cpp文件中实现IUnknown接口中的三个必须要实现的方法(AddRef,Release和QueryInterface)。私有的成员变量m_nrefCount是用来控制对象的生命周期的。m_nrefCount成员变量并不是直接增减的,而是通过使用API函数InterlockedIncrement和InterlockedDecrement,一种线程安全的方式实现的。

HRESULT __stdcall CAddObj::QueryInterface(
                                    REFIID riid , 
                                    void **ppObj)
    {
    if (riid == IID_IUnknown)
        {
        *ppObj = static_cast(this) ; 
        AddRef() ;
        return S_OK;
        }
    if (riid == IID_IAdd)
        {
        *ppObj = static_cast(this) ;
        AddRef() ;
        return S_OK;
        }
    //
    //if control reaches here then , let the client know that
    //we do not satisfy the required interface
    //
    *ppObj = NULL ;
    return E_NOINTERFACE ;
    }//QueryInterface method
ULONG   __stdcall CAddObj::AddRef()
    {
    return InterlockedIncrement(&m_nRefCount) ;
    }
    
	
ULONG   __stdcall CAddObj::Release()
    {     
    long nRefCount=0;
    nRefCount=InterlockedDecrement(&m_nRefCount) ;
    if (nRefCount == 0) delete this;
    return nRefCount;
    }

Step 6:

We have finished with the functionality part of the Add COM object. As per COM guide lines, every COM object must have a separate implementation of the interface IClassFactory. Clients will use this interface to get an instance of our IAdd interface implementation. The interface IClassFactory, like all other COM interfaces, derives from IUnknown. Therefore we will have to provide an implementation of the IUnknown methods, as well as the IClassFactory methods (LockServer and CreateInstance). Create a new file (name it AddObjFactory.cpp) , declare a class CAddFactory here and make this class derive from IClassFactory.

我们已经完成了Add COM对象的部分功能。根据COM指导原则,每个COM对象都要独立实现IClassFactory工厂接口。客户端可以使用该接口去获取IAdd接口的一个实例对象。IClassFactory工厂类接口,和其他COM接口一样,继承于IUnknown。因此,我们还要实现,IUnknown中声明的方法。还有IClassFactory中的方法LockServer和CreateInstance。我们新建一个AddObjfactory.cpp文件,声明一个继承IClassFactory的CAddFactory的类。

///
//
//AddObjFactory.h
//Contains the C++ class declarations for the IClassFactory implementations
//
 
class CAddFactory : public IClassFactory
    {
 
    public:
 
 
    //interface IUnknown methods 
    HRESULT __stdcall QueryInterface(
                                REFIID riid , 
                                void **ppObj);
    ULONG   __stdcall AddRef();
    ULONG   __stdcall Release();
 
 
    //interface IClassFactory methods 
    HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter,
	                                         const IID& iid,
	                                         void** ppv) ;
    HRESULT __stdcall LockServer(BOOL bLock) ; 
 
    private:
    long m_nRefCount;
    };

Step 7:

The CAddFactory methods need to be implemented. Create a new file (AddObjFactory.cpp) and provide the method bodies for all the IUnknown and IClassFactory methods. The AddRef, Release and QueryInterface methods have implementations similar to that of class CAddObj. The method CreateInstance is the place, where the class CAddObj is instantiated and and the requested interface pointer is passed back. The method LockServer has not be given any specific implementation.

需要实现CAddFactory类中的方法,创建AddObjFactory.cpp,提供实现所有IUnknown and IClassFactory接口中声明的函数。AddRef, Release和 QueryInterface方法实现类似于CAddObj类。函数CreateInstance 是接收传入接口指针和实例化CAddObj类。函数LockServer 暂时没有指定的实现功能。

HRESULT __stdcall CAddFactory::CreateInstance(IUnknown* pUnknownOuter,
                                           const IID& iid,
                                           void** ppv) 
    {
    //
    //This method lets the client manufacture components en masse
    //The class factory provides a mechanism to control the way
    //the component is created. Within the class factory the 
    //author of the component may decide to selectivey enable
    //or disable creation as per license agreements 
    //
    //
 
    // Cannot aggregate.
    if (pUnknownOuter != NULL)
        {
        return CLASS_E_NOAGGREGATION ;
        }
 
    //
    // Create an instance of the component.
    //
    CAddObj* pObject = new CAddObj ;
    if (pObject == NULL)
        {
        return E_OUTOFMEMORY ;
        }
 
    //
    // Get the requested interface.
    //
    return pObject->QueryInterface(iid, ppv) ;
    }
 
 
HRESULT __stdcall CAddFactory::LockServer(BOOL bLock) 
    {
    return E_NOTIMPL;
    }

Step 8:

An inprocess COM object is nothing more than a simple Win32 DLL, that obeys a certain protocol. Every COM DLL must have an exported function by the name DllGetClassObject. Clients will invoke this function to get an instance of the class factory (either IUnknown or IClassFactory), followed by invocation of the CreateInstance method. Create a new file (Exports.cpp). Implement DllGetClassObject here.

一个进程内COM组件,不仅仅是一个Win32DLl,它需要遵守指定的协议。每一个COM DLL必须导出DllGetClassObject函数。

STDAPI DllGetClassObject(const CLSID& clsid,
                         const IID& iid,
                         void** ppv)
    {
    //
    //Check if the requested COM object is implemented in this DLL
    //There can be more than 1 COM object implemented in a DLL
    //
 
    if (clsid == CLSID_AddObject)
        {
        //
        //iid specifies the requested interface for the factory object
        //The client can request for IUnknown, IClassFactory,
        //IClassFactory2
        //
        CAddFactory *pAddFact = new CAddFactory;
        if (pAddFact == NULL)
            return E_OUTOFMEMORY;
        else
            {
            return pAddFact->QueryInterface(iid , ppv);
            }
        }
    
 
    //
    //if control reaches here then that implies that the object
    //specified by the user is not implemented in this DLL
    //
 
    return CLASS_E_CLASSNOTAVAILABLE;
    }


Step 9:

Clients need to know when a COM DLL can be unloaded from memory. Deep down,an inprocess COM object gets explicitly loaded by a call to the API LoadLibrary, which brings the specified DLL into the client's process space. An explicitly loaded DLL can be unloaded by a call to FreeLibrary.
COM clients must know when a DLL can be safely unloaded. A client must make sure that there are no instances of any COM object alive , from a particular DLL. To make this accounting simpler , within the COM DLL, we will increment a global variable (g_nComObjsInUse) in the C++ constructors of CAddObj & CAddFactory. Similarly, we will decrement g_nComObjsInUse in their respective destructors.
We will export another COM specified function ,DllCanUnloadNow. The implementation is as follows:

STDAPI DllCanUnloadNow()
    {
    //
    //A DLL is no longer in use when it is not managing any existing objects
    // (the reference count on all of its objects is 0). 
    //We will examine the value of g_nComObjsInUse 
    //
 
    if (g_nComObjsInUse == 0)
        {
        return S_OK;
        }
    else
        {
        return S_FALSE;
        }
 
    }

Step 10:

The location of a COM object has to be entered into the registry. This can be done through an external .REG file or make the DLL export a function DllRegisterServer. To erase the registry contents, we will export another function DllUnregisterServer. The implementations of these two functions are in the file Registry.cpp . A simple tool like regsvr32.exe can be used to load a specified DLL and then execute DllRegisterServer/DllUnregisterServer.

To make the linker export the 4 functions, we will create a module definition file (Exports.def)

;
;contains the list of functions that are being exported from this DLL
;
 
DESCRIPTION     "Simple COM object"
 
EXPORTS
                DllGetClassObject   	PRIVATE
                DllCanUnloadNow     	PRIVATE
                DllRegisterServer   	PRIVATE
                DllUnregisterServer 	PRIVATE

Step 11:

We have to give the finishing touches to our AddObj Win32 DLL project. Insert the file IAdd.idl into the project work space. 


Set the custom build options for this file.


Insert a command line string in the "Post-build step" dialog box for executing regsvr32.exe at the end of every build.


Build the DLL. Inserting the IDL file into the workspace alleviates the need for external compilation, every time the file is modified. Every time we successfuly build our project, the COM object is registered.


Step 12:

To use the AddObj COM object from  Visual Basic , creat a simple EXE project and run the following lines of code. Make sure to add a project reference to the IAdd.tlb typelibrary.

 Dim iAdd As CodeGuruMathLib.iAdd
    Set iAdd = CreateObject("CodeGuru.FastAddition")
 
    iAdd.SetFirstNumber 100
    iAdd.SetSecondNumber 200
 
    MsgBox "total = " & iAdd.DoTheAddition()

Step 13:

The following files were used by us:

IAdd.idl Contains the interface declarations.
AddObj.h Contains the C++ class declaration for the class CAddObj
AddObjFactory.h Contains the C++ class declaration for the class CAddFactory
AddObj.cpp Contains the C++ class implementation for the class CAddObj
AddObj.cpp Contains the C++ class implementation for the class CAddFactory
Exports.cpp Contains the implementations for DllGetClassObject,DllCanUnloadNow & DllMain
Registry.cpp Contains the implementations for DllRegisterServer,DllUnregisterServer
AddObjGuid.h Contains the GUID value for our AddObj COM object.

Step 14:

Along with the AddObj.dll, the type library can also be distributed. To simplify the process, the type library IAdd.tlb can also be embedded as a binary resource file in the AddObj DLL . Henceforth, only the DLL file AddObj.dll needs to be distributed to the clients. 



Step 15:

A Visual C++ client can use use COM interfaces through any of the following:

  • #import "IAdd.tlb" .
  • IAdd.h header file. In this case the DLL vendor must ship the IAdd.h header file along with the DLL.
  • Generate C++ code using some wizard sort of a tool(eg: MFC's Class Wizard)
In Case 1, the compiler creates some intermediate files (.tlh, tli) that contain the expanded interface declarations. Further, the compiler also declares smart interface pointer classes built around the raw interfaces. Smart interface pointer classes make life easier for the COM programmer by properly managing the lifetime of the COM object.
In the following example #import has been done on the file AddObj.dll and not on the IAdd.tlb, because we are shipping the TLB file within the DLL. Otherwise, #import should have been done on the TLB.
Create a new console EXE project in VC++. Type the following contents and compile.

//
///Client.cpp
//
//Demo of client using the COM object from AddObj.dll 
//
 
#include    <objbase.h>
#include    <stdio.h>
#import     "AddObj.dll"
//
//Here we do a #import on the DLL ,you can also do a #import on the .TLB 
//The #import directive generates two files (.tlh/.tli) in the output folders.
//
 
void main()
    {
 
    long n1 =100, n2=200;        
    long nOutPut = 0;
 
    CoInitialize(NULL);
    CodeGuruMathLib::IAddPtr pFastAddAlgorithm;
    //
    //IAddPtr is not the actual interface IAdd, but a template C++ class (_com_ptr_t)
    //that contains an embedded instance of the raw IAdd pointer
    //While destructing , the destructor makes sure to invoke Release() on the internal
    //raw interface pointer. Further, the operator -> has been overloaded to direct all
    //method invocations to the internal raw interface pointer.
    //
    pFastAddAlgorithm.CreateInstance("CodeGuru.FastAddition");
 
    pFastAddAlgorithm->SetFirstNumber(n1);//"->" overloading in action
    pFastAddAlgorithm->SetSecondNumber(n2);
    nOutPut = pFastAddAlgorithm->DoTheAddition();
 
    printf("\nOutput after adding %d & %d is %d\n",n1,n2,nOutPut);
    
 
    }

Downloads

Download source - 114 Kb
Download demo project - 101 Kb




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值