C#笔记26: 与非托管代码交互操作
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)。
-
类型库导入程序提供命令行选项,用以调整结果 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:可参考内容
-
要向 COM 公开的所有托管类型、方法、属性、字段和事件都必须是公共的。 类型必须具有公共的默认构造函数,该构造函数是唯一可以通过 COM 调用的构造函数。
-
托管代码中的自定义特性可以增强组件的互用性。
-
COM 开发人员可能会要求您总结引用和部署程序集所涉及的步骤。
从 COM 中使用托管类型
-
程序集(和类型库)中的类型必须在设计时注册。 如果安装程序未注册程序集,应指示 COM 开发人员使用 Regasm.exe。
-
COM 开发人员可以通过他们当前使用的相同工具和技术来引用程序集中的类型。
-
COM 开发人员可以按照对任何非托管类型调用方法的相同方式来对 .NET 对象调用方法。 例如,COM CoCreateInstance API 将激活 .NET 对象。
-
具有强名称的程序集可以安装在全局程序集缓存中并需要其发行者的签名。 不具有强名称的程序集必须安装在客户端的应用程序目录中。
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
NET C# 入门级 | .NET C# 专业级 | .NET 架构级 | BS系统专业级 | BS系统安全 |
1.开篇及C#程序、解决方案的结构 2.源码管理之TFS入门 3.打老鼠初级 …… 21.CMS之主要功能实现 22.进程和线程基础 23.类型转换 24.算法基础 25.初级课程之剩余知识点 |
1.消灭打老鼠游戏中的自定义委托 2.垃圾回收 3.Dispose模式 …… 16.异常使用指导 17.最常用的重构指导 18.Debug和IDE的进阶 19.Resharper的使用 20.ILSPY的使用 |
1.Socket入门 2.打造打老鼠游戏网络版 3.WCF入门 …… 10.依赖注入 11.万物兼可测试 12.软件指标之覆盖率计算 13.软件指标之代码行 14.软件指标之圈复杂度、嵌套深度 |
1.HTML 2.WebForm原理 3.CSS必知必会 …… 19.让浏览器缓存Shop 20.Asp.net的生命周期 21.Asp.net网站的发布以及调试晋级 22.BS程序的本质 23.压力测试我们的Shop |
1.Fiddler必知必会 2.IE开发者工具必知必会 3.跨站脚本防范 4.权限欺骗防范 5.参数越界防范 6.会话劫持防范 7.CSRF防范 8.盗链防范 9.静态文件的保护 |