随笔-90  评论-947  文章-0  trackbacks-0

引言

前几天山寨了ATL的COM_INTERFACE,了解了一个COM类的如何进行通用的组织。今天再来学习下COM协议,看看如何实现一个COM组件——当然,也是不能用ATL的,不然就学不到什么了。

 

COM DLL说简单简单,说复杂也很复杂。说简单呢,其实貌似只要导出下面这五个函数就可以了:

DllCanUnloadNow

DllGetClassObject

DllRegisterServer

DllUnregisterServer

DllInstall

(我有点怀疑但不确定DllInstall是不是后来加的,本文中我们先不理它。)

 

前四个函数中,后两个是注册与反注册,就是写写注册表的事情,简单。前面两个,特别是DllGetClassObject,比较关键。

 

先不研究这些,我们先按前两天的方法写个COM类吧。

 

准备一个COM

 

首先,我们定义一个接口 ISampleInterface,以及一个类CSampleClass

Interface.h

#include <Unknwn.h>

 

struct __declspec(uuid("{83C783E3-F989-4E0D-BFC5-631273EDFFDA}"))

ISampleInterface : public IUnknown

{

    STDMETHOD(SampleMethod)() PURE;

};

 

class __declspec(uuid("{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"))

SampleClass;

 

与前面不同的是,这里两个声明都写上了一个UUID,前一个是接口IDIID),后一个是类IDCLSID)。这个文件将是提供给COM的使用者的。

 

SampleClass.h

class SampleClass : public xl::ComClass<SampleClass>,

                    xl::IUnknownImpl<ISampleInterface>

{

public:

    SampleClass();

    ~SampleClass();

 

public:

    STDMETHOD(SampleMethod)();

 

public:

    XL_COM_INTERFACE_BEGIN(SampleClass)

        XL_COM_INTERFACE(ISampleInterface)

    XL_COM_INTERFACE_END()

};

 

SampleClass.cpp

SampleClass::SampleClass()

{

    InterlockedIncrement(&g_nModuleCount);

}

 

SampleClass::~SampleClass()

{

    InterlockedDecrement(&g_nModuleCount);

}

 

STDMETHODIMP SampleClass::SampleMethod()

{

    MessageBox(NULL, _T("SampleMethod called."), _T("Info"), MB_OK | MB_ICONINFORMATION);

    return S_OK;

}

 

 

SampleMethod 就简单调用一个MessageBox意思一下。构造函数和析构函数中两行先不看,后面解释。

 

实现DllCanUnloadNow

这个函数的MSDN文档见:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms690368.aspx

 

函数原型为:

HRESULT __stdcall DllCanUnloadNow(void);

 

函数实现的要求是,当此DLL内的所有COM对象都消亡了的时候,返回S_OK;如果还有COM对象存在,就返回S_FALSE

 

现在我们有一个COMSampleClass,它可能被创建一次,然后引用计数加加减减;也可能被创建多次,每个实例的引用计数同样会被加加减减。当引用计数被减为0的时候,对象将消亡(析构函数被调用)。因此,我们在对象的构造和析构的地方埋点就可以了。

 

定义一个全局变量:

LONG g_nModuleCount = 0;

 

然后看到刚才的SampleClass的构造函数和析构函数中的灰色代码,就可以存对象的创建/销毁计数了。对于多个COM对象的情况,也可以这么搞。

 

然后,DllCanUnloadNow的实现就很简单了:

 

STDAPI DllCanUnloadNow()

{

    return g_nModuleCount > 0 ? S_FALSE : S_OK;

}

 

实现DllGetClassObject

终于到关键的地方了。以前一直弄不懂这个函数。它的MSDN文档见:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms680760.aspx

 

函数原型为:

HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv);

 

第一个参数是要创建的对象的CLSID,很明确。第二个参数有点迷惑,MSDN原文是:

A reference to the identifier of the interface that the caller is to use to communicate with the class object. Usually, this is IID_IClassFactory (defined in the OLE headers as the interface identifier for IClassFactory).

 

Usually”,它是IID_IClassFactory。我不知道有没有不“Usually”的情况,也不知道这个接口原先的设计意图是什么。如果可能,其实完全可以绕开类厂机制,直接用想要使用的那个接口的IID,貌似整套机制也能运转……有木有达人解释下它的渊源?

 

不过呢,目前我就把它当作IID_IClassFactory,其他一律不支持。

 

我们先要实现一个“类厂”——一个继承于IClassFactoryCOM类:

 

ClassFactory.h

class ClassFactory : public xl::ComClass<ClassFactory>,

                     public xl::IClassFactoryImpl<>

{

public:

    ClassFactory(REFCLSID rclsid);

    ~ClassFactory();

 

public:

    STDMETHOD(CreateInstance)(_In_opt_ IUnknown *pUnkOuter,

                              _In_ REFIID riid,

                              _COM_Outptr_  void **ppvObject);

 

public:

    XL_COM_INTERFACE_BEGIN(ClassFactory)

        XL_COM_INTERFACE(IClassFactory)

    XL_COM_INTERFACE_END()

 

private:

    CLSID m_clsid;

};

 

IClassFactory有两个方法,CreateInstanceLockServer。我们只实现前者,它用于创建一个对象,第一个参数不理它,第二个参数是IID,第三个参数用于输出。CLSID由构造函数传入,保存在m_clsid中——类厂是与COM类一一对应的。

 

ClassFactory.cpp

ClassFactory::ClassFactory(REFCLSID rclsid)

{

    m_clsid = rclsid;

}

 

ClassFactory::~ClassFactory()

{

 

}

 

STDMETHODIMP ClassFactory::CreateInstance(_In_opt_ IUnknown *pUnkOuter,

                                          _In_ REFIID riid,

                                          _COM_Outptr_  void **ppvObject)

{

    if (riid == __uuidof(ISampleInterface) && m_clsid == __uuidof(SampleClass))

    {

        ISampleInterface *p = new SampleClass;

        p->QueryInterface(riid, ppvObject);

 

        return S_OK;

    }

 

    return CLASS_E_CLASSNOTAVAILABLE;

}

 

这里只对IID__uuidof(ISampleInterface)CLSID__uuidof(SampleClass))的情况作了响应,创建一个SampleClass对象。注意到创建对象之后有一次QueryInterface,在这里面会做一次AddRef操作,因此引用计数此时为1。其实AddRef可以不出现在代码中,需要的时候就用QueryInterface代替。

 

这些都准备好了,最后来实现DllGetClassObject

 

STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID *ppv)

{

    if (riid == __uuidof(IClassFactory) && rclsid == __uuidof(SampleClass))

    {

        IClassFactory *p = new ClassFactory(rclsid);

        p->QueryInterface(riid, ppv);

 

        return S_OK;

    }

 

    return CLASS_E_CLASSNOTAVAILABLE;

}

 

 

实现DllRegisterServerDllUnregisterServer

这个我就直接贴代码了:

 

STDAPI DllRegisterServer(void)

{

    TCHAR szModulePath[MAX_PATH] = {};

    GetModuleFileName(g_hModule, szModulePath, ARRAYSIZE(szModulePath));

 

    xl::Registry::SetString(HKEY_CLASSES_ROOT,

                            _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}\\InprocServer32"),

                            _T(""),

                            szModulePath);

 

    xl::Registry::SetString(HKEY_CLASSES_ROOT,

                            _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}\\ProgID"),

                            _T(""),

                            _T("Streamlet.COMProvider.SampleClass.1"));

 

    xl::Registry::SetString(HKEY_CLASSES_ROOT,

                            _T("Streamlet.COMProvider.SampleClass.1"),

                            _T(""),

                            _T("SampleClass Class"));

 

    xl::Registry::SetString(HKEY_CLASSES_ROOT,

                            _T("Streamlet.COMProvider.SampleClass.1\\CLSID"),

                            _T(""),

                            _T("{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"));

 

    return S_OK;

}

 

STDAPI DllUnregisterServer(void)

{

    xl::Registry::DeleteKeyRecursion(HKEY_CLASSES_ROOT,

                                     _T("CLSID\\{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}"));

 

    xl::Registry::DeleteKeyRecursion(HKEY_CLASSES_ROOT,

                                     _T("Streamlet.COMProvider.SampleClass.1"));

 

    return S_OK;

}

 

注意,我这里并没有写得很全,只是注册几项必要的。至此,我们的COM组件实现完毕。

 

调用COM组件

编译刚才的DLL,并使用regsvr32注册(注意管理员权限)。然后写一个小程序来调用之:

 

#include <tchar.h>

#include <Objbase.h>

#include "../COMProvider/Interface.h"

 

int _tmain(int argc, TCHAR *argv[])

{

    HRESULT hr = CoInitialize(NULL);

 

    ISampleInterface *pSampleInterface = nullptr;

    hr = CoCreateInstance(__uuidof(SampleClass),

                          nullptr,

                          CLSCTX_INPROC_SERVER,

                          __uuidof(ISampleInterface),

                          (LPVOID *)&pSampleInterface);

 

    if (SUCCEEDED(hr))

    {

        pSampleInterface->SampleMethod();

        pSampleInterface->Release();

    }

   

    CoUninitialize();

 

    return 0;

}

 

运行结果:

 

clip_image001

 

上述例子代码COMProtocol.rarhttp://pan.baidu.com/s/1c0GSI7u),库依赖见http://xllib.codeplex.com/。当然,现在只是简单地迎合了一下CoCreateInstance,还有许多其他事情要做,且听下回分解。

 

posted on 2012-09-07 00:23 溪流 阅读(6166) 评论(5)  编辑 收藏 引用 所属分类: C++WindowsCOM

评论:
# re: 裸写一个进程内 COM 组件 2013-11-26 11:38 | 还在想啊
楼主费尽心思写了这么好的文章, 居然没人评论 !!   回复  更多评论
  
# re: 裸写一个进程内 COM 组件 2013-12-28 23:00 | archerC
Re:
“Usually”,它是IID_IClassFactory。我不知道有没有不“Usually”的情况,也不知道这个接口原先的设计意图是什么。如果可能,其实完全可以绕开类厂机制,直接用想要使用的那个接口的IID,貌似整套机制也能运转

=================================

除了是IID_IClassFactory, 也可以是任何从它继承的接口。
这个接口的设计意图还是把接口和实现分开。
C++类机制基本上做到了接口和实现可分,只有一个地方例外,就是构造函数。
只有知道了具体类的名称和构造函数形式,才能创建该类对象,这就是说,对象的创建依赖于实现而不是只依赖于接口。为了解决这个问题,需要采用类工厂模式,把创建对象的功能交给工厂对象来完成,而工厂对象又可以通过类机制实现多态。 这种设计允许有多个不同的工厂对象(甚至是不同的工厂类)采用不同的方式创建同一类对象。
  回复  更多评论
  
# re: 裸写一个进程内 COM 组件 2014-08-14 16:00 | meng85440835
为啥子博客里的代码都下载不了啊  回复  更多评论
  
# re: 裸写一个进程内 COM 组件 2014-08-31 21:37 | 溪流
@meng85440835
因为 SkyDrive sb 了  回复  更多评论
  
# re: 裸写一个进程内 COM 组件 2015-01-10 15:23 | 溪流
@meng85440835
已经修复所有附件,转到百度网盘了  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理