com组件是什么东西_如何注册com组件

com组件是什么东西_如何注册com组件COM编程——GUID和注册表2014年1月13日作者:果冻想1,129views暂无评论什么是GUID?做COM开发,就不得不去了解IID了,IID作为每一个接口的唯一标识符;我之前也有像下面这样定义一个IID://{2A06BBB3-667C-4D51-A8AD-F3CFDD7EF682}staticconstIIDIID_IX={0x

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

COM编程——GUID和注册表

什么是GUID?

做COM开发,就不得不去了解IID了,IID作为每一个接口的唯一标识符;我之前也有像下面这样定义一个IID:

// {2A06BBB3-667C-4D51-A8AD-F3CFDD7EF682}
static const IID IID_IX =
{ 0x2a06bbb3, 0x667c, 0x4d51, { 0xa8, 0xad, 0xf3, 0xcf, 0xdd, 0x7e, 0xf6, 0x82 } };

Jetbrains全家桶1年46,售后保障稳定

那么IID到底是什么东西呢?说到底,IID就是一个128比特的一个GUID结构;GUID是什么?GUID的英文是Globally Unique Identifier,就是全局唯一标识符的首字母缩写。

为什么要使用GUID?

为什么要使用GUID来标识接口而不用一个长整数呢?一个长整数可以标识2的32次方个接口的;但是,我们很多时候是不关心能标识多少个接口的,而是需要保证每个接口标识的唯一性,为什么呢?想想我们的QueryInterface的实现:

HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv)
{
     if (iid == IID_IUnknown)
     {
          cout<<"Query IID_IUnknown:"<<endl;
          *ppv = static_cast<IX *>(this);
     }
     else if (iid == IID_IX)
     {
          cout<<"Query IID_IX:"<<endl;
          *ppv = static_cast<IX *>(this);
     }
     else if (iid == IID_IY)
     {
          cout<<"Query IID_IY"<<endl;
          *ppv = static_cast<IY *>(this);
     }
     else
     {
          cout<<"Query interface not supported:"<<endl;
          *ppv = NULL;
          return E_NOINTERFACE;
     }
     reinterpret_cast<IUnknown *>(*ppv)->AddRef();
     return S_OK;
}

是的,我们需要根据IID返回对应的接口的指针;全球那么多的程序员在进行着COM开发,我们必须要保证接口标识的唯一性,如果有两个接口存在相同的标识符,那么QueryInterface该返回哪个接口的指针?,如果一旦这种问题出现了,程序肯定会出现不可预期的行为。

GUID则提供了一种更好的解决方案。在不用任何中央授权机构进行协调的情况下,可以用编程的方法来生成具有唯一性的GUID。在Visual Studio的菜单的TOOLS中有一个Create GUID工具,使用这个工具可以生成一个唯一的GUID,工具截图如下:

果冻想 | 一个原创文章分享网站

其实Create GUID这个工具实际上调用了Microsoft COM库中的CoCreateGuid函数。

GUID的声明和定义

我之前的代码中都是这样定义的:

// {2A06BBB3-667C-4D51-A8AD-F3CFDD7EF682}
static const IID IID_IX = 
{ 0x2a06bbb3, 0x667c, 0x4d51, { 0xa8, 0xad, 0xf3, 0xcf, 0xdd, 0x7e, 0xf6, 0x82 } };

当然了,我们也可以使用objbase.h中所定义的DEFINE_GUID宏来定义一个GUID:

// {6BED30F3-8FC7-4E12-8F5E-7E902F917312}
DEFINE_GUID(<<name>>,
0x6bed30f3, 0x8fc7, 0x4e12, 0x8f, 0x5e, 0x7e, 0x90, 0x2f, 0x91, 0x73, 0x12);

GUID的使用

GUID的使用非常简单,很多时候,我们会对GUID进行比较判断,可以直接进行==符号进行判断两个GUID是否相等。同时,我之前都是将GUID作为接口的标识符,其实,我们也是将GUID作为组件的标识符。同接口一样,所有的组件都有一个不同的ID,为了将类标识符同IID区分开来,与类标识符相应的类型为CLSID;两个组件可以实现相同的接口集,但是每一个组件仍然必须具有各不相同的CLSID。在不改变类标识符的情况下,可以向组件中加入新的接口。但是若对某个组件的修改将会妨碍已有应用程序的正常运行,那么必须要为组件分配一个新的CLSID。

在使用GUID是,需要特别注意,由于GUID是128位的,在函数之间传递GUID时,请最好使用引用传参的方式。可以将参数定义为const IID&,也可以使用等价的REFIID。类似的,在传递类标识符时,可以使用REFCLSID;而在传递GUID值时可以使用REFGUID。

Windows注册表

如果对COM编程不熟悉,可能很惊讶,为什么总结COM编程,要总结到Windows的注册表呢?只要是从事Windows平台的开发,就不得不对注册表有所了解。COM组件就是用CLSID作为关键字在注册表中发布包含它们的DLL文件名称,然后CoCreateInstance将用CLSID作为关键字在注册表中查找所需的文件名称。

在Windows中,注册表是Windows操作系统的一个正式的共享系统数据库。注册表中包含关于系统的各种信息,可以说只有你想不到,没有你找不到的一个数据库。所有基于Windows的程序都可以向注册表中加入若干信息,并可以从中读取所需要的信息。客户可以在注册表中搜索它们所需要的组件。

关于注册表的结构,我就不在这里多说什么了。你可以使用regedit命令打开注册表看看就知道了。还是把重点放在COM在注册表中的表现形式吧。

CLSID关键字结构

注册表那么大,那么复杂;对于COM来说,只使用了注册表的一个分支:HKEY_CLASSES_ROOT。在这个关键字下面,可以找到一个CLSID关键字,在CLSID下面就是系统中安装的所有组件的CLSID。就是下面这样的结构:HKEY_CLASSES_ROOT\CLSID\{00000010-0000-0010-8000-00AA006D2EA4}

可以看到,每个CLSID的关键字都是类似00000010-0000-0010-8000-00AA006D2EA4这样的结构,而按照CLSID来查看注册表将是一件极不轻松的事情,这就是为什么每一个CLSID关键字都用组件的有意义的名称来作为其缺省值。

对于每一个CLSID关键字,我们现在关心的只是它的一个子关键字InprocServer32。这个关键字的缺省值就是组件所在的DLL文件名称。CLSID和文件名称是注册表中记录的两种最重要的信息,我们可以在不向注册表中加入任何有关组件信息的情况下编写出大量的组件。

在注册表的HKEY_CLASSES_ROOT下面还有一些与COM编程有关的关键字需要讲解:

  1. AppID;这个关键字下的子关键字的作用是将某个APPID(应用程序ID)映射成某个远程服务器名称。分布式COM将用到此关键字;
  2. Interface;这个关键字用于将IID映射成与某个接口相关的信息,这些信息主要用于在跨越进程边界使用接口的情况;
  3. Licenses;Licenses保存的是授权使用COM组件的一些许可信息;
  4. TypeLib;类型库关键字所保存的是关于接口成员函数所用参数的信息;这个关键字可以将一个LIBID映射成存储类型库的名称;
  5. Component Categories;注册表的这一分支可以将CATID(组件类别ID)映射成某个特定的组件类别。

ProgID

什么又是ProgID啊?其实,在HKEY_CLASSES_ROOT下面,大多数关键字都是ProgID,比如FunctionDiscovery.WCNProvider。所谓的ProgID指的是程序员给某个CLSID指定的一个程序员容易记住的名字。某些编程语言将使用ProgID而不是CLSID来标识组件。但是没有办法来保证ProgID是唯一的,因此名字冲突将是一个潜在的问题;但是对于ProgID处理起来却是容易很多的。

ProgID一般都是按照<Program>.<Component>.<Version>的格式进行命名的;这不是一定要遵守的规则,有的ProgID就没有按照这个进行命名。有的时候,组件经常会有一个与版本号无关的ProgID。这个ProgID被映射成所安装的最新版本的组件。与版本无关的ProgID的命名约定是将上述约定中的版本号去掉。

ProgID的主要作用是获取相应的CLSID,由于在每一个CLSID项中查找某个ProgID将是非常低效的,因此ProgID是在HKEY_CLASSES_ROOT的下面直接列出的。它在注册表中的结构如下:

果冻想 | 一个原创文章分享网站

从ProgID到CLSID的转换

将所需要的信息保存到注册表之后,进行CLSID和ProgID之间的转换将是非常容易的。COM库为此提供了两个函数CLSIDFromProgID和ProgFromCLSID,用来完成所需要的注册表处理。

CLSID clsid;
CLSIDFromProgID(L"Cmiv2.CmiFactory.2", &clsid);

自注册

现在我们都知道注册表中保存了组件的重要信息;但是,这些信息是如何写进注册表的呢?由于DLL知道它所包含的组件,因此DLL可以完成这些信息的注册。由于DLL本身不能运行,因此在DLL中一定要输出以下两个函数,由其它程序调用DLL完成注册。

extern "C" HRESULT __stdcall DllRegisterServer();
extern "C" HRESULT __stdcall DllUnRegisterServer();

DLL导出了这两个函数,然后,我们就可以使用系统提供的regsvr32.exe工具来调用对应的DLL完成注册。

DllRegisterServer用来完成注册,DllUnRegsiterServer用来完成反注册;它们的实现原理就是调用Win32的注册表操作函数完成对注册表的写操作。

组件类别

组件类别就是HKEY_CLASSES_ROOT键下的Component Categories。一个组件类别实际上就是一个接口集合。该集合将被分配给一个GUID,此GUID此时将被称作是CATID。对于某个组件而言,若它实现了某个组件类别的所有接口,那么它就可以将其注册成该组件类别的一个成员。对于将自己注册为某个组件类别的组件,它将保证它可以支持此组件类别的所有成员接口;就好比C++中的抽象类一样,派生类必须实现抽象类中的全部纯虚函数。

对于组件类别,我们不需要自己完成注册表的处理,这些工作可以由Windows系统附带的一个名为Component Category Manager来完成。CLSID_StdComponentCategoriesMgr是一个实现了两个主要接口的COM组件:ICatRegister和ICatInformation。ICatRegister可以完成新组件类别的登记或取消注册;ICatInformation则可用于获取系统中某个组件类别的数据。

OleView工具

果冻想 | 一个原创文章分享网站

当我们注册完我们的组件以后,就可以使用该工具进行查看了;如果没有找到,则说明注册过程中出现问题了。该工具的使用非常简单,这里就不过多的说明了。

总结

好了,COM中的这些零碎知识点又搞定一篇了。下一篇再见。我坚信,分享使我们更加进步。如果你喜欢,请分享这篇文章。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/215907.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Tomcat 配置WEB虚拟映射 及 配置虚拟主机

    Tomcat 配置WEB虚拟映射 及 配置虚拟主机

    2021年12月9日
    41
  • 关于balun的一些仿真和思考[通俗易懂]

    关于balun的一些仿真和思考[通俗易懂]关于balun的一些仿真和思考1、错误的初始设计,电路图如下:2、来看看ADS里,理想balun的仿真结果3、将原电路的50ohm并接在balun的差分端,而非单端,再看看仿真结果。4、那么为什么出现上述情况呢?5、我们应该如何去做呢?6、总结:如有错误,还请留言区指出~为了提高共模抑制能力,很多芯片的输入输出口会设计成差分口,在射频电路中也是如此。最近在项目测试中,一个关于balun的问题困扰了我很久,迟迟没有想明白。我爸说:“当一个问题你久久思考却毫无头绪时,你就暂时放一放。也许有天突然开窍,你就想

    2025年8月1日
    3
  • Linux使用rpm命令卸载软件[通俗易懂]

    Linux使用rpm命令卸载软件[通俗易懂]window上面要卸载一个软件很容易,在系统软件管理里面或者通过第三应用工具,比如360软件管理。如果是Linux需要卸载一个软件应该怎么样操作??rpm-q-a#查询所有已安装的软件-qquery查询-aall所有查询所有安装的软件[root@cdh1~]#rpm-q-agnome-session-xsession-2.28.0-18.el6.x86_64m17n-contrib-assamese-1.1.10-4.el6_1.1.noarchm17n-con

    2025年9月23日
    6
  • 什么是PXE及PXE启动

    什么是PXE及PXE启动PXE(Pre-bootExecutionEnvironment)是由Intel设计的协议,它可以使计算机通过网络启动。协议分为client和server两端,PXEclient在网卡的ROM中,当计算机引导时,BIOS把PXEclient调入内存执行,并显示出命令菜单,经用户选择后,PXEclient将放置在远端的操作系统通过网络下载到本地运行。  PXE协议的成功运行需要解决

    2022年6月29日
    101
  • Linux下的双网卡绑定bond0 em1 em2

    一、什么是bonding Linuxbonding驱动提供了一个把多个网络接口设备捆绑为单个的网络接口设置来使用,用于网络负载均衡及网络冗余 二、bonding应用方向1、网络负载均衡 对于bonding的网络负载均衡是我们在文件服务器中常用到的,比如把三块网卡,当做一块来用,解决一个IP地址,流量过大,服务器网络压力过大的问题。对于文件服务器来说,比如NFS或SAMBA文件服

    2022年4月8日
    105
  • java获取汉字首字母以及全拼字母_excel中提取汉字首字母

    java获取汉字首字母以及全拼字母_excel中提取汉字首字母使用Hutool工具类官网链接以下为Hutool支持的拼音库的pom坐标,你可以选择任意一个引入项目中,如果引入多个,Hutool会按照以上顺序选择第一个使用。使用链接查看Hutool最新版本

    2022年10月18日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号