掌握了.NET平台下的互操作性技术可以帮助我们在**.NET中调用非托管的dll和COM组件**。.NET是建立在操作系统的之上的一个开发框架,其中.NET 类库中的类也是对Windows API的抽象封装,然而.NET类库不可能对所有Windows API进行封装,当.NET中没有实现某个功能的类,然而该功能在Windows API被实现了,此时我们完全没必要去自己在.NET中自定义个类,这时候就可以调用Windows API 中的函数来实现,此时就涉及到托管代码与非托管代码的交互,此时就需要使用到互操作性的技术来实现托管代码和非托管代码更好的交互。.NET 平台下提供了3种互操作性的技术:

1. Platform Invoke(P/Invoke),即平台调用,主要用于调用C库函数和Windows API
2. C++ Introp, 主要用于Managed C++(托管C++)中调用C++类库
3. COM Interop, 主要用于在.NET中调用COM组件和在COM中使用.NET程序集。




平台调用可以帮助我们实现在.NET平台下(也就是指用C#、VB.net语言写的应用程序下)可以调用非托管函数(指定的是C/C++语言写的函数)。这样如果我们在.NET平台下实现的功能有现有的C/C++ 函数实现了这样的功能,这时候我们完全没必要自己再用托管语言(如C#、vb.net)去实现一个这样的功能,这时候我们应该想到 “拿来主义”,直接使用平台调用技术调用C/C++ 实现的函数。然而在实际应用中,使用平台调用技术来调用Win32 API较为普遍,

上图中 我用红色标示出 MessageBox 有两个版本,而MessageBoxA 代表的就是ANSI版本,而MessageBoxW 代笔的就是Unicode版本,这也是上面所说的依据。下面就看看 MessageBox的C++声明的(更多的函数的定义大家可以从MSDN中找到



捕捉由托管定义导致的异常演示代码:
捕获由Win32函数本身返回异常:

捕获由Win32函数本身返回异常的演示代码如下:
using System;
using System.ComponentModel;
// 使用平台调用技术进行互操作性之前,首先需要添加这个命名空间
using System.Runtime.InteropServices;

namespace 处理Win32函数返回的错误
{
class Program
{
// Win32 API
// DWORD WINAPI GetFileAttributes(
// In LPCTSTR lpFileName
//);

    // 在托管代码中对非托管函数进行声明 SetLastError=true[DllImport("Kernel32.dll",SetLastError=true,CharSet=CharSet.Unicode)]public static extern uint GetFileAttributes(string filename);static void Main(string[] args){// 试图获得一个不存在文件的属性// 此时调用Win32函数会发生错误GetFileAttributes("FileNotexist.txt");// 在应用程序的Bin目录下存在一个test.txt文件,此时调用会成功//GetFileAttributes("test.txt");// 获得最后一次获得的错误int lastErrorCode = Marshal.GetLastWin32Error();// 将Win32的错误码转换为托管异常//Win32Exception win32exception = new Win32Exception();Win32Exception win32exception = new Win32Exception(lastErrorCode);if (lastErrorCode != 0){Console.WriteLine("调用Win32函数发生错误,错误信息为 : {0}", win32exception.Message);}else{Console.WriteLine("调用Win32函数成功,返回的信息为: {0}", win32exception.Message);}Console.Read();}
}

}


数据封送——在托管代码中对非托管函数进行互操作时,需要通过方法的参数和返回值在托管内存和非托管内存之间传递数据的过程,数据封送处理的过程是由CLR(公共语言运行时)的封送处理服务(即封送拆送器)完成的。

二、封送Win32数据类型

对非托管代码进行互操作时,一定会有数据的封送处理。然而封送时需要处理的数据类型分为两种——可直接复制到本机结构中的类型(blittable)和非直接复制到本机结构中的类型(non-bittable)。下面就这两种数据类型分别做一个介绍。
2.1 可直接复制到本机结构中的类型

由于在托管代码和非托管代码中,数据类型在托管内存和非托管内存的表示形式不一样,因为这样的原因,所以我们需要对数据进行封送处理,以至于在托管代码中调用非托管函数时,把正确的传入参数传递给非托管函数和把正确的返回值返回给托管代码中。然而,并不是所有数据类型在两者内存的表现形式不一样的,这时候我们把在托管内存和非托管内存中有相同表现形式的数据类型称为——可直接复制到本机结构中的类型,这些数据类型不需要封送拆送器进行任何特殊的处理就可以在托管和非托管代码之间传递, 下面列出一些课直接复制到本机结构中的简单数据类型:

除了上表列出来的简单类型之外,还有一些复制类型也属于可直接复制到本机结构中的数据类型:

(1) 数据元素都是可直接复制到本机结构中的一元数组,如整数数组,浮点数组等

(2)只包含可直接复制到本机结构中的格式化值类型

(3)成员变量全部都是可复制到本机结构中的类型且作为格式化类型封送的类



四、封送结构体的处理

在我们实际调用Win32 API函数时,经常需要封送结构体和类等复制类型,下面就以Win32 函数GetVersionEx为例子来演示如何对作为参数的结构体进行封送处理。为了在托管代码中调用非托管代码,首先我们就要知道非托管函数的定义,下面是GetVersionEx非托管定义


using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace 封送结构体的处理
{
class Program
{
// 对GetVersionEx进行托管定义
// 为了传递指向结构体的指针并将初始化的信息传递给非托管代码,需要用ref关键字修饰参数 // 这里不能使用out关键字,如果使用了out关键字,CLR就不会对参数进行初始化操作,这样就会导致调用失败 [DllImport(“Kernel32”,CharSet=CharSet.Unicode,EntryPoint=“GetVersionEx”)]
private static extern Boolean GetVersionEx_Struct(ref OSVersionInfo osVersionInfo);

    // 因为Win32 GetVersionEx函数参数lpVersionInformation是一个指向 OSVERSIONINFO的数据结构// 所以托管代码中定义个结构体,把结构体对象作为非托管函数参数[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]public struct OSVersionInfo{public UInt32 OSVersionInfoSize; // 结构的大小,在调用方法前要初始化该字段public UInt32 MajorVersion; // 系统主版本号public UInt32 MinorVersion; // 系统此版本号public UInt32 BuildNumber;  // 系统构建号public UInt32 PlatformId;  // 系统支持的平台// 此属性用于表示将其封送成内联数组[MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)]public string CSDVersion; // 系统补丁包的名称public UInt16 ServicePackMajor; // 系统补丁包的主版本public UInt16 ServicePackMinor;  // 系统补丁包的次版本public UInt16 SuiteMask;   //标识系统上的程序组public Byte ProductType;    //标识系统类型public Byte Reserved;  //保留,未使用}// 获得操作系统信息private static string GetOSVersion(){// 定义一个字符串存储版本信息string versionName = string.Empty;// 初始化一个结构体对象OSVersionInfo osVersionInformation = new OSVersionInfo();// 调用GetVersionEx 方法前,必须用SizeOf方法设置结构体中OSVersionInfoSize 成员osVersionInformation.OSVersionInfoSize = (UInt32)Marshal.SizeOf(typeof(OSVersionInfo));// 调用Win32函数Boolean result = GetVersionEx_Struct(ref osVersionInformation);if (!result){// 如果调用失败,获得最后的错误码int errorcode = Marshal.GetLastWin32Error();Win32Exception win32Exc = new Win32Exception(errorcode);Console.WriteLine("调用失败的错误信息为: " + win32Exc.Message);// 调用失败时返回为空字符串return string.Empty;}else{Console.WriteLine("调用成功");switch (osVersionInformation.MajorVersion){// 这里仅仅讨论 主版本号为6的情况,其他情况是一样讨论的case 6:switch (osVersionInformation.MinorVersion){case 0:if (osVersionInformation.ProductType == (Byte)0){versionName = " Microsoft Windows Vista";}else{versionName = "Microsoft Windows Server 2008"; // 服务器版本}break;case 1:if (osVersionInformation.ProductType == (Byte)0){versionName = " Microsoft Windows 7";}else{versionName = "Microsoft Windows Server 2008 R2";}break;case 2:versionName = "Microsoft Windows 8";break;}break;default:versionName = "未知的操作系统";break;}return versionName;}}static void Main(string[] args){string OS=GetOSVersion();Console.WriteLine("当前电脑安装的操作系统为:{0}", OS);Console.Read();}
}

}


五、封送类的处理

下面直接通过GetVersionEx函数进行封送类的处理的例子,具体代码如下:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace 封送类的处理
{
class Program
{
// 对GetVersionEx进行托管定义
// 由于类的定义中CSDVersion为String类型,String是非直接复制到本机结构类型,
// 所以封送拆送器需要进行复制操作。
// 为了是非托管代码能够获得在托管代码中对象设置的初始值(指的是OSVersionInfoSize字段,调用函数前首先初始化该值),
// 所以必须加上[In]属性;函数返回时,为了将结果复制到托管对象中,必须同时加上 [Out]属性
// 这里不能是用ref关键字,因为 OsVersionInfo是类类型,本来就是引用类型,如果加ref 关键字就是传入的为指针的指针了,这样就会导致调用失败 [DllImport(“Kernel32”, CharSet = CharSet.Unicode, EntryPoint = “GetVersionEx”)]
private static extern Boolean GetVersionEx_Struct([In, Out] OSVersionInfo osVersionInfo);

    // 获得操作系统信息private static string GetOSVersion(){// 定义一个字符串存储操作系统信息string versionName = string.Empty;// 初始化一个类对象OSVersionInfo osVersionInformation = new OSVersionInfo();// 调用Win32函数Boolean result = GetVersionEx_Struct(osVersionInformation);if (!result){// 如果调用失败,获得最后的错误码int errorcode = Marshal.GetLastWin32Error();Win32Exception win32Exc = new Win32Exception(errorcode);Console.WriteLine("调用失败的错误信息为: " + win32Exc.Message);// 调用失败时返回为空字符串return string.Empty;}else{Console.WriteLine("调用成功");switch (osVersionInformation.MajorVersion){// 这里仅仅讨论 主版本号为6的情况,其他情况是一样讨论的case 6:switch (osVersionInformation.MinorVersion){case 0:if (osVersionInformation.ProductType == (Byte)0){versionName = " Microsoft Windows Vista";}else{versionName = "Microsoft Windows Server 2008"; // 服务器版本}break;case 1:if (osVersionInformation.ProductType == (Byte)0){versionName = " Microsoft Windows 7";}else{versionName = "Microsoft Windows Server 2008 R2";}break;case 2:versionName = "Microsoft Windows 8";break;}break;default:versionName = "未知的操作系统";break;}return versionName;}}static void Main(string[] args){string OS = GetOSVersion();Console.WriteLine("当前电脑安装的操作系统为:{0}", OS);Console.Read();}
}[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class OSVersionInfo
{public UInt32 OSVersionInfoSize = (UInt32)Marshal.SizeOf(typeof(OSVersionInfo));public UInt32 MajorVersion = 0;public UInt32 MinorVersion = 0;public UInt32 BuildNumber = 0;public UInt32 PlatformId = 0;// 此属性用于表示将其封送成内联数组[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]public string CSDVersion = null;public UInt16 ServicePackMajor = 0;public UInt16 ServicePackMinor = 0;public UInt16 SuiteMask = 0;public Byte ProductType = 0;public Byte Reserved;
}

}


**

在C# 中调用COM组件

**

using System;
// 添加额外的命名空间
using Microsoft.Office.Interop.Word;

namespace COM互操作性
{
class Program
{
static void Main(string[] args)
{
// 调用COM对象来创建Word文档
CreateWordDocument();
}

    private static void CreateWordDocument(){// 启动Word并使Word可见Application wordApp = new Application() { Visible = true };// 新建Word文档wordApp.Documents.Add();Document wordDoc = wordApp.ActiveDocument;Paragraph para = wordDoc.Paragraphs.Add();para.Range.Text = "欢迎你;// 保存文档object filename = @"D:\learninghard.doc";wordDoc.SaveAs2(filename);// 关闭WordwordDoc.Close();wordApp.Application.Quit();}
}

}
此时在所指定的文件目录中就可以看到你刚才创建的Word文档了。通过COM互操作的技术我们可以Office的自动化操作。

然而我们也可以使用Visual Studio中内置的支持来完成为COM类型库创建互操作程序集的工作,我们只需要在VS中为.NET 项目添加对应的COM组件的引用,此时VS就会自动将COM类型库中的COM类型库转化为程序集中的元数据,并在项目的Bin目录下生成对于的互操作程序集,所以在VS中添加COM引用,其实最后程序中引用的是互操作程序集,然后通过RCW来对COM组件进行调用。 然而对于Office中的Microsoft.Office.Interop.Wordd.dll,这个程序集也是互操作程序集,但是它又是主互操作程序集,即PIA(Primary Interop Assemblies)。主互操作程序集是一个由供应商提供的唯一的程序集,为了生成主互操作程序集,可以在使用TlbImp命令是打开 /primary 选项。看到这里,朋友们肯定有这样的疑问:PIA与普通程序集到底有什么区别呢?——区别就是PIA除了包含了COM组件定义的数据类型外,还包含了一些特殊的信息,如公钥,COM类型库的提供者等信息。然而 为什么需要主互操作程序集的呢 ? 对于这个问题的答案就是——主互操作程序集可以帮助我们解决部署程序时,引用互操作程序集版本不一致的问题。(如果开发人员会为一个COM组件类型库生成多个互操作程序集,项目中引用的互操作程序集版本与部署时的互操作程序集版本不一致的问题,有了互操作程序集时,我们可以直接引用官方提供主互操作程序集。)

四、错误处理

知道了如何调用COM组件之后,大家或许会问:如果调用COM对象的方法失败时怎么去获取失败的信息呢?对于这个疑问,错误的处理的方法和我们平常托管代码中的处理方式是一样的,下面就具体看看是如何获取错误信息的,下面这段代码的功能是——打开一个现有的Word文档并插入相应的文本,当指定的Word文档不存在时,此时就会出现调用COM对象的Open方法失败的情况,具体代码如下:
using System;
using Microsoft.Office.Interop.Word;
using System.IO;
using System.Runtime.InteropServices;

namespace COM互操作中的错误处理
{
class Program
{
static void Main(string[] args)
{
// 打开存在的文档插入文本
string wordPath = @“D:\test.docx”;
OpenWordDocument(wordPath);
Console.Read();
}

    // 向现有文档插入文本private static void OpenWordDocument(string wordPath){// 启动Word 应用程序Application wordApp = new Application() { Visible = true };Document wordDoc=null;try{// 如果文档不存在时,就会出现调用COM对象失败的情况// 打开Word文档wordDoc = wordApp.Documents.Open(wordPath);// 向Word中插入文本Range wordRange = wordDoc.Range(0, 0);wordRange.Text = "这是插入的文本";// 保存文档wordDoc.Save();}catch(Exception ex){          // 获得异常相对应的HRESULT值// 因为COM中根据方法返回的HRESULT来判断调用是否成功的int HResult = Marshal.GetHRForException(ex);// 设置控制台的前景色,即输出文本的颜色Console.ForegroundColor = ConsoleColor.Red;// 下面把HRESULT值以16进制输出Console.WriteLine("调用抛出异常,异常类型为:{0}, HRESULT= 0x{1:x}", ex.GetType().Name, HResult);Console.WriteLine("异常信息为:" + ex.Message.Replace('\r', ' '));}finally{// 关闭文档并if (wordDoc != null){wordDoc.Close();}// 退出Word程序wordApp.Quit();}}
}

}

.NET Interop 互操作 COM+相关推荐

  1. 【推荐】《精通.NET互操作:P/Invoke,C++ Interop和COM Interop》

    ---如果你想知道Windows平台上的托管代码与非托管代码之间如何互操作 ---如果你想知道.NET平台提供的各种互操作方法 ---如果你想知道C++ Interop.COM Interop --- ...

  2. 无法嵌入互操作类型NationalInstruments.TestStand.Interop.UI.ExecutionViewOptions。请改用适用的接口...

    参考一下文章说明, 修改Interop.UI动态库的引入属性为 False,不再报错: VS2010,VS2012,VS2013中,无法嵌入互操作类型"--",请改用适用的接口的解 ...

  3. 《精通.NET互操作:P/Invoke、C++ Interop和COM Interop》

    <精通.NET互操作:P/Invoke.C++ Interop和COM Interop>官方博客 一篇用C++/CLI讲述在托管委托(delegate)和非托管函数指针之间相互转化的文章 ...

  4. Microsoft.Office.Interop.Word引用- 无法嵌入互操作类型

    添加引用->COM->Microsoft Word 14.0 Object Library 程序调试的时候报错: 错误 4317 无法嵌入互操作类型"Microsoft.Offi ...

  5. 报错:无法嵌入来自程序集“e:\Microsoft.Office.Interop.Word.dll”的互操作类型,因为它缺少“ImportedFromTypeLibAttribute”特性或“Prim

    解决方法: 选中引用的Dll:Microsoft.Office.Interop.xxx:右键属性,找到"嵌入互操作类型",将true改为false, 就可以了.

  6. .net和java互操作

    .net网站theserverside.com上,有一篇讲.net和java互操作的文章,收集了net和java互操作性的文章精选 http://www.theserverside.net/tt/ar ...

  7. 如何正确清理Excel互操作对象?

    我在C#( ApplicationClass )中使用Excel互操作,并将以下代码放在我的finally子句中: while (System.Runtime.InteropServices.Mars ...

  8. 利用 C++ Interop 封装 ISO C++ 对象, 供其他 .Net 语言使用

    2019独角兽企业重金招聘Python工程师标准>>> .Net BCL 支持两种互操作技术,模块 级重用 P/Invoke 和组件级重用COM 互操作, C++/CLI 除了支持以 ...

  9. 【预告】1月6日下午14:30 CLR开发系列课程(3):COM Interop基础 (Level 300)

    1月6日下午14:30我将在MSDN中文网络广播中主讲.NET中COM和COM Interop的相关基础知识.有兴趣的朋友可以通过下面的链接登记并收听此次网络广播:  公共语言运行库(CLR)开发系列 ...

最新文章

  1. 【SSM框架系列】Mybatis基本介绍
  2. ICML 2020 | 基于类别描述的文本分类模型
  3. express中get和post的区别
  4. apr java_基于 APR 的原生库
  5. P4296-[AHOI2007]密码箱【数论】
  6. java web应用程序_说说Java Web中的Web应用程序|乐字节
  7. 网易BUFF产品体验报告
  8. 第八章:贪心+二分 题目::Aggressive cows
  9. 裸辞后,随便找份工作干着还是等找到满意的为止?
  10. 项目Beta冲刺(2/7)(追光的人)(2019.5.24)
  11. mysql:Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (111)解决方法
  12. Android_Provision
  13. xyplorer的完美设置
  14. Chrome插件安装的3种方法,解决拖放不能安装的情况,并提供插件下载
  15. 关于Vue中keep-alive的作用是什么?怎么使用?
  16. Qt中Qlabel 图片拖放显示
  17. JAVA简单的银行管理系统
  18. CAN总线整车电子电气架构设计培训
  19. 企业如何搭建属于自己的协同办公管理系统?
  20. 在caffe 中添加Crowd counting 数据层

热门文章

  1. MacBook Pro换固态硬盘出现的一个稀缺问题
  2. 电线线缆铜芯和铝芯有什么区别?哪个更好呢?
  3. 怎么求最大公因数和最小公倍数
  4. 求最大公约数和最小公倍数的多种方法
  5. 阿里云自助建站+模板建站+功能定制建站如何选择,详细教程
  6. 计组头哥实验 第2关 原码一位乘法器设计
  7. Android使用Opengl录像时添加(动态)水印
  8. prometheus监控常用告警规则
  9. PointCloud的修修补补
  10. FB_LLC 死区时间计算(保证ZVS)