[实例]这就是COM组件

时间 2012-02-21 10:49:15  CSDN博客
主题 COM技术

[实例]这就是COM组件

Author: ume Date:2012-02-21

自从微软推出.NET以来,COM技术就渐渐淡出人们的视野,然而这并不意味COM不再发挥作用,相反,COM非常重要。可以说.NET的实现离不开COM的支撑。COM是好东西,但是它太难了,不利于产品推广,于是微软只能在之上增加一层封装从而提高产品的易用性。对COM有所了解是很有必要的,希望这篇文章给你带来一点启发。

1. COM的思想

开篇就讲COM的思想肯定让人泄气,因为它极有可能抽象空洞、晦涩难懂。换个角度来说,我觉得COM的思想仍然是需要自己去体会的,这里给出的不过是一个思考的线索而已,你大可不求甚解。

软件的开发是永无止境的,只要软件还在存活期,它就应当不断被完善和更新。一款软件可能十分庞大,但真正可变的部分却是很有限的。我们当然希望在更新软件的时候,只更新变化的部分,而不要笨手笨脚把整个软件都换掉。只更新变化的部分?这就要求模块支持动态链接。所谓动态链接就是模块只在被使用的时候才被加载,程序调用模块中的某个函数是通过指针来完成的。动态链接允许模块分离出去,而不像静态链接那样须经过编译才能整合到程序中来。dll是实现动态链接的一种方法,它使更新软件的工作浓缩成了更新dll,用户无需重新安装软件,只需替换相应的dll文件就能实现软件的升级。

动态链接是针对普通用户而言的,现在换一个对象:模块的用户。模块的用户是应用程序开发人员,对于模块的提供商来说也算得上同行了,只不过术业有专攻,各自工作的重点不同而已。显然采用dll的形式,模块的提供商可以很方便的发布自己的产品。其中不可忽视的另一点即信息的封装(或称隐藏),即将模块的实现细节隐蔽起来,用户无法知道模块的提供商采用何种语言、何种算法,简而言之就是用户看不到模块的源代码。dll是二进制级别上的代码复用,它实现了信息的封装。

综上所述,软件开发要求模块支持“动态链接”和“信息封装”,更直白地说就是要求模块和客户代码之间更低的耦合度。把模块制作成组件是必然的选择,而COM本质上是一种创建组件的方法和规范。

注:dll并不等同于组件,它只是组件的一种形式。由于dll的易用性,它的应用很广泛。

2. 实例说明

我们创建一个COM组件,它将实现接口ICouplet,用户可通过该接口调用what()方法输出一副对联。what()方法不值一提,不过你可以将它当作程序可变的部分。我们创建的COM组件也要实现接口IClassFactory,它是创建组件的简单组件。之所以这么设计是为了让组件与客户代码彻底脱耦,尽可能少的联系。

除了实现接口ICouplet和IClassFactory外, COM组件还要能实现自注册,因此它必须导出函数DllRegister/DllUnregister。另外两个导出函数DllCanUnloadNow和DllGetClassObject也非常重要,前者用来询问当前dll能否被卸载,它被CoFreeUnusedLibraries调用;后者用来创建类厂组件,它被CoCreateInstance调用。名称形如Coxxx的函数是COM库函数,它是实现COM组件的公共操作,由微软提供,类似于Win32 API。我们常见的客户代码中CoInitialize/CoUninitialize函数就起到初始化和卸载COM库的作用。要导出上述4个函数就必须编写一个.def文件,具体写法见代码清单。

最后要说明的是COM组件的自注册。我们知道注册表是Windows的公共系统数据库,其中记录了软件、硬件、用户配置等信息。而COM组件是用一个128比特的GUID标识的,为了使得COM组件的安装目录更灵活,我们可以在注册表中对它进行注册,注册的主要信息即COM组件的GUID标识与其存储路径的对应关系,在使用该组件时就到注册表中去查找。注册一个COM组件一般使用regsvr32.exe程序来完成,当然你也可以自己写一个类似于regsvr32.exe的小程序来完成COM组件的注册,regsvr32.exe本质上调用了组件的导出函数DllRegister/DllUnregister。

生成Couplet.dll文件后,首先在控制台注册它。具体方法:切换到Couplet.dll所在目录,输入指令regsvr32 Couplet.dll。然后运行客户程序Reader.exe,其结果如下所示:

Create Couplet object
Succeeded in getting the pointer to ICouplet 1st Scroll: Study Hard, Work Hard, Make Money More and More
2nd Scroll: Eat Well, Sleep Well, Have Fun Day by Day
Top Scroll: Gelievable Couplet object deleted
请按任意键继续. . .

然后修改Couplet::what()方法,让它输出中文,重新生成Couplet.dll。这一步不用重新注册Couplet.dll,因为Couplet.dll的路径没变,CLSID_Couplet也没变。运行客户程序Reader.exe,其结果如下所示:

CreateCouplet object

Succeededin getting the pointer to ICouplet

上联:我爱的人名花有主

下联:爱我的人惨不忍睹

横批:命苦

Coupletobject deleted

请按任意键继续. . .

这个例子证明了COM组件的更新不会对客户端造成影响,使用COM组件可以实现模块与客户代码彻底脱耦。实验结束后,在控制台输入指令regsvr32 /u Couplet.dll,从注册表中将dll模块信息清除。

3. 代码清单

/* File List: (COM) IFace.h Register.h Register.cpp Couplet.cpp Couple.def
* (Client) IFace.h Reader.cpp
* date: 2012-02-21
* author: ume
*/
/////////////////////////////////////////////////////////////////
// IFace.h 接口的声明,组件ID、接口ID的定义
//
#include <ObjBase.h>
// interface
interface ICouplet : IUnknown
{
virtual void what() = 0;
};
// GUIDs
// {03844548-B0B9-4B12-869D-061AAE2E4B7F}
static const GUID IID_ICouplet =
{ 0x3844548, 0xb0b9, 0x4b12, { 0x86, 0x9d, 0x6, 0x1a, 0xae, 0x2e, 0x4b, 0x7f } };
// {26615B48-1D2E-4A40-9C07-AD5B1B48368C}
static const GUID CLSID_Couplet =
{ 0x26615b48, 0x1d2e, 0x4a40, { 0x9c, 0x7, 0xad, 0x5b, 0x1b, 0x48, 0x36, 0x8c } };
/////////////////////////////////////////////////////////////////
// Register.h 注册函数的声明
//
HRESULT RegisterServer(HMODULE hModule,
const CLSID& clsid,
const char* szFriendlyName,
const char* szVerIndProgID,
const char* szProgID);
HRESULT UnRegisterServer(const CLSID& clsid,
const char* szVerIndProgID,
const char* szProgID);
/////////////////////////////////////////////////////////////////
// Register.cpp 注册函数的定义
// 这些函数可重复使用,非本文重点
//
#include <objbase.h>
#include "Register.h"
//set the given key and its value;
BOOL setKeyAndValue(const char* pszPath,
const char* szSubkey,
const char* szValue);
//Convert a CLSID into a char string
void CLSIDtochar(const CLSID& clsid,
char* szCLSID,
int length);
//Delete szKeyChild and all of its descendents
LONG recursiveDeleteKey(HKEY hKeyParent,const char* szKeyChild);
//size of a CLSID as a string
const int CLSID_STRING_SIZE = 39;
//Register the component in the registry
HRESULT RegisterServer(HMODULE hModule,
const CLSID& clsid,
const char* szFriendlyName,
const char* szVerIndProgID,
const char* szProgID)
{
//Get the Server location
char szModule[512];
DWORD dwResult = ::GetModuleFileName(hModule,szModule,sizeof(szModule)/sizeof(char));
assert(dwResult!=0); //Convert the CLSID into a char
char szCLSID[CLSID_STRING_SIZE];
CLSIDtochar(clsid,szCLSID,sizeof(szCLSID)); //Build the key CLSID\\{}
char szKey[64];
strcpy(szKey,"CLSID\\");
strcat(szKey,szCLSID); //Add the CLSID to the registry
setKeyAndValue(szKey,NULL,szFriendlyName); //Add the Server filename subkey under the CLSID key
setKeyAndValue(szKey,"InprocServer32",szModule); setKeyAndValue(szKey,"ProgID",szProgID); setKeyAndValue(szKey,"VersionIndependentProgID",szVerIndProgID); //Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT
setKeyAndValue(szVerIndProgID,NULL,szFriendlyName);
setKeyAndValue(szVerIndProgID,"CLSID",szCLSID);
setKeyAndValue(szVerIndProgID,"CurVer",szProgID); //Add the versioned ProgID subkey under HKEY_CLASSES_ROOT
setKeyAndValue(szProgID,NULL,szFriendlyName);
setKeyAndValue(szProgID,"CLSID",szCLSID);
return S_OK;
} //
//Remove the component from the register
//
HRESULT UnRegisterServer(const CLSID& clsid, // Class ID
const char* szVerIndProgID, // Programmatic
const char* szProgID) // IDs
{
//Convert the CLSID into a char.
char szCLSID[CLSID_STRING_SIZE];
CLSIDtochar(clsid,szCLSID,sizeof(szCLSID)); //Build the key CLSID\\{}
char szKey[64];
strcpy(szKey,"CLSID\\");
strcat(szKey,szCLSID); //Delete the CLSID key - CLSID\{}
LONG lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szKey);
assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND)); //Delete the version-independent ProgID Key
lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szVerIndProgID);
assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND)); //Delete the ProgID key.
lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szProgID);
assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND)); return S_OK;
}
//Convert a CLSID to a char string
void CLSIDtochar(const CLSID& clsid,
char* szCLSID,
int length)
{
assert(length>=CLSID_STRING_SIZE); //Get CLSID
LPOLESTR wszCLSID = NULL;
HRESULT hr = StringFromCLSID(clsid,&wszCLSID);
assert(SUCCEEDED(hr)); //Convert from wide characters to non_wide
wcstombs(szCLSID,wszCLSID,length); //Free memory
CoTaskMemFree(wszCLSID);
}
// Delete a Key and all of its descendents
LONG recursiveDeleteKey(HKEY hKeyParent,const char* lpszKeyChild)
{
//Open the child.
HKEY hKeyChild;
LONG lRes = RegOpenKeyEx(hKeyParent,lpszKeyChild,0,KEY_ALL_ACCESS,&hKeyChild); if(lRes != ERROR_SUCCESS)
return lRes; //Enumerate all of the decendents of this child
FILETIME time;
char szBuffer[256];
DWORD dwSize = 256 ; while(RegEnumKeyEx(hKeyChild,0,szBuffer,&dwSize,NULL,
NULL,NULL,&time) == S_OK)
{
//Delete the decendents of this child.
lRes = recursiveDeleteKey(hKeyChild,szBuffer);
if(lRes != ERROR_SUCCESS)
{
RegCloseKey(hKeyChild);
return lRes;
}
dwSize = 256;
}
RegCloseKey(hKeyChild);
return RegDeleteKey(hKeyParent,lpszKeyChild);
} BOOL setKeyAndValue(const char* szKey,
const char* szSubkey,
const char* szValue)
{
HKEY hKey;
char szKeyBuf[1024]; //Copy keyname into buffer.
strcpy(szKeyBuf,szKey); //Add subkey name to buffer.
if(szSubkey!=NULL)
{
strcat(szKeyBuf,"\\");
strcat(szKeyBuf,szSubkey);
} // Create and open key and subkey.
long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT ,
szKeyBuf,
0, NULL, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL,
&hKey, NULL) ;
if (lResult != ERROR_SUCCESS) {
return FALSE ;
} // Set the Value.
if (szValue != NULL)
{
RegSetValueEx(hKey, NULL, 0, REG_SZ,
(BYTE *)szValue,
strlen(szValue)+1) ;
} RegCloseKey(hKey) ;
return TRUE ;
}
/////////////////////////////////////////////////////////////////
// Couplet.cpp 接口的实现
// 本文的重点,尤其是Couplet和CFactory的实现
//
#include "IFace.h"
#include "Register.h"
#include <iostream>
using namespace std;
// trace
void trace(const char* msg) { cout<<msg<<endl; }
// global variables
HMODULE g_hModule;
static long g_cComponents = 0;
static long g_cLocks = 0;
// Friendly name of component
const char g_szFriendlyName[] = "A Couplet";
// Version independent ProgID
const char g_szVerIndProgID[] = "Couplet.Test";
// ProgID
const char g_szProgID[] = "Couplet.Test.1";
// implementation
class Couplet : public ICouplet
{
public:
virtual LRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
virtual ULONG __stdcall AddRef()
{
return ::InterlockedIncrement(&m_cRef);
}
virtual ULONG __stdcall Release()
{
if(::InterlockedDecrement(&m_cRef) == 0)
{
delete this;
return 0;
}
return m_cRef;
}
virtual void what()
{
//cout<<"\n上联:我爱的人名花有主\n下联:爱我的人惨不忍睹\n横批:命苦\n\n";
cout<<"\n1st Scroll: Study Hard, Work Hard, Make Money More and More\n\
2nd Scroll: Eat Well, Sleep Well, Have Fun Day by Day\nTop Scroll: Gelievable\n\n";
}
// constructor
Couplet() : m_cRef(1)
{
::InterlockedIncrement(&g_cComponents);
trace("Create Couplet object");
}
// destructor
~Couplet()
{
::InterlockedDecrement(&g_cComponents);
trace("Couplet object deleted");
}
private:
long m_cRef;
};
// definition of QueryInterface
LRESULT __stdcall Couplet::QueryInterface(const IID& iid, void** ppv)
{
if((iid == IID_IUnknown) || (iid == IID_ICouplet))
{
*ppv = static_cast<ICouplet*>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
// class CFactory
class CFactory : public IClassFactory
{
public:
virtual LRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
virtual ULONG __stdcall AddRef()
{
return ::InterlockedIncrement(&m_cRef);
}
virtual ULONG __stdcall Release()
{
if(::InterlockedDecrement(&m_cRef) == 0)
{
delete this;
return 0;
}
return m_cRef;
}
virtual LRESULT __stdcall CreateInstance(IUnknown* pCmpntOuter,
const IID& iid,
void** ppv);
virtual LRESULT __stdcall LockServer(BOOL bLock);
private:
long m_cRef;
};
// definition of QueryInterface
LRESULT __stdcall CFactory::QueryInterface(const IID& iid, void** ppv)
{
if((iid == IID_IUnknown) || (iid == IID_IClassFactory))
{
*ppv = static_cast<IClassFactory*>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
// definition of CreateInstance
LRESULT __stdcall CFactory::CreateInstance(IUnknown* pCmpntOuter,
const IID& iid,
void** ppv)
{
if(pCmpntOuter != NULL)
{
cout<<"No Aggregate in this Class Factory"<<endl;
return CLASS_E_NOAGGREGATION;
}
Couplet* pCouplet = new Couplet;
if(pCouplet == NULL)
return E_OUTOFMEMORY;
HRESULT hr = pCouplet->QueryInterface(iid, ppv);
pCouplet->Release();
return hr;
}
// definition of LockServer
LRESULT __stdcall CFactory::LockServer(BOOL bLock)
{
if(bLock)
{
::InterlockedIncrement(&g_cLocks);
}
else
{
::InterlockedDecrement(&g_cLocks);
}
return S_OK;
}
STDAPI DllCanUnloadNow()
{
if((g_cComponents == 0) && (g_cLocks == 0))
{
return S_OK;
}
else
{
return S_FALSE;
}
}
// Get class factory
STDAPI DllGetClassObject(const CLSID& clsid,
const IID& iid,
void** ppv)
{
// Can we create this component?
if(clsid != CLSID_Couplet)
{
return CLASS_E_CLASSNOTAVAILABLE;
}
// Create class factory
CFactory* pFactory = new CFactory;
if(pFactory == NULL)
{
return E_OUTOFMEMORY;
}
// Get requested interface
HRESULT hr = pFactory->QueryInterface(iid, ppv);
pFactory->Release();
return hr;
}
// register and unregister component
STDAPI DllRegisterServer()
{
return RegisterServer(g_hModule,
CLSID_Couplet,
g_szFriendlyName,
g_szVerIndProgID,
g_szProgID);
}
STDAPI DllUnregisterServer()
{
return UnRegisterServer(CLSID_Couplet,
g_szVerIndProgID,
g_szProgID);
}
// dll main
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD dwReason,
void* lpReserved)
{
if(dwReason == DLL_PROCESS_ATTACH)
{
g_hModule = (HMODULE)hModule;
}
return TRUE;
}
/////////////////////////////////////////////////////////////////
// Couplet.def 模块定义文件
//
LIBRARY Couplet.dll
EXPORTS
DllCanUnloadNow @1 PRIVATE
DllGetClassObject @2 PRIVATE
DllRegisterServer @3 PRIVATE
DllUnregisterServer @4 PRIVATE
/////////////////////////////////////////////////////////////////
// Reader.cpp 通过ICouplet接口调用what()方法读取对联内容
// 注意: 客户端的IFace.h与COM组件中的IFace.h完全一样
//
#include <iostream>
#include <ObjBase.h>
#include "IFace.h"
using namespace std;
// global function
void trace(const char* pMsg){ cout<<pMsg<<endl; }
// main function
int main()
{
::CoInitialize(NULL);
ICouplet* pICouplet = NULL;
HRESULT hr = ::CoCreateInstance(CLSID_Couplet, NULL, CLSCTX_INPROC_SERVER, IID_ICouplet,
(void**)&pICouplet);
if(SUCCEEDED(hr))
{
trace("Succeeded in getting the pointer to ICouplet");
pICouplet->what();
pICouplet->Release();
}
else
{
trace("Failed to get the pointer to ICouplet");
}
::CoUninitialize();
system("pause");
return 0;
}

【VS开发】这就是COM组件的更多相关文章

  1. Android开发——构建自定义组件

    Android中,你的应用程序程序与View类组件有着一种固定的联系,例如按钮(Button). 文本框(TextView), 可编辑文本框(EditText), 列表框(ListView), 复选框 ...

  2. iOS组件化开发· 什么是组件化

    越来越多公司,开始了组件化,你还要等到什么时候...... 说到开发模式,我们最熟知的开发模式 MVC 或者最近比较热门的MVVM.但是我今天说的组件化的开发,其实MVC不是一类的.它其实是····· ...

  3. winform快速开发平台 -> 工作流组件(仿GooFlow)

    对于web方向的工作流,一直在用gooflow对于目前我的winform开发平台却没有较好的工作流组件.  针对目前的项目经验告诉我们.一个工作流控件是很必要的. 当然在winform方面的工作流第三 ...

  4. winform快速开发平台 -> 基础组件之分页控件

    一个项目控件主要由及部分的常用组件,当然本次介绍的是通用分页控件. 处理思想:我们在处理分页过程中主要是针对数据库操作. 一般情况主要是传递一些开始位置,当前页数,和数据总页数以及相关关联的业务逻辑. ...

  5. android开发之自定义组件

    android开发之自定义组件 一:自定义组件: 我认为,自定义组件就是android给我们提供的的一个空白的可以编辑的图片,它帮助我们实现的我们想要的界面,也就是通过自定义组件我们可以把我们要登入的 ...

  6. Yii Framework 开发教程Zii组件-Tabs示例

    有关Yii Tab类: http://www.yiichina.com/api/CTabView http://www.yiichina.com/api/CJuiTabs http://blog.cs ...

  7. Android开发 ---基本UI组件4:拖动事件、评分进度条、圆圈式进度条、进度条控制

    Android开发 ---基本UI组件4 1.activity_main.xml 描述: 定义了一个按钮 <?xml version="1.0" encoding=" ...

  8. jquery插件模式开发和react组件开发之间的异同

    jquery插件模式开发和react组件开发之间的异同

  9. Android开发 ---基本UI组件3:单选按钮、多选按钮、下拉列表、提交按钮、重置按钮、取消按钮

    Android开发 ---基本UI组件2 1.activity_main.xml 描述: 定义一个用户注册按钮 <?xml version="1.0" encoding=&q ...

随机推荐

  1. 长春理工大学第十四届程序设计竞赛F Successione di Fixoracci——找规律&&水题

    题目 链接 题意:给出x数列的定义: $T_0 = a$ $T_1 = b$ $T_n = T_{n-2} \bigoplus T_{n-1} $ 求第 $n$ 项( $0 \leqslant a,b ...

  2. HDU 6060 - RXD and dividing | 2017 Multi-University Training Contest 3

    /* HDU 6060 - RXD and dividing [ 分析,图论 ] | 2017 Multi-University Training Contest 3 题意: 给一个 n 个节点的树, ...

  3. 源码安装ROS Melodic Python3 指南 (转) + 安装记录

    这篇文章转自   https://blog.csdn.net/id9502/article/details/80410989  csdn真是作大死,我保存这篇博客的时候还不需要花钱就能看,现在居然要v ...

  4. luogu2634

    P2634 [国家集训队]聪聪可可 题目描述 聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃.两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一 ...

  5. 7.19T3

    小 X 的图 题目背景及题意 小 X 有一张图,有 n 个点(从 0 开始编号),一开始图里并没有 边,他有时候会向其中添加一条双向边(x 到 y).小 X 会时不时想知 道某两个点是否联通,如果连通 ...

  6. Nginx 499的问题

    PHP 异步 HTTP 与 NGINX 499 PHP 异步 HTTP 在 PHP 代码中提交异步 HTTP 请求比较常用的方式是通过 fsockopen/fwrite/fclose 来实现,请参考如 ...

  7. dapper通用分页方法

    /// <summary> /// dapper通用分页方法 /// </summary> /// <typeparam name="T">泛型 ...

  8. Redis订阅广播实现多级缓存

    Redis应用场景很多,现在介绍一下它的几大特性之一   发布订阅(pub/sub) 特性介绍: 什么是redis的发布订阅(pub/sub)?   Pub/Sub功能(means Publish, ...

  9. C#_选择结构,Console的应用,数据类型转换

    1:先看一个顺序结构编程,代码如下: using System; using System.Collections.Generic; using System.Linq; using System.T ...

  10. Mybatis-Plus BaseMapper自动生成SQL及MapperProxy

    目录 Spring+Mybatis + Mybatis-Plus 自定义无XML的sql生成及MapperProxy代理生成 问题产生背景 框架是如何使用 无Xml的SQL是如何生成生成及SQL长成什 ...