C#笔记26: 与非托管代码交互操作

来源:互联网 发布:centos ed2k下载工具 编辑:程序博客网 时间:2024/04/27 19:51

C#笔记26: 与非托管代码交互操作

本文摘要:

1:非托管代码交互操作的概念

2:类型库

3:激活 COM 对象

4:向 COM 公开 .NET Framework 组件

   4.1:可参考内容

   4.2:什么样的 .NET 类型才能向COM公开

5:使用非托管DLL

   5.1:标识 DLL 中的函数

   5.2:在托管代码中创建原型

   5.3:调整定义DllImportAttribute

6:映射 HRESULT 和异常

 

1:非托管代码交互操作的概念

    在运行时控制下执行的代码称作托管代码。相反,在运行时之外运行的代码称作非托管代码。COM 组件、ActiveX 接口和 Win32 API 函数都是非托管代码的示例。在托管和非托管对象模型之间,数据类型、方法签名和错误处理机制都存在差异。为了简化 .NET Framework 组件和非托管代码之间的互用并便于进行迁移,公共语言运行时将从客户端和服务器中隐藏这两种对象模型之间的差异。

 

2:类型库

    类型库包含对象接口的元信息(Meta)。通过类型库(TypeLib),你可以很轻松获得类(或接口)的种种信息,如: 
◦类名  
◦基类 
◦方法列表(包括方法的原型,方法的名称) 
◦属性列表(包括属性的类型,属性的名称) 
◦等等 
    COM 类型库可以是扩展名为 .tlb 的独立文件,如 Loanlib.tlb。某些类型库嵌入在 .dll 或 .exe 文件的资源部分中。类型库信息的其他来源包括 .olb 和 .ocx 文件。

    在您找到包含目标 COM 类型的实现的类型库后,可以通过下列选项来生成包含类型元数据的互操作程序集:

  • Visual Studio

    Visual Studio 将类型库中的 COM 类型自动转换为程序集中的元数据。有关说明,请参见如何:添加对类型库的引用和演练:嵌入 Microsoft Office 程序集中的类型信息(C# 和 Visual Basic)。

  • 类型库导入程序 (Tlbimp.exe)

    类型库导入程序提供命令行选项,用以调整结果 Interop 文件中的元数据、从现有类型库中导入类型以及生成互操作程序集和命名空间。有关说明,请参见如何:从类型库生成互操作程序集。

  • System.Runtime.InteropServices..::.TypeLibConverter 类

    此类提供可将类型库中的 coclass 和接口转换为程序集中的元数据的方法。此类生成与 Tlbimp.exe 相同的元数据输出。但与 Tlbimp.exe 不同的是,TypeLibConverter 类可以将内存中类型库转换为元数据。

  • 自定义包装

    当类型库不可用或不正确时,一种可选的做法是在托管源代码中创建类或接口的重复定义。然后,用面向运行时的编译器来编译源代码以生成程序集中的元数据。

    要手动定义 COM 类型,必须具备下列各项:

    • 所定义的 coclass 和接口的精确描述。

    • 可生成正确 .NET Framework 类定义的编译器,如 C# 编译器。

    • 有关类型库到程序集转换规则的知识。

    编写自定义包装是一项高级技术。有关如何生成自定义包装的其他信息,请参见自定义标准包装。

     有关 COM 互操作导入过程的更多信息,请参见有关从类型库转换到程序集的摘要。

 

3:激活 COM 对象

    假设您有一个包含 Loan 类及其成员的程序集,则可以很容易地执行早期绑定激活。 下面的代码示例将从托管代码中激活LOANLib.Loan coclass 的一个实例:

using System;using LoanLib;public class LoanApp {    public static void Main(String[] Args) {        Loan ln = new Loan();        …    }}

 

4:向 COM 公开 .NET Framework 组件

    上文讲到的是.NET Framework调用COM,反之,.NET Framework也可以向COM公开自己的组件。

4.1:可参考内容

  • 为互用性限定 .NET 类型。

    要向 COM 公开的所有托管类型、方法、属性、字段和事件都必须是公共的。 类型必须具有公共的默认构造函数,该构造函数是唯一可以通过 COM 调用的构造函数。

  • 应用 Interop 特性。

    托管代码中的自定义特性可以增强组件的互用性。

  • 将 COM 的程序集打包。

    COM 开发人员可能会要求您总结引用和部署程序集所涉及的步骤。

    从 COM 中使用托管类型

  1. 向 COM 注册程序集。

    程序集(和类型库)中的类型必须在设计时注册。 如果安装程序未注册程序集,应指示 COM 开发人员使用 Regasm.exe。

  2. 从 COM 中引用 .NET 类型。

    COM 开发人员可以通过他们当前使用的相同工具和技术来引用程序集中的类型。

  3. 调用 .NET 对象。

    COM 开发人员可以按照对任何非托管类型调用方法的相同方式来对 .NET 对象调用方法。 例如,COMCoCreateInstance API 将激活 .NET 对象。

  4. 为 COM 访问部署应用程序。

    具有强名称的程序集可以安装在全局程序集缓存中并需要其发行者的签名。 不具有强名称的程序集必须安装在客户端的应用程序目录中。

4.2:什么样的 .NET 类型才能向COM公开

  • 类应显式实现接口。

  • 托管类型必须是公共的。

  • 方法、属性、字段和事件必须是公共的。

  • 类型必须有一个公共默认构造函数才能从 COM 中激活。

  • 类型不能是抽象的。

     

5:使用非托管DLL

     管代码可以调用在动态链接库 (DLL)(如 Win32 API 中的 DLL)中实现的非托管函数。此服务将查找并调用导出的函数,然后根据需要跨越互用边界封送其参数(整数、字符串、数组、结构等)。

5.1:标识 DLL 中的函数

     DLL 函数的标识包括以下元素:

  • 函数的名称或序号

  • 实现所在的 DLL 文件的名称

     例如,如果指定 User32.dll 中的 MessageBox 函数,需要标识该函数 (MessageBox) 及其位置(User32.dll、User32 或 user32)。 Microsoft Windows 应用程序编程接口 (Win32 API) 可以包含每个字符和字符串处理函数的两个版本:单字节字符 ANSI 版本和双字节字符 Unicode 版本。 如果不进行指定,CharSet 字段所表示的字符集将默认为 ANSI。 某些函数可以有两个以上的版本。

     下面是 Win32 API 中几个常用的 DLL:

GDI32.dll,用于设备输出的图形设备接口 (GDI) 函数,例如用于绘图和字体管理的函数。

Kernel32.dll,用于内存管理和资源处理的低级别操作系统函数。

User32.dll,用于消息处理、计时器、菜单和通信的 Windows 管理

5.2:在托管代码中创建原型

     托管代码中访问非托管 DLL 函数之前,首先需要知道该函数的名称以及将其导出的 DLL 的名称。 获取以上信息后,就可以开始为在 DLL 中实现的非托管函数编写托管定义。 此外,您还可以调整平台调用创建函数以及向/从函数封送数据的方式。

     从下面的示例中可以发现,非托管函数的托管定义与语言相关。

using System.Runtime.InteropServices;[DllImport("user32.dll")]    public static extern IntPtr MessageBox(int hWnd, String text, String caption, uint type);

 

5.3:调整定义DllImportAttribute

     可使用 DllImportAttribute 来设置值。

     下表列出了所有与平台调用相关的特性字段。 对于每个字段,下表都将包含其默认值,并且会提供一个链接,用于获取有关如何使用这些字段定义非托管 DLL 函数的信息。

BestFitMapping,启用或禁用最佳匹配映射。

CallingConvention,指定用于传递方法参数的调用约定。 默认值为 WinAPI,该值对应于基于 32 位 Intel 的平台的 __stdcall。

CharSet,控制名称重整以及将字符串参数封送到函数中的方式。 默认值为 CharSet.Ansi。

EntryPoint,指定要调用的 DLL 入口点。

ExactSpelling,控制是否应修改入口点以对应于字符集。 对于不同的编程语言,默认值将有所不同。

PreserveSig,控制托管方法签名是否应转换成返回 HRESULT 并且返回值有一个附加的 [out, retval] 参数的非托管签名。

默认值为 true(不应转换签名)。

SetLastError,允许调用方使用 Marshal.GetLastWin32Error API 函数来确定执行该方法时是否发生了错误。 在 Visual Basic 中,默认值为 true;在 C# 和 C++ 中,默认值为 false。

ThrowOnUnmappableChar,控制对转换为 ANSI“?”字符的不可映射的 Unicode 字符引发异常。

  

6:映射 HRESULT 和异常

     COM 方法通过返回 HRESULT 来报告错误;.NET 方法则通过引发异常来报告错误。 运行时将处理这两者之间的转换。 .NET Framework 中的每个异常类都会映射到一个 HRESULT。

     相关知识查看:http://msdn.microsoft.com/zh-cn/library/9ztbc5s1.aspx

  

练习:

1.You write a class named Employee that includes the following code segment.   
public class Employee {string employeeId, employeeName, jobTitleName;        
public string GetName() { return employeeName;   }       
public string GetTitle() { return jobTitleName;   }   
You need to expose this class to COM in a type library. The COM interface must also facilitate 
forward-compatibility across new versions of the Employee class. You need to choose a method for generating the
COM interface.   What should you do? 
A. Add the following attribute to the class definition. 
[ClassInterface(ClassInterfaceType.None)]public class Employee {}  
B.  Add the following attribute to the class definition.  
[ClassInterface(ClassInterfaceType.AutoDual)]public class Employee {}  
C.  Add the following attribute to the class definition.  
[ComVisible(true)]public class Employee {} 
D. Define an interface for the class and add the following attribute to the class definition. 
[ClassInterface(ClassInterfaceType.None)]public class Employee : IEmployee {} 
Answer: D

  

2.You need to call an unmanaged function from your managed code by using platform invoke services. What    
should you do? 
A. Create a class to hold DLL functions and then create prototype methods by using managed code. 
B. Register your assembly by using COM and then reference your managed code from COM. 
C. Export a type library for your managed code. 
D. Import a type library as an assembly  and then create instances of COM object. 
Answer: A

 

3.You write the following code to call a function from the Win32 Application Programming Interface (API) by 
using platform invoke.      

Int rc=MessageBox(hWnd,text,caption,type)
You need to define a method prototype. Which code segment should you use?    
A. [DllImport("user32")]

public static extern int MessageBox(int hWnd, String text, String caption, uint type);  
B. [DllImport("user32")] 
public static extern int MessageBoxA(int hWnd,String text, String caption, uint type);    
C. [DllImport("user32")] 
public static extern int Win32API_User32_MessageBox(int hWnd, String text, String caption, uint type); 
D. [DllImport(@"C. \WINDOWS\system32\user32.dll")] 
public static extern int MessageBox(int hWnd, String text, String caption, uint type);    
Answer: A

 

4.You are developing a method to call a COM component. You need to use declarative security to explicitly  
request the runtime to perform a full stack walk. You must     ensure that all callers have the required level of trust  
for COM interop before the callers execute your method. Which attribute should you place on the method?    
A. [SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)] 
B. [SecurityPermission(SecurityAction.LinkDemand,   
   Flags=SecurityPermissionFlag.UnmanagedCode)] 
C. [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)]  
D. [SecurityPermission(SecurityAction.Deny, Flags = SecurityPermissionFlag.UnmanagedCode)]  
Answer: A

 

5.You need to create a class definition that is interoperable along with COM. You need to ensure that COM  
applications can create instances of the class and can call the GetAddress method. Which code segment should    
you use?  
A. public class Customer { string addressString;        
public Customer(string address) { addressString = address; }       
public string GetAddress() { return addressString; }}  
B. public class Customer {static string addressString;         
public Customer() { }       
public static string GetAddress() { return addressString; }}  
C. public class Customer {string addressString;        
public Customer() { }       
public string GetAddress() { return addressString; }}  
D. public class Customer {string addressString;        
public Customer() { }       
internal string GetAddress() { return addressString; }} 
Answer: C

 

6.You write the following code segment to call a function from the Win32 Application Programming Interface (API) by using platform invoke.    
 string personName="N?el";

 string msg = "welcom" + personName + " to clus";

 bool rc=User32API.MessageBox(0, msg, personName, 0);
You need to define a method prototype that can best marshal the string data. Which code segment should you use?  
A. [DllImport("user32", CharSet = CharSet.Ansi)] 
public static extern bool MessageBox(int hWnd, String text, String caption, uint type);} 
B. [DllImport("user32", EntryPoint = "MessageBoxA", CharSet = CharSet.Ansi)] 
public static extern bool MessageBox(int hWnd, [MarshalAs(UnmanagedType.LPWStr)]String text, 
[MarshalAs(UnmanagedType.LPWStr)]String caption, uint type);} 
C. [DllImport("user32", CharSet = CharSet.Unicode)] 
public static extern bool MessageBox(int hWnd, String text, String caption, uint type);} 
D. [DllImport("user32", EntryPoint = "MessageBoxA", CharSet = CharSet.Unicode)]

public static extern bool MessageBox(int hWnd, [MarshalAs(UnmanagedType.LPWStr)]String  text,    
[MarshalAs(UnmanagedType.LPWStr)]String caption, uint type);} 
Answer: C

原创粉丝点击