C++ COM与C#组件的互操作.docx
- 文档编号:11718334
- 上传时间:2023-03-31
- 格式:DOCX
- 页数:16
- 大小:164.16KB
C++ COM与C#组件的互操作.docx
《C++ COM与C#组件的互操作.docx》由会员分享,可在线阅读,更多相关《C++ COM与C#组件的互操作.docx(16页珍藏版)》请在冰豆网上搜索。
C++COM与C#组件的互操作
C++环境下按COM方式调用C#组件的方法
1.互操作基础
首先需要声明清楚的是:
COM与.NET组件(准确的说,应该叫程序集,Assembly)是两个不同的概念!
COM组件对象模型是微软早期的一种软件复用标准和技术方案,用IDL语言定义接口,用非托管语言(C++)定义类以实现接口功能,编译生成DLL组件,并生成相应的类型定义库TLB。
而.NET程序集则是一种基于CLR的多编程语言无缝集成的软件封装技术。
可采用.NET编程语言(C#,VB.NET)直接定义接口与类,能够简单生成程序集。
编写源代码时按照一定的要求赋予GUID属性,生成DLL后注册到GAC即可实现共享复用。
AlthoughCOMclientscancallcodethatisexposedinapublicclassby.NETservers,.NETcodeisnotdirectlyaccessibletoCOMclients.Inordertouse.NETcodefromaCOMclient,youneedtocreateaproxyknownasaCOMcallablewrapper(CCW).
COMCallableWrappers(COM包装)
COM在以下几个重要方面与.NETFramework对象模型存在差异:
生命周期(自己管理/CLR管理)、对象实例的获取(接口指针QueryInterface/反射)、内存的管理(位置不变/CLR调整)。
ManagedcodecomponentsnotonlydependontheCLR,theyrequirethecomponentswithwhichtheyinteracttodependontheCLR.BecauseCOMcomponentsdon'toperatewithintheCLR,theyareunabletocallmanagedcodecomponentsdirectly.TheunmanagedcodesimplycannotreachintotheCLRtodirectlycallmanagedcomponents.Thewayoutofthisdilemmaistouseaproxy(CCW).
2.开发一个面向非托管客户的.NET程序集步骤
要使基于.NET的Class以COM组件的形式对非托管代码可见(可调用),必须要满足两个基本条件:
一是,必须给Interface和Class分别添加GUID属性;二是,必须导出程序集对等的COM类型库TLB并且在注册表中注册组件信息(这里的组件即InteropCOM,此组件实际上以托管程序集的形势存在,在被调用时,由CCW将其实例化为内存中的COM对象并管理其生存周期与内存释放)。
创建.NET程序集的大致步骤如下:
1)定义接口,并创建类以实现接口功能。
添加引用:
usingSystem.Runtime.InteropServices;usingSystem.Windows.Forms;在类与接口定义前分别添加GUID属性,代码如下
[Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")]
在类定义前添加属性[ClassInterface(ClassInterfaceType.None)]。
设置[assembly:
ComVisible(true)]使得对COM可见(重要!
)
2)给程序集添加版本属性;添加强命名Key(非必须);
3)[非必须步骤]将程序集添加到公用程序集缓冲区。
Installthe.NETassemblyintotheGlobalAssemblyCache(GAC)sothatitwillbeavailableasasharedassembly.ToinstallanassemblyintotheGAC,usethegacutiltool:
gacutil/iYourNETServer.dll
4)从生成的DLL类库中导出类型库(typelibrary,TLB),并注册COM组件。
可调用Regasm工具(AssemblyRegistrationTool)一步完成。
如果只需要导出tlb,可以使用Tlbexp工具(typelibraryexporter)。
当重新编译生成Dll时需要使用REGASM/u命令将前一次Dll注销。
或者,最直接最简单的方式是设置项目属性,在[生成]选项卡中,将“为COMInterOp注册”选项设置为true即可在生成程序集时完成注册。
REGASMComInteropDemo.dll/tlbComInteropDemo.tlb
REGASM/uComInteropDemo.dll
注意:
程序集可以是专用的或共享的(在GAC中)。
专用程序集仅用于与该程序集位于同一目录结构的客户端;共享程序集可用于任何本地COM应用程序。
所有程序集和类型库都必须在Windows注册表中注册,以便COM客户端透明地使用托管类型。
共享程序集应安装在全局程序集缓存中。
所有共享程序集必须带有强名称(由发行者签名,可运行命令:
sn-kNETServer.snk。
生成strongnamekey文件(存储了公钥密钥对),编译项目时放在bin程序目录下.)。
当任何引用程序集中类型的COM应用程序遇到Mscoree.dll时,都会查找该程序集。
3.在非托管环境下(C++)编写客户端调用.Net组件
1)新建C++工程。
在CPP源文件中添加代码导入COM引用。
(可以导入DLL或导入TLB)
#import“
2)添加代码调用COM对象。
CoInitialize(NULL);
//声明Pointer
//CreateInstance();
//调用对象实例的成员函数
CoUninitialize();
3)编译工程,运行。
4.C#客户端调用非托管COM组件
C#调用C++的COM比较简单,下面通过一个实例来说明:
一、建立ATLCOM,增加接口ITest和实现类Test,增加以下函数:
IDL文件:
interface ITest :
IDispatch
{
[id
(1), helpstring("method ADD")] HRESULT ADD([in] LONG x, [in] LONG y, LONG* z);
[id
(2), helpstring("method UpperCase")] HRESULT UpperCase([in] CHAR A, CHAR* B);
[id(3), helpstring("method LowerCase")] HRESULT LowerCase([in] BSTR A, BSTR* B);
[id(4), helpstring("method Change")] HRESULT Change([in] VARIANT A, [out] VARIANT* B);
};
ITest文件:
MIDL_INTERFACE("52CA8A5C-593D-4E2E-B58F-BB6C6604EAF2")
ITest :
public IDispatch
{
public:
virtual /**//* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ADD(
/**//* [in] */ LONG x,
/**//* [in] */ LONG y, LONG *z) = 0;
virtual /**//* [helpstring][id] */ HRESULT STDMETHODCALLTYPE UpperCase(
/**//* [in] */ CHAR A,
CHAR *B) = 0;
virtual /**//* [helpstring][id] */ HRESULT STDMETHODCALLTYPE LowerCase(
/**//* [in] */ BSTR A,
BSTR *B) = 0;
virtual /**//* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Change(
/**//* [in] */ VARIANT A,
/**//* [out] */ VARIANT *B) = 0;
};
在Test类中实现以上接口函数。
二、在C#中的使用方法
1)使用IDE中给项目添加引用,来AddReference–>com选项卡下查找到需要引用的COMdll,在此之前COM必须先注册:
(一般编译时自动注册,另外regsvr32.exe可注册COM)
2)使用命令TlbimpATLCOM.tlb/out:
C:
\ATLCOM.dll,然后浏览引用所生成的DLL(tblmp命令帮你注册com)。
3)TypeLibConverter类。
TypeLibConverter类(位于System.Runtime.InteropServices命名空间中)提供了将类型库中的Coclass和接口转换为程序集中的元数据的方法。
此API将生成与Tlbimp.exe相同的元数据输出,与Tlbimp.exe不同的是,TypeLibConverter类可以将内存中的类型库转换为元数据。
4)自定义包装。
当类型库不可用或不正确时,一种可选的做法是在托管源代码中创建类或接口的重复定义。
然后,用面向运行库的编译器来编译源代码以生成程序集中的元数据。
要手动定义COM类型,必须具备下列各项:
所定义的coclass和接口的精确描述。
可生成正确.NETFramework类定义的编译器,如C#编译器。
有关类型库到程序集转换规则的知识。
编写自定义包装是一种较少使用的高级技术。
三、(方法二)使用ComImport引用COM组件
COMInterop提供对现有COM组件的访问,而不需要修改原始组件。
使用ComImport引用COM组件常包括下面几个方面的问题:
1、创建COM对象。
2、确定COM接口是否由对象实现。
3、调用COM接口上的方法。
4、实现可由COM客户端调用的对象和接口。
创建COM类包装
要使C#代码引用COM对象和接口,需要在C#中包含COM接口的定义。
完成此操作的最简单方法是使用TlbImp.exe。
TlbImp将COM类型库转换为.NET框架元数据,从而有效地创建一个可以从任何托管语言调用的托管包装。
如果使用VisualStudio开发环境,则只需添加对COM类型库的引用,将为您自动完成此转换。
检查TlbImp输出的一种很好的方法是运行工具Ildasm.exe(Microsoft中间语言反汇编程序)来查看转换结果。
TlbImp执行下列转换:
1、COMcoclass转换为具有无参数构造函数的C#类。
2、COM结构转换为具有公共字段的C#结构。
虽然TlbImp是将COM定义转换为C#的首选方法,但也不是任何时候都可以使用它(例如,在没有COM定义的类型库时或者TlbImp无法处理类型库中的定义时,就不能使用该方法)。
在这些情况下,另一种方法是使用C#属性在C#源代码中手动定义COM定义。
创建C#源映射后,只需编译C#源代码就可产生托管包装。
执行COM映射需要理解的主要属性包括:
1、ComImport:
它将类标记为在外部实现的COM类。
2、Guid:
它用于为类或接口指定通用唯一标识符(UUID)。
3、InterfaceType,它指定接口是从IUnknown还是从IDispatch派生。
4、PreserveSig,它指定是否应将本机返回值从HRESULT转换为.NET框架异常。
声明COMcoclass
COMcoclass在C#中表示为类。
这些类必须具有与其关联的ComImport属性。
下列限制适用于这些类:
1、类不能从任何其他类继承。
2、类不能实现任何接口。
4、类还必须具有为其设置全局唯一标识符(GUID)的Guid属性。
以下示例在C#中声明一个coclass:
//声明一个COM类FilgraphManager
[ComImport,Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")]
classFilgraphManager
{}
C#编译器将添加一个无参数构造函数,可以调用此构造函数来创建COMcoclass的实例。
创建COM对象
COMcoclass在C#中表示为具有无参数构造函数的类。
使用new运算符创建该类的实例等效于在C#中调用CoCreateInstance。
使用以上定义的类,就可以很容易地实例化此类:
classMainClass
{
publicstaticvoidMain()
{FilgraphManagerfilg=newFilgraphManager();}
}
四、在C#的client调用COM提供的接口,我们可以看到在C#中COM的接口和函数被变换为如下:
//MemberofATLcom.TestClass:
publicTestClass();
publicvirtualvoidADD(intx,inty,refintz);
publicvirtualvoidUpperCase(sbyteA,refsbyteB);
publicvirtualvoidChange(objectA,outobjectB);
publicvirtualvoidLowerCase(stringA,refstringB);
From:
C++Programmer’sCookbook
5.snk文件和AssemblyInfo.cs文件的作用
snk文件在.net里面被用作存放密钥或密钥对的存储文件,生成密钥对snk文件可以用.net中的sn.exe命令,如“sn-kkeyPair.snk”。
snk本身只是用来存放非对称密钥的,但在各个需要用到加密、签名的地方都可以使用:
用snk文件生成强命名程序集,这样一个assembly才可以被赋予full-trust属性,也可以被添加到GAC中。
在VS.NET中生成strong-namedassembly,只需要在AssemblyInfo.cs里面添加一下代码并编译即可:
[assembly:
AssemblyDelaySign(false)]
[assembly:
AssemblyKeyFile("..\\..\\keyPair.snk")]
[assembly:
AssemblyKeyName("")]
强命名程序集的缘由:
目前Windows中出现的DLLHell问题(两个不同的公司可能开发处具有相同名称的程序集,如果将相同名称的程序集放置到同一个目录下,则会出现程序集覆盖现象,最后安装的程序集会覆盖前面的程序集,从而可能导致应用序不能正常运行)。
由此看来,仅靠名称来区分程序集是不足够的。
CLR采取了强命名程序集的方式来唯一的表示程序集。
强命名程序集包含四个标识:
名称,版本号,语言文化标识和一个公有/私有密钥对。
两种程序集/两种部署方式:
.Net支持两种程序集:
弱命名程序集和强命名程序集(注:
.Net框架中没有弱命名程序集,只是为了和强命名程序集相对应而已)。
弱命名程序集和强命名程序集在结构上是相同的。
他们都采用PE文件格式,包含PE表头,CLR表头,元数据和清单表。
区别在于:
强命名程序集拥有一个发布者的公钥/私钥签名对,他们用于唯一的标识程序集的发布者。
通过公钥/私钥对,我们可以对程序集进行唯一的标识,安全策略和版本策略。
6.GAC
GAC中的所有的Assembly都会存放在系统目录"%winroot%\assembly下面。
放在系统目录下的好处之一是可以让系统管理员通过用户权限来控制Assembly的访问。
GAC全称是GlobalAssemblyCache,他的作用是可以存放一些有很多程序都要用到的公共Assembly,例如System.Data、System.Windows.Forms等等。
这样,很多程序就可以从GAC里面取得Assembly,而不需要再把所有要用到的Assembly都拷贝到应用程序的执行目录下面。
举例而言,如果没有GAC,那么势必每个WinForm程序的目录下就都要从C:
\WINDOWS\Microsoft.NET\Framework\v1.0.3705下面拷贝一份System.Windows.Forms.dll,这样显然不如都从GAC里面取用方便,也不利于Assembly的升级和版本控制。
GAC的作用就是提供给CLR一个已知的确定的目录去寻找引用的程序集。
除了系统默认放置在GAC中的Assembly如System.Windows.Forms以外,我们也可以添加自己的Assembly:
1)创建一个strong-name的Assembly,例如ToolbarComponent.dll
2)运行gacutil/iToolbarComponent.dll,把这个Assembly添加到GAC,或者直接将需要安装的DLL文件拖放到C:
\Windows\Assembly文件夹中,即可简单的完成安装。
3)在程序中动态装载:
System.Reflection.Assemblyass=Assembly.Load("ToolbarComponent,Version=1.0.934.20434,Culture=neutral,PublicKeyToken=65f45658c8d4927f");
MessageBox.Show("IstheassemblyloadedfromGAC?
"+ass.GlobalAssemblyCache);
在上面的程序中,ToolbarComponent就是从GAC装载而不是从程序的运行目录下的dll文件中装载,程序目录下不需要放置ToolbarComponent.dll程序也能正常运行。
另外,Assembly.Load()中的参数可以通过"gacutil-l"查到。
要将用sn工具生成的密钥与项目的程序集关联,请在VisualStudio.NET解决方案资源管理器中双击AssemblyInfo.cs文件。
此文件具有一个程序集属性列表,默认情况下,在VisualStudio.NET中创建项目时将包括这些属性。
在AssemblyInfo.cs代码中修改“AssemblyKeyFile”程序集属性,如下所示:
[assembly:
AssemblyKeyFile("C:
\GACKey\GACKey.snk")]
另外,VisualStudio2005/2010允许我们使用项目的属性页指定*.snk文件的位置(这是目前更提倡的一种做法,在VisualStudio2005使用[AssemblyKeyFile]特性的话,编译时会产生警告)。
选择Signing(签名)节点,提供*.snk的路径,选中“Signtheassembly”复选框即可。
7.Assembly
使用VB、C#或任一种CLR支持的编程语言编写的应用程序源代码,经过与编程语言对应的编译器将源代码翻译成一个程序集(Assembly)——.Net组件,为EXE或者DLL文件。
程序集由中间语言MSIL(简称IL)代码、元数据和一个文件清单组成。
程序集需要在CLR的控制下编译成特定于机器的汇编指令才能运行。
CLR使用文件清单来确定应用程序集的正确版本,并检查MSIL代码与元数据以确认代码是类型安全的。
From《ArcObjectsGIS应用开发——基于C#.NET》(兰小机,刘德儿)
在.net架构中,集合(Assembly)的概念代替了原来的组件概念,集合可以认为是受管理的组件。
集合运行在CLR中。
8.COM组件与.NET组件对比
COM组件与.NET组件是两种不同的模型,.NET组件模型是微软在.NET平台上的一种新方案,比原COM组件模型优秀的地方在于它解决了DLLHELL问题,且开发与维护要相对容易。
解决COM组件模型带来的DLLHELL问题:
1)在COM中,对于每一种类型都采用128bit的GUID进行定义,而在CLR中采用了强命名(strongnamed)机制,为了更好地确保唯一性,采用了128字节的公钥和在局部范围内是保持唯一的类型名称来提供全球唯一标识。
当一个客户端应用程序调用某集合时,在客户应用程序中存有一个64位的公钥Hash值,从而能确保被调用的集合是正确的集合。
2)在.net中,sidebyside概念是版本问题的核心。
"sidebyside"是在同一台机器上同时运行不同版本的相同组件的能力。
使用支持并列的组件,编程人员不必努力维护严格的向后兼容,因为不同的应用程序自由使用某个共享组件的不同版本。
PIA(PrimaryInteropAssembly)和普通的InteropAssembly:
当用.NET调用已有的COM组件的时候,一般需要使用tlbimp导入COM组件的TypeLibrary信息,生成对应的InteropAssembly。
所谓PIA(PrimaryInteropAssembly),则是“官方”发布的对于某个COM组件的.NETDLL。
比如微软公司发布了Office2003的COM组件,可以用于非托管环境下操作COM,同时,微软也发布了Office2003所对应的PIA,用于.NET托管环境下调用。
PIA和普通的Interop(又称为AIA,AlternativeInteropAssembly)的不同之处在于:
1.PIA是官方发布并经过Sign,可以在不同程序中共享。
而AIA如果被不同的公司Sig
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ COM与C#组件的互操作 COM C# 组件 操作