COM是基于二进制的组件模块,从设计之初就以支持所有语言作为它的一个目标,这篇文章主要探讨COM的跨语言部分。

idl文件

一般COM接口的实现肯定是以某一具体语言来实现的,比如说使用VC++语言,这就造成了一个问题,不同的语言对于接口的定义,各个变量的定义各不相同,如何让使用vc++或者说Java等其他语言定义的接口能被别的语言识别?为了达到这个要求,定义了一种文件格式idl——(Interface Definition Language)接口定义语言,IDL提供一套通用的数据类型,并以这些数据类型来定义更为复杂的数 据类型。一般来说,一个文件有下面几个部分说明

  1. 接口的定义
  2. 组件库的定义
  3. 实现类的定义

    而各个部分又包括他们的属性定义,以及函数成员的定义

属性:

属性是在接口定义的上方,使用“[]”符号包裹,一般在属性中使用下面几个关键字:

object:标明该部分是一个对象(可以理解为c++中的对象,包括接口和具体的实现类)

uuid:标明该部分的GUID

version:该部分的版本

接口定义

接口定义采用关键字interface,接口函数定义在一对大括号中,它的定义与类的定义相似,其中函数定义需要修饰函数各个参数的作用,比如使用in 表示它作为输入参数,out表示作为输出参数,retval表示该参数作为返回值,一般在VC++定义的接口中,函数返回值为HRESULT,但是需要返回一个值供外界调用,此时就使用输出参数并加上retval表示它将在其他语言中作为函数的返回值。

组件库定义

库使用library关键字定义,在定义库的时候,它的属性一般定义GUID和版本信息,而在库中通常定义库中的实现类的相关信息,库中的信息也是写在一对大括号中

实现类的定义

接口实现类使用关键字coclass,接口类的属性一般定义一个object,一个GUID,然后一般定义实现类不需要向在C++中那样定义它的各个接口,各个数据成员,只需要告知它实现哪些接口即可,也就是说它继承自哪些接口。

下面是一个具体的例子:

  1. import "unknwn.idl";
  2. [
  3. object,
  4. uuid(CF809C44-8306-4200-86A1-0BFD5056999E)
  5. ]
  6. interface IMyString : IUnknown
  7. {
  8. HRESULT Init([in] BSTR bstrInit);
  9. HRESULT GetLength([out, retval] ULONG *pretLength);
  10. HRESULT Find([in] BSTR bstrFind, [out, retval] BSTR* bstrSub);
  11. };
  12. [
  13. uuid(ADF50A71-A8DD-4A64-8CCA-FFAEE2EC7ED2),
  14. version(1.0)
  15. ]
  16. library ComDemoLib
  17. {
  18. importlib("stdole32.tlb");
  19. [
  20. uuid(EBD699BA-A73C-4851-B721-B384411C99F4)
  21. ]
  22. coclass CMyString
  23. {
  24. interface IMyString;
  25. };
  26. };

上面的例子中定义了一个IMyString接口继承自IUnknown接口,函数参数列表中in表示参数为输入参数,out表示它为输出参数,retval表示该参数是函数的返回值。import导入了一个库文件类似于include。而importlib导入一个tlb文件,我们可以将其看成VC++中的#pragma comment导入一个lib库

从上面不难看出一个IDL文件至少有3个ID,一个是接口ID,一个是库ID,还有一个就是实现类的ID

在VC环境中通过midl命令可以对该文件进行编译,编译会生成下面几个我们在编写实现时会用到的重要文件:

  1. 一个.h文件:包含各个部分的声明,以及接口的定义
  2. 一个_i.c文件:包含各个部分的定义,主要是各个GUI的定义

需要实现的导出函数

一般我们需要在dll文件中导出下面几个全局的导出函数:

  1. STDAPI DllRegisterServer(void);
  2. STDAPI DllUnregisterServer(void);
  3. STDAPI DllGetClassObject(const CLSID & rclsid, const IID & riid, void ** ppv);
  4. STDAPI DllCanUnloadNow(void);

其中DllRegisterServer用来向注册表中注册模块的相关信息,主要注测在HKEY_CLASSES_ROOT中,主要定义下面几项内容:

  1. 字符串名称项,该项中包含一个默认值,一般给组件的字符串名称;CLSID子健,一般给实现类的GUID;CurVer子健一般是子健的版本
  2. 以版本字符串为键的注册表项,该项中主要保存:默认值,当前版本的项目名称;CLSID当前版本库的实现类的GUID
  3. 在HKEY_CLASSES_ROOT/CLSID子健中注册以实现类GUID字符串为键的注册表项,里面主要包含:默认值,组件字符串名称;InprocServer32,组件所在模块的全路径;ProgID组件名称;TypeLib组件类型库的ID,也就是在定义IDL文件时,定义的实现库的GUID。

    下面是具体的定义:
  1. const TCHAR *g_RegTable[][3] = {
  2. { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}"), 0, _T("FirstComLib.MyString")}, //组件ID
  3. { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\InprocServer32"), 0, (const TCHAR*)-1 }, //组建路径
  4. { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\ProgID"), 0, _T("FirstComLib.MyString")}, //组件名称
  5. { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\TypeLib"), 0, _T("{ADF50A71-A8DD-4A64-8CCA-FFAEE2EC7ED2}") }, //类型库ID
  6. { _T("FirstComLib.MyString"), 0, _T("FirstComLib.MyString") }, //组件的字符串名称
  7. { _T("FirstComLib.MyString\\CLSID"), 0, _T("{EBD699BA-A73C-4851-B721-B384411C99F4}")}, //组件的CLSID
  8. { _T("FirstComLib.MyString\\CurVer"), 0, _T("FirstComLib.MyString.1.0") }, //组件版本
  9. { _T("FirstComLib.MyString.1.0"), 0, _T("FirstComLib.MyString") }, //当前版本的项目名称
  10. { _T("FirstComLib.MyString.1.0\\CLSID"), 0, _T("{EBD699BA-A73C-4851-B721-B384411C99F4}")} //当前版本的CLSID
  11. };

使用上一篇博文的代码,来循环注册这些项即可

DllGetClassObject:该函数用来生成对应的工厂类,而工厂类负责产生对应接口的实现类。

DllCanUnloadNow:函数用来询问是否可以卸载对应的dll,一般在COM中有两个全局的引用计数,用来记录当前内存中有多少个模块中的类,以及当前有多少个线程在使用它,如果当前没有线程使用或者存在的对象数为0,则可以卸载

实现类的定义

实现部分的整体结构图如下:

由于所有类都派生自IUnknown,所在在这里就不显示这个基类了。

每个实现类都对应了一个它具体的类工厂,而项目中CMyString类的类厂的定义如下:

  1. class CMyClassFactory : public IClassFactory
  2. {
  3. public:
  4. CMyClassFactory();
  5. ~CMyClassFactory();
  6. STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
  7. STDMETHOD(LockServer)(BOOL isLock);
  8. STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);
  9. STDMETHOD_(ULONG, AddRef)(void);
  10. STDMETHOD_(ULONG, Release)(void);
  11. protected:
  12. ULONG m_refs;
  13. };

STDMETHOD宏展开如下:

  1. #define STDMETHOD(method) virtual HRESULT __stdcall method

所以上面的代码展开后就变成了:

  1. virtual HRESULT __stdcall CreateInstance((IUnknown *pUnkOuter, REFIID riid, void **ppvObject);

另外3个派生自IUnknown接口就没什么好说的,主要说说另外两个:

CreateInstance:主要用来生成对应的实现类,然后再调用实现类——CMyString的QueryInterface函数生成对应的接口

LockServer:当前是否被锁住:如果传入的值为TRUE,则表示被锁住,对应的锁计数器+1, 否则 -1

至于CMyString类的代码与之前的大同小异,也就没什么说的。

其他语言想要调用,以该项目为例,一般会经历下面几个步骤:

  1. 调用对应语言提供的产生接口的函数,该函数参数一般是传入一个组件的字符串名称。如果要引用该项目中的组件则会传入FirstComLib.MyString
  2. 在注册表的HKEY_CLASSES_ROOT\组件字符串名\CLSID(比如HKEY_CLASSES_ROOT\FirstComLib.MyString\CLSID)中找到对应的CLSID值
  3. 在HKEY_CLASSES_ROOT\CLSID\对应ID\InprocServer32(CLSID\{EBD699BA-A73C-4851-B721-B384411C99F4}\InprocServer32)位置处找到对应模块的路径
  4. 加载该模块
  5. 根据IDL文件告知其他语言里面存在的接口,由语言调用对应的创建接口的函数创建接口
  6. 调用模块的导出函数DllGetClassObject将查询到的CLSID作为第一个参数,并将接口ID作为第二个参数传入,得到一个接口

    6.后面根据idl文件中的定义,直接调用接口中提供的函数

真实ATLCOM项目的解析

最后来看看一个正式的ATLCOM项目里面的内容,来复习前面的内容,首先通过VC创建一个ATLCOM的dll项目

在项目上右键-->New Atl Object,输入接口名称,IDE会根据名称生成一个对应的接口,还是以MyString接口为例,完成这一步后,整个项目的类结构如下:



这些全局函数的作用与之前的相同,它里面多了一个_Module的全局对象,该对象类似于MFC中的CWinApp类,它用来表示整个项目的实例,里面封装了对于引用计数的管理,以及对项目中各个接口注册信息的管理,所以看DllRegisterServer等函数就会发现它们里面其实很简单,大部分的工作都由_Module对象完成。

整个IDL文件的定义如下:


  1. import "oaidl.idl";
  2. import "ocidl.idl";
  3. [
  4. object,
  5. uuid(E3BD0C14-4D0C-48F2-8702-9F8DBC96E154),
  6. dual,
  7. helpstring("IMyString Interface"),
  8. pointer_default(unique)
  9. ]
  10. interface IMyString : IDispatch
  11. {
  12. };
  13. [
  14. uuid(A61AC54A-1B3D-4D8E-A679-00A89E2CBE93),
  15. version(1.0),
  16. helpstring("FirstAtlCom 1.0 Type Library")
  17. ]
  18. library FIRSTATLCOMLib
  19. {
  20. importlib("stdole32.tlb");
  21. importlib("stdole2.tlb");
  22. [
  23. uuid(11CBC0BE-B2B7-4B5C-A186-3C30C08A7736),
  24. helpstring("MyString Class")
  25. ]
  26. coclass MyString
  27. {
  28. [default] interface IMyString;
  29. };
  30. };

里面的内容与上一次的内容相差无几,多了一个helpstring属性,该属性用于产生帮助信息,当使用者在调用接口函数时IDE会将此提示信息显示给调用者。

由于有系统框架给我们做的大量的工作,我们再也不用关心像引用计数的问题,只需要将精力集中在编写接口的实现上,减少了不必要的工作量。

至此从结构上说明了为了实现跨语言COM组件内部做了哪些工作,当然只有这些工作是肯定不够的,后面会继续说明它所做的另一块工作——提供的一堆通用的变量类型。

COM学习(三)——COM的跨语言的更多相关文章

  1. golang(gin框架),基于RESTFUL的跨语言远程通信尝试

    golang(gin框架),基于RESTFUL的跨语言远程通信尝试 背景: 在今年的项目实训过程中,遇到了这样的问题: 企业老师讲课实用的技术栈是Java springboot. 实训实际给我们讲课以 ...

  2. 跨语言学习的基本思路及python的基础学习

    笔者是C#出身,大学四年主修C#,工作三年也是C#语言开发.但在学校里其他的语言也有相应的课程,eg:Java,Php,C++都学过,当然只是学了皮毛(大学嘛,你懂得),严格来说未必入门,但这些语言的 ...

  3. 前端学习 第三弹: JavaScript语言的特性与发展

    前端学习 第三弹: JavaScript语言的特性与发展 javascript的缺点 1.没有命名空间,没有多文件的规范,同名函数相互覆盖 导致js的模块化很差 2.标准库很小 3.null和unde ...

  4. 应用AXIS开始Web服务之旅(soap web services)——使用三种不同的语言访问创建的Web服务,分别是JAVA、VB、VC

    一. 介绍 本文并不是想介绍Web服务的原理.系统架构等,我们假设您已经了解了关于Web服务的一些基本的概念.原理等知识.本文主要是针对那些已经了解Web服务概念,但是还没有亲身体会Web服务所带来令 ...

  5. 跨语言和跨编译器的那些坑(CPython vs IronPython)

    代码是宝贵的,世界上最郁闷的事情,便是写好的代码,还要在另外的平台上重写一次,或是同时维护功能相同的两套代码.所以才需要跨平台. 不仅如此,比如有人会吐槽Python的原生解释器CPython跑得太慢 ...

  6. Golang通过Thrift框架完美实现跨语言调用

    每种语言都有自己最擅长的领域,Golang 最适合的领域就是服务器端程序. 做为服务器端程序,需要考虑性能同时也要考虑与各种语言之间方便的通讯.采用http协议简单,但性能不高.采用TCP通讯,则需要 ...

  7. Golang、Php、Python、Java基于Thrift0.9.1实现跨语言调用

    目录: 一.什么是Thrift? 1) Thrift内部框架一瞥 2) 支持的数据传输格式.数据传输方式和服务模型 3) Thrift IDL 二.Thrift的官方网站在哪里? 三.在哪里下载?需要 ...

  8. Apache Thrift - 可伸缩的跨语言服务开发框架

    To put it simply, Apache Thrift is a binary communication protocol 原文地址:http://www.ibm.com/developer ...

  9. Struts2框架学习(三) 数据处理

    Struts2框架学习(三) 数据处理 Struts2框架框架使用OGNL语言和值栈技术实现数据的流转处理. 值栈就相当于一个容器,用来存放数据,而OGNL是一种快速查询数据的语言. 值栈:Value ...

随机推荐

  1. Python 点滴 IV

    [继承示意图] 类是实例的工厂, OOP就是在树中搜索属性,类事实上就是变量名与函数打成的包 . 每一个class语句会生成一个新的类对象 . 每次类调用时,就会生成一个新的实例对象 . 实例自己主动 ...

  2. ORA-16032: parameter LOG_ARCHIVE_DEST_3 destination string cannot be translated问题处理过程

    1,现象是oracle启动报错例如以下: SQL> startup ORA-16032: parameter LOG_ARCHIVE_DEST_3 destination string cann ...

  3. Springboot的默认定时任务——Scheduled注解

    本博客参考博文地址. 1.pom依赖: 引入springboot starter包即可 <dependencies> <dependency> <groupId>o ...

  4. 开源组件NanUI一周年 - 使用HTML/CSS/JS来构建.Net Winform应用程序界面

    NanUI是什么 NanUI基于ChromiumFX项目进行开发,它能让你在你的Winform应用程序中使用HTML5/CSS3/Javascript等网页技术来呈现用户界面(类似Electron). ...

  5. 青否云 - 小程序待办事项 jquery开源系统

    青否云最新开源系统:小程序待办事项 jquery-demo 青否云 Jquery demo 下载地址:https://github.com/qingful/jquery-demo 官网 http:// ...

  6. JPA 单向一对多关联关系

    映射单向一对多的关联关系 1.首先在一的一端加入多的一端的实体类集合 2.使用@OneToMany 来映射一对多的关联关系3.使用@JoinColumn 来映射外键列的名称4.可以使用@OneToMa ...

  7. Intellij Idea配置MapReduce编程环境

    原文参考地址:http://www点w2bc点com/article/229178 增加内容:question1: Hadoop2以上版本时,在Hadoop2的bin目录下没有winutils.exe ...

  8. Vagrant安装完lnmp后,配置linux和windows共享文件并配置虚拟主机访问项目

    虚拟机目录下的Vagrantfile文件是vagrant的配置文件,如果想把虚拟机当作一台服务器,可以通过ip访问,需要修改配置文件进行配置. (1)第一步:打开虚拟机目录下的Vagrantfile文 ...

  9. dropout理解:1神带9坑

    Dropout是深度学习中防止过拟合的一项非常常见的技术,是hinton大神在12年提出的一篇论文里所采用的方法.有传言hinton大神的数学功底不是很好,所以他所提出的想法背后的数学原理并不是很复杂 ...

  10. Xamarin Android自定义文本框

    xamarin android 自定义文本框简单的用法 关键点在于,监听EditText的内容变化,不同于java中文本内容变化去调用EditText.addTextChangedListener(m ...