代码的链接在《用C#编写一个进程外的COM组件》,小技巧:如果你要同时看示例代码和讲解的话,可以用浏览器分别打开示例代码和这篇文章,然后使用Windows提供的纵向平铺窗口功能就可同时看两篇文章了。

TestComVisibleClass.cs里面定义了我们要发布给COM客户程序的.NET对象,由于我们的.NET进程外组件需要调用几个COM库的API,因此在ComHelperClass里面定义这些API在.NET里面的声明方式,正确地声明P/Invoke函数的原型非常困难,要求程序员对Win32,COM和托管代码都很熟悉才可以做,所以我写了另外一篇文章《使用Signature Tool自动生成P/Invoke调用Windows API的C#函数声明》简化P/Invoke函数声明的步骤。

ComHelperClass类里面CoRegisterClassObject函数的原型比较有意思,注意rclsid参数前面的[MarshalAs(UnmanagedType.LPStruct)]属性,这个属性告诉.NET,在从.NET一端传递rclsid参数值到非托管代码一端时,不要使用默认的列集(Marshal)规则,在P/Invoke里面,NET默认将结构体对象完整复制到非托管的内存里,使用UnmanagedType.LPStruct告诉NET将Guid对象的指针传递给非托管函数,就省去了在调用的时候添加ref关键字的麻烦,UnmanagedType.LPStruct会有另外一篇文章来解释它,它有些特别。.NET默认将类实例对象列集成VARIANT拷贝到非托管的内存里,因此第二个参数我用[MarshalAs(UnmanagedType.IUnknown)]通知.NET需要将这个对象实例列集成IUnknow *指针。

我们的.NET进程外组件有可能被一些C++编写的客户端调用到,对于C++程序来说,使用前绑定(即通过接口指针调用接口成员函数)的方式会更加方便一些,否则使用延迟绑定技术(通过IDispatch接口调用接口成员函数)的方式C++代码会比较复杂一些。因此将TestComVisibleClass的一些方法和属性提取成一个接口,并且分别给ITestComVisible接口和TestComVisibleClass类分配了一个GUID,在COM世界里,前者就是我们熟知的IID,后者则是CLSID。

同时,为了方便VB程序使用.NET进程外组件,我还特意给ITestComVisible接口的属性和函数指定了DispID,因为在OLE规范里,DispID为0,-1等几个特殊值的函数有特殊的意义,这一点我将会在后面的文章里讲到。

由于我们不能用mscoree.dll自己提供的类激活策略来在COM中激活我们的.NET对象,mscoree.dll默认提供的激活策略也会在后面的文章里讲到,我们只好显示地提供类厂,并且将我们的类厂在COM运行库里面注册一下。类厂(ClassFactory)的相关接口同样需要定义一个C#形式的原型,一个比较取巧的办法就是使用tlbimp生成一个dll,或者就是看看System.Runtime.InteropServices.ComTypes命名空间里面是否已经有定义好了的类型?IClassFactory最重要的一个函数就是CreateInstance,我们的实现就是看看客户端需要什么样子的接口,如果是IUnknown或者是我们发布的ITestComVisible(102行到107行),否则就在109行的位置上抛出一个异常(碰到错误就抛异常的习惯在COM世界里不是一个友好的方式,我的代码为了简单就采取了抛异常的方式,更好的做法是返回错误码,由COM客户端决定如何处理这个错误。)

在程序启动的时候,我们将自己实现的类厂注册在COM运行库里面(Program.cs的48行到53行),记得保留CoRegisterClassObject返回给我们的注册ID(第53行)。Program.cs里面的35行到44行可选,它们的目的是做一些安全检查,确保只有一些有权限的用户才能调用你的C# Dcom组件,如果你对安全性不关心的话,可以删除它们。在程序退出的时候(Program.cs的26行到29行)扫除一些尾巴,释放一些资源。

那为什么我们还要一个注册表文件呢?这是因为在前绑定调用方式里,我们需要将指针在进程间传递,比如在客户端C++代码的第21行,实际上CoCreateInstanceEx需要启动我们的.NET进程(也即是进程外COM服务器),调用我们在Program.cs的第53行注册过了的IClassFactory接口来创建.NET对象实例,然后将实例的ITestComVisible接口指针从.NET进程传回C++客户端进程里来。可能有人会说,可以直接将指针的地址到C++客户端进程去嘛?这是不行的,因为Windows操作系统将进程与其它进程独立开来,简单说,一个进程里面的虚拟内存地址在另外一个进程里面可能指向一个垃圾,原理请参看操作系统书籍里面关于虚拟内存的描述。

为了在进程间传递ITestComVisible接口指针,COM库需要知道如何列集ITestComVisible指针,一般情况下,程序员需要提供另外一个DLL,这个DLL包含了列集ITestComVisible指针的代码。为什么要提供代码来列集指针的原因是,不同的接口包含不同的函数,例如在客户端C++代码的第25行和第26行,COM库需要列集远程函数调用(RPC),也就是需要一个方法将TestMethod和Release的调用区分开来。一般这个DLL,可以通过用msidl.exe分析IDL文件来生成列集函数的源代码编译生成。然而,这种方法比较麻烦,因此微软提供了OLEAUT32.dll,里面有一个通用的接口列集函数(但是这个函数不能列集所有的接口,可以列集的接口需要遵循一些规则,这个后面有时间再讲),可以通过分析TLB文件来列集指针,因为TLB文件相当于.NET Assembly里面的元数据,oleaut32.dll可以知道你的com组件里面有哪些接口,各个接口的声明又是怎样的,函数的参数类型是什么等等。但是oleaut32.dll需要查询注册表才能知道接口存在的Tlb文件的位置:

1.         因此注册表代码里面的第3行到第16行在注册表里面保存了类型库的存放路径,注意,在COM世界里,tlb文件也是用GUID来唯一标识的,你可以用oleview.exe打开regasm.exe或者tlbexp.exe生成的tlb文件,找到[custom(9903F14C-12CE-4c99-9986-2EE3D7D588A8)…]那一段文字来找到Tlb文件的GUID。

2.         注册表代码里面的第18行到第28行,在注册表里面保存了列集ITestComVisible接口的信息,例如ProxyStubClsid指的是采用oleaut32.dll提供的通用接口列集函数,并且保存了该函数所使用tlb文件的信息(第26行到28行)。如果注册表里面没有列集接口的信息,CoCreateInstanceEx函数会返回E_NOINTERFACE(不支持此接口)错误—一个让人稀里糊涂的错误代码。

3.         最后注册表代码里面的第30行到第41行向全世界声明(不好意思,不是中国人民从此站起来了的那种声明):我们的.NET对象不需要通过mscoree来在COM端激活,自己可以完成激活操作,因此我们删掉了由regasm.exe插入的InprocServer32键值,而是添加了LocalServer32键值。

附,上面注册表代码里面的第3行到第28行可以用RegisterTypeLib函数来完成,而RegisterTypeLib所需要的ptlib参数可以通过LoadTypeLib函数来拿到。

本文转自 donjuan 博客园博客,原文链接:http://www.cnblogs.com/killmyday/archive/2009/02/21/1395432.html   ,如需转载请自行联系原作者

用C#编写一个进程外的COM组件示例代码讲解相关推荐

  1. 用python朗读字母-使用python编写一个语音朗读闹钟功能的示例代码

    想找一个可以播放文字的闹钟找不到,自己写一个更简单.TTS实现由很多种办法,百度等都提供了API接口,但类似百度,需要先注册等一系列动作. 其实windows自带的win32com功能可以简单实现TT ...

  2. python源码脚本实例_python编写一个会算账的脚本的示例代码

    python算账脚本 1.假如小明卡里有10000元去商场买东西发现钱不够又向父母借了5000账单如下 2.以下脚本就能实现上面的运算 from time import strftime import ...

  3. python在线朗读-使用python编写一个语音朗读闹钟功能的示例代码

    想找一个可以播放文字的闹钟找不到,自己写一个更简单.TTS实现由很多种办法,百度等都提供了API接口,但类似百度,需要先注册等一系列动作. 其实windows自带的win32com功能可以简单实现TT ...

  4. python编写脚本教程_python编写一个会算账的脚本的示例代码

    python算账脚本 1.假如小明卡里有10000元去商场买东西发现钱不够又向父母借了5000账单如下 2.以下脚本就能实现上面的运算 from time import strftime import ...

  5. python闹钟源码_使用python编写一个语音朗读闹钟功能的示例代码

    想找一个可以播放文字的闹钟找不到,自己写一个更简单.TTS实现由很多种办法,百度等都提供了API接口,但类似百度,需要先注册等一系列动作. 其实windows自带的win32com功能可以简单实现TT ...

  6. python 语音朗读软件下载_使用python编写一个语音朗读闹钟功能的示例代码

    想找一个可以播放文字的闹钟找不到,自己写一个更简单.TTS实现由很多种办法,百度等都提供了API接口,但类似百度,需要先注册等一系列动作. 其实windows自带的win32com功能可以简单实现TT ...

  7. 微博短网址 php,一个php短网址的生成示例代码(仿微博短网址)

    一个php短网址的生成示例代码(仿微博短网址) 发布于 2014-12-15 12:15:11 | 148 次阅读 | 评论: 0 | 来源: 网友投递 PHP开源脚本语言PHP(外文名: Hyper ...

  8. HTML5移动的代码,HTML_HTML5实现一个能够移动的小坦克示例代码,复制代码代码如下: !DOCTYPE h - phpStudy...

    HTML5实现一个能够移动的小坦克示例代码 复制代码代码如下: 您的浏览器不支持canvas标签 var canvas1=document.getElementById('tankMap'); var ...

  9. 12.编写COM进程外组件

    相比进程内组件,进程外组件的编写较为麻烦,在前面已经讲的IDL和进程外组件原理基础上,本节以一个简单实例讲解进程外组件的编写步骤和注意事项. 1.IDL生成代理/存根 假如我们需要实现一个猫猫翻译的接 ...

最新文章

  1. easyui combotree的使用
  2. 联想ThinkPad E450装系统后开机一直停留在BootMenu上,无法选择硬盘进入
  3. E-mail Composition and Decoding
  4. 【BUG解决】使用body-parser失效的实例解决
  5. 计算机的硬盘和光盘数,磁盘与光盘介绍-计算机组成原理与汇编语言-电子发烧友网站...
  6. COE、SOE、EOE、FOE是什么?
  7. 基于 CNN 和迁移学习的农作物病害识别方法研究
  8. 如何给linux添加新硬盘(转)
  9. W25Q64 Flash芯片原理与应用方案(含W25Q64中文数据手册)
  10. struts2拦截器实现登录限制
  11. 推荐10本必读的心理学书籍
  12. BZOJ2109 [Noi2010]Plane 航空管制
  13. 【第二章】Google Guava 之 Splitter 学习
  14. DCM: 诊断通信管理 (Diagnostic Communiction Manager)
  15. 熊绎:我看软件工程师的职业规划(转载)
  16. WebGL入门(三十九)-透明与不透明物体共存,绘制透明面和不透明面的立方体
  17. 中了一篇ccf的b类会议sdm'15,要去温哥华参会。很高兴啊!!!!
  18. 东网科技、Rancher Labs联合发布“容器+虚拟化”双引擎超融合平台HOR
  19. JAVAweb连接不上数据库原因及解决办法
  20. 我的世界服务器清道夫不显示,我的世界清道夫指令 | 手游网游页游攻略大全

热门文章

  1. 循环队列(java)
  2. python语句写入oracle_将Python变量插入Oracle数据库
  3. ENSP如何开启服务器的http_Centos\Liunx如何安装MySQL?
  4. html 设置font size,css font-size属性说明
  5. 求解模糊运动角度matlab,动态模糊图像复原MATLAB程序
  6. 错误票据java_【蓝桥杯】错误票据 - osc_bskh1wlw的个人空间 - OSCHINA - 中文开源技术交流社区...
  7. 1716.计算力扣银行的钱-LeetCode
  8. mysql 数据库连接不够_一个Web报表项目的性能分析和优化实践(二):MySQL数据库连接不够用(TooManyConnections)问题的一次分析和解决案例...
  9. 去除标签_生活小常识:3m胶怎么去除
  10. unity天空盒渐变_Unity 制作天空盒