C++环境下按COM方式调用C#组件的方法
1.互操作基础
首先需要声明清楚的是:COM与.NET组件(准确的说,应该叫程序集,Assembly)是两个不同的概念! COM组件对象模型是微软早期的一种软件复用标准和技术方案,用IDL语言定义接口,用非托管语言(C++)定义类以实现接口功能,编译生成DLL组件,并生成相应的类型定义库TLB。而.NET程序集则是一种基于CLR的多编程语言无缝集成的软件封装技术。可采用.NET编程语言(C#,VB.NET)直接定义接口与类,能够简单生成程序集。编写源代码时按照一定的要求赋予GUID属性,生成DLL后注册到GAC即可实现共享复用。
Although COM clients can call code that is exposed in a public class by .NET servers, .NET code is not directly accessible to COM clients. In order to use .NET code from a COM client, you need to create a proxy known as a COM callable wrapper (CCW).
COM Callable Wrappers (COM包装)
COM 在以下几个重要方面与 .NET Framework 对象模型存在差异:生命周期(自己管理 / CL
R管理)、对象实例的获取(接口指针QueryInterface / 反射)、内存的管理(位置不变 / CLR调整)。
CCW概览图
通过运行时可调用包装(RCW)访问 COM 对象
Managed code components not only depend on the CLR, they require the components with which they interact to depend on the CLR. Because COM components don't operate within the CLR, they are unable to call managed code components directly. The unmanaged code simply cannot reach into the CLR to directly call managed components. The way out of this dilemma is to use a proxy(CCW).
2.开发一个面向非托管客户的.NET 程序集步骤
要使基于.NET的Class以COM组件的形式对非托管代码可见(可调用),必须要满足两个基本条件:
一是,必须给Interface和Class分别添加GUID属性;二是,必须导出程序集对等的COM类
型库TLB并且在注册表中注册组件信息(这里的组件即Interop COM,此组件实际上以托管程序集的形势存在,在被调用时,由CCW将其实例化为内存中的COM对象并管理其生存周期与内存释放)。
创建.NET程序集的大致步骤如下:
1)定义接口,并创建类以实现接口功能。添加引用:; ; 在类与接口定义前分别添加GUID属性,代码如下
[Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")]
在类定义前添加属性[ClassInterface(ClassInterfaceType.None)]。
设置[assembly: ComVisible(true)]使得对COM可见(重要!)
2)给程序集添加版本属性;添加强命名Key(非必须);
3)[非必须步骤] 将程序集添加到公用程序集缓冲区。Install the .NET assembly into the Global Assembly Cache (GAC) so that it will be available as a shared assembly. To inst
all an assembly into the GAC, use the gacutil tool:
gacutil /i YourNETServer.dll
4)从生成的DLL类库中导出类型库(type library, TLB), 并注册COM组件。
可调用Regasm工具(Assembly Registration Tool)一步完成。如果只需要导出tlb,可以使用Tlbexp工具(type library exporter)。当重新编译生成Dll时需要使用REGASM /u命令将前一次Dll注销。或者,最直接最简单的方式是设置项目属性,在[生成]选项卡中,将“为COM InterOp 注册”选项设置为true即可在生成程序集时完成注册。
REGASM ComInteropDemo.dll /tlb ComInteropDemo.tlb
REGASM /u ComInteropDemo.dll
注意:程序集可以是专用的或共享的(在GAC中)。专用程序集仅用于与该程序集位于同一目录结构的客户端;共享程序集可用于任何本地 COM应用程序。所有程序集和类型库都必须在 Windows 注册表中注册,以便 COM 客户端透明地使用托管类型。共享程序集应安装
在全局程序集缓存中。所有共享程序集必须带有强名称(由发行者签名, 可运行命令: sn -k NETServer.snk。 生成strong name key文件(存储了公钥密钥对),编译项目时放在bin程序目录下.)。 当任何引用程序集中类型的 COM 应用程序遇到 Mscoree.dll 时,都会查该程序集。
3.在非托管环境下(C++)编写客户端调用.Net组件
1)新建C++工程。在CPP源文件中添加代码导入COM引用。(可以导入DLL或导入TLB)
#import “<Full Path>\com.MyInterop.tlb" named_guids raw_interfaces_only
2)添加代码调用COM对象。
CoInitialize(NULL);
//声明Pointer
//CreateInstance();
//调用对象实例的成员函数
CoUninitialize ();
3)编译工程,运行。
4.C#客户端调用非托管COM组件
C#调用C++的COM比较简单,下面通过一个实例来说明:
一、建立ATL COM,增加接口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] VARIAN
[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] VARIAN
T* 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;
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] */visual studio和vs code的区别 VARIANT A,
/**//* [out] */ VARIANT *B) = 0;
};
/**//* [in] */ BSTR A,
BSTR *B) = 0;
virtual /**//* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Change(
/**//* [in] */visual studio和vs code的区别 VARIANT A,
/**//* [out] */ VARIANT *B) = 0;
};
在Test类中实现以上接口函数。
二、在C#中的使用方法
1)使用IDE中给项目添加引用,来Add Reference –> com选项卡下查到需要引用的COM dll,在此之前COM必须先注册:(一般编译时自动注册,另外可注册COM)
2)使用命令Tlbimp ATLCOM.tlb /out:C:\ATLCOM.dll , 然后浏览引用所生成的DLL(tblmp命令帮你注册com)。
3)TypeLibConverter类。 TypeLibConverter 类(位于 System.Runtime.InteropServices 命名空间中)提供了将类型库中的 Coclass 和接口转换为程序集中的元数据的方法。此 API 将生成与 相同的元数据输出,与 不同的是,TypeLibConverter 类可以将内存中的类型库转换为元数据。
4)自定义包装。
当类型库不可用或不正确时,一种可选的做法是在托管源代码中创建类或接口的重复定义。然后,用面向运行库的编译器来编译源代码以生成程序集中的元数据。 要手动定义 COM 类型,必须具备下列各项:
所定义的 coclass 和接口的精确描述。
可生成正确 .NET Framework 类定义的编译器,如 C# 编译器。
有关类型库到程序集转换规则的知识。编写自定义包装是一种较少使用的高级技术。
三、(方法二)使用ComImport引用COM组件
COM Interop 提供对现有 COM 组件的访问,而不需要修改原始组件。使用ComImport引用COM组件常包括下面 几个方面的问题:
1、创建 COM 对象。
2、确定 COM 接口是否由对象实现。
3、调用 COM 接口上的方法。
4、实现可由 COM 客户端调用的对象和接口。
创建 COM 类包装
要使 C# 代码引用COM 对象和接口,需要在 C# 中包含 COM 接口的定义。完成此操作的最简单方法是使用 。TlbImp 将 COM 类型库转换为 .NET 框架元数据,从而有效地创建一个可以从任何托管语言调用的托管包装。如果使用 Visual Studio 开发环境,则只需添加对 COM 类型库的引用,将为您自动完成此转换。检查 TlbImp 输出的一种很好的
方法是运行工具 (Microsoft 中间语言反汇编程序)来查看转换结果。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论