com的概念:
COM对象是遵循COM规范编写、以Win32动态链接库(DLL)或可执行文件(EXE)形式发布的可执行二进制代码,能够满足对组件架构的所有需求。
com的定义
COM:The Component Object Model 组件对象模型
遵循COM的规范标准,组件与应用、组件与组件之间可以互操作,极其方便地建立可伸缩的应用系统。COM是一种技术标准
综上,可以看出,com有3种特征:1、与接口相似,有自己严格的标准规范 2、实现更偏向功能完整的组件 3、与语言无关
再来看下.net core创建com对象的方式:
1.使用.NET包装COM组件
这是最简单的就是导入COM组件所在的DLL,让IDE生成。NET一个IL包装加到项目中,这样原来COM里面所有实现了IDispatch,Dual的COM类型及其相关类型就可以直接在。NET程序里面使用,比如以前在2003时代,想要写自己的基于IE的浏览器,就得手动加入与IWebBrowser2接口相关的DLL,这种方式是大家最常用的,也是最傻瓜化的,因此也没什么可解释的。
但是这种方式有个至命的缺点——不是所有的C# COM对象都能用这种方式导出。正如前面所说的,只有实现了IDispatch,Dual类型的接口才支持被导出,而且面对不同版本的COM或许会生成不一样的导出DLL,比如说A机器上写代码时导入了一个Jet2.6版本的包装DLL,代码编译了拿到B机器上去运行,但是B机器上的Jet版本是2.8的,就可能会出现运行时错误。
2.用反射动态创建
包括使用Type.GetTypeFromCLSID和Type.GetFromProgID两种方法获取COM对象的Type再创建.这种方式也好理解,就是说使用这两个方法之前,必须得知道COM对象的GUID或ProgID,好在这也不是什么难事,一般我们要使一个COM对象,多多少少都了解一些这个COM对象的GUID或ProgID信息.用这种方获取到了一个Type对象后,就可以用.NET里面通用的反射创建对象的方法来做了.
这里给出一个创建JetEngine 的COM对象的代码实例:
Guid g=new Guid("DE88C160-FF2C-11D1-BB6F-00C04FAE22DA"); Type Typet=Type.GetTypeFromCLSID(clsid); if(t==null){} else{ Activator.CreateInstance(t); }
是不是觉得最后调用GetActiveXObject(g)的地方和IE里面Javascript里面用new ActiveXOjbect创建COM对象的方法很相像?
3.声明CoCreateInstance外部函数,用这个函数去创建相应的COM实例
M$在2005里面包装的WebBrowser控件内部就是用这个函数去创建的, 使用这种方式创建COM,就跟在C++里面不什么两样了.有一点需要说明的是,一般我们在代码中引入外部方法的时候,方法的参数和返回值的类型不一定是唯一的一种,只要在逻辑上相互能转化,一般都可以使用.
[return:MarshalAs(UnmanagedType.Interface)] [DllImport("ole32.dll",ExactSpelling=true,PreserveSig=false)] public static extern object CreateObjectDelegate1([In]ref Guid clsid, [MarshalAs(UnmanagedType.Interface)]object punkOuter,intc ontext,[In]ref Guid iid); [DllImport("ole32.dll",ExactSpelling=true,PreserveSig=false)] public static extern IntPtr CreateObjectDelegate2([In]ref Guid clsid, IntPtr punkOuter,int context,[In]ref Guid iid); [DllImport("ole32.dll",ExactSpelling=true)] public static extern int CreateObjectDelegate3([In]ref Guid clsid, IntPtr punkOuter,int context,[In]ref Guid iid,[Out]out IntPtr pVoid); [DllImport("ole32.dll",ExactSpelling=true)] public static extern int CreateObjectDelegate3([In]ref Guid clsid, [MarshalAs(UnmanagedType.Interface)]object punkOuter,int context, [In]ref Guid iid,[MarshalAs(UnmanagedType.Interface),Out] out object pVoid);
那么问题来了,com到底与系统有没有关系呢,.net core是个跨平台的框架,能否使用com的库呢?答案是:不能,至少标准com是不行的,因为是基于win32的,需要实现固定的一套接口。非标准的com,是不完全支持的。对于上面1、2方法,都是基于标准com的,所以在linux下只能考虑方法3去创建com对象
分析下问题,对于非标准com,有点类似库接口的调用,从理论上来说应该是可行的。但c#还有个内存管理的问题,com对象是通过引用计数维护的,而c#是gc,这两者从理论上还是有点冲突。可以看到方法3中的out是object,但从c++头文件可以看到,这个地方实际的签名是void**,也就是.net还有一次内部转换,对c#来说就是IntPtr->object,对应Marshal.GetUniqueObjectForIUnknown(ptr)这个方法,但这个object对应的内存区域却是非托管内存,甚至不是在c#内部创建的,可见这个映射是多么的特殊。很遗憾的是,在linux下,.net core 3.0这个转换还不被支持,猜测可能形成了逻辑闭环。大家可以试试在.net 6以上这个问题是否解决,在.net core中使用这些需要充分考虑跨平台的问题,不然运行中会出异常