当前位置: 首页 > news >正文

托管与非托管

interop(inter operability,交互操作,就是托管与非托管代码进行交互操作)是Visual Studio .NET 通过引入面向CLR(Common Language Runtime 通用语言运行时)的托管代码概念。

Visual Studio .NET 通过引入面向CLR(Common Language Runtime 通用语言运行时)的托管代码概念,使开发人员在创建和运行应用程序的方式上有了重大改变。托管代码提供了包括自动内存管理、基于属性的编程和公共类型系统等在内的许多优点。不幸的是,正是这些功能强大、独树一帜的特性也使得它与以往基于 Windows API 和 COM 对象的编程有了本质区别。虽然 Visual Studio .NET 也在尽力使得使用和创建非托管代码容易一些,但有些情况下使用非托管代码是很困难的。 让受管代码对象非受管对象协同工作的过程称为互用性(interoperability),通常简称为 interop。

1      DLLImport的使用

using System;
using System.Runtime.InteropServices; //命名空间
class Example
{
    //用DllImport 导入Win32的MessageBox函数
    [DllImport("user32.dll", CharSet =CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, String text, Stringcaption, uint type);

    //方法被声明为 static。这是 P/Invoke 方法所要求的,因为在该 Windows API 中没有
    //一致的实例概念。接下来,还要注意该方法被标记为 extern。这是提示编译器该方法是通
    //过一个从 DLL 导出的函数实现的,因此不需要提供方法体。
    static void Main()
    {
        // Call the MessageBox functionusing platform invoke.
        MessageBox(new IntPtr(0),"Hello World!", "Hello Dialog", 0);
    }
}

使用非托管DLL函数并不困难,下面我们可以详细的了解上面的代码的含义。首先介绍什么是托管代码,什么是非托管代码。然后再详细介绍DLLImport的使用方法和各字段的意义。

2      托管代码 (managed code)

.NET Framework的核心是其运行库的执行环境,称为公共语言运行库(CLR)或.NET运行库。通常将在CLR的控制下运行的代码称为托管代码(managed code)

托管代码是运行库环境(而不是直接由操作系统)执行的代码。托管代码应用程序可以获得公共语言运行库服务,例如自动垃圾回收、运行库类型检查和安全支持等。这些服务帮助提供独立于平台和语言的、统一的托管代码应用程序行为。

托管代码是可以使用20多种支持Microsoft .NET Framework的高级语言编写的代码,它们包括:C#, J#, Microsoft Visual Basic .NET, Microsoft JScript .NET, 以及C++所有的语言共享统一的类库集合,并能被编码成为中间语言(IL)。运行库编译器(runtime-aware ompiler)在托管执行环境下编译中间语言(IL)使之成为本地可执行的代码,并使用数组边界和索引检查,异常处理,垃圾回收等手段确保类型的安全。

在托管执行环境中使用托管代码及其编译,可以避免许多典型的导致安全黑洞和不稳定程序的编程错误。同样,许多不可靠的设计也自动的被增强了安全性,例如类型安全检查,内存管理和释放无效对象。程序员可以花更多的精力关注程序的应用逻辑设计并可以减少代码的编写量。这就意味着更短的开发时间和更健壮的程序。

简单点说,托管代码是microsoft的中间语言,他主要的作用是在.NET FRAMEWORK的CLR执行代码前去编译源代码,也就是说托管代码充当着翻译的作用,源代码在运行时分为两个阶段:

1.源代码编译为托管代码;(所以源代码可以有很多种,如VB,C#,J#)

2.托管代码编译为microsoft系统的.net平台专用文件(如类库、可执行文件等)。

2.1     非托管代码 (unmanaged code)

在公共语言运行库环境的外部,由操作系统直接执行的代码。非托管代码必须提供自己的垃圾回收、类型检查、安全支持等服务;它与托管代码不同,后者从公共语言运行库中获得这些服务。

.net中托管代码的含义

2.2     什么是托管?托管是什么意思?

托管代码就是基于.net元数据格式的代码,运行于.net平台之上,所有的与操作系统的交换有.net来完成,就像是把这些功能委托给.net,所以称之为托管代码。非托管代码则反之。

举个例子

Vc.net还可以使用mfc,atl来编写程序,他们基于MFC或者ATL,而不是.NET,所以是非托管代码,如果基于.net比如C#,VB.net则是托管代码。

非托管代码是指.NET解释不了的

简单的说,托管代码的话,.net可以自动释放资源,非托管代码需要手动释放资源。

什么是托管C++

托管是.NET的一个专门概念,它倡导一种新的编程理念,因此我们完全可以把“托管”视为“.NET”。由托管概念所引发的C++应用程序包括托管代码、托管数据和托管类三个组成部分。

托管代码

.Net环境提供了许多核心的运行(RUNTIME)服务,比如异常处理和安全策略。为了能使用这些服务,必须要给运行环境提供一些信息代码(元数据),这种代码就是托管代码。所有的C#、VB.NET、JScript.NET默认时都是托管的,但Visual C++默认情况下是非托管的,必须在编译器中使用命令行选项(/CLR)才能产生托管代码。

托管数据

与托管代码密切相关的是托管数据。托管数据是由公共语言运行的垃圾回收器进行分配和释放的数据。默认情况下,C#、Visual Basic 和 JScript.NET 数据是托管数据。不过,通过使用特殊的关键字,C# 数据可以被标记为非托管数据。Visual C++数据在默认情况下是非托管数据,即使在使用 /CLR 开关时也不是托管的。

托管类

尽管Visual C++数据在默认情况下是非托管数据,但是在使用C++的托管扩展时,可以使用“__gc”关键字将类标记为托管类。就像该名称所显示的那样,它表示类实例的内存由垃圾回收器管理。另外,一个托管类也完全可以成为 .NET 框架的成员,由此可以带来的好处是,它可以与其他语言编写的类正确地进行相互操作,如托管的C++类可以从Visual Basic类继承等。但同时也有一些限制,如托管类只能从一个基类继承等。

2.3     托管代码如何调用非托管代码(c sharp如何调用c++代码)?

两种常用的做法:

下载:

http://download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d-5554a0608523/CLRInsideOut2007_01.exe

1. COM interop

具体操作:

a、用atl写com服务程序

b、使用Tlbimp将atl写的com程序转换成 COM DLL

   用如下命令:

   tlbimp 你写的com.dll

   tlbimp(type lib import)是 .NETFramework SDK中附带的类型库导入程序。用这个命令即生成一个非托管com dll的托管包装

c、托管客户端非常简单

   直接new一下,然后调用对应的方法即可。

2. P/Invoke(平台调用服务(Platform Invocation Services))

a. 在托管客户端增加一条 DllImport语句和一个方法的调用。

介绍一个P/Invoke网站,http://pinvoke.net/

这个网站主要是一个wiki,允许开发者发现,编辑,增加PInvoke的签名(通过签名才能找到方法的入口),用户自定义类型和从托管代码(指c#和VB.net开发语言)访问win32和其他非托管api的信息。

世界各地的.Net开发者可以很容易分享自己有价值的东西给社区

2.4     托管代码和非托管代码效率的对比

参加原文来自http://www.cnblogs.com/loverswordsman/articles/1367131.html

更详细的信息http://www.cnblogs.com/loverswordsman/articles/1367131.html

3     DllImportAttribute 的字段

在对托管代码进行 P/Invoke 调用时,DllImportAttribute 类型扮演着重要的角色。DllImportAttribute 的主要作用是给 CLR 指示哪个 DLL 导出您想要调用的函数。相关 DLL 的名称被作为一个构造函数参数传递给DllImportAttribute。

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

字段

说明

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

控件引发的异常,将无法映射的 Unicode 字符转换成一个 ANSI"?"字符。

除了指出宿主 DLL 外,DllImportAttribute 还包含了一些可选属性,其中四个特别有趣:EntryPoint、CharSet、SetLastError 和CallingConvention。http://www.360doc.com/content/11/0105/09 /3877783_84071078.shtml

3.1     entrypoint

入口点用于标识函数在 DLL 中的位置。在托管对象中,目标函数的原名或序号入口点将标识跨越交互操作边界的函数。此外,您可以将入口点映射到一个不同的名称,这实际上是将函数重命名。

以下列出了重命名 DLL 函数的可能原因:

避免使用区分大小写的 API 函数名

符合现行的命名标准

提供采用不同数据类型的函数(通过声明同一 DLL 函数的多个版本)

简化对包含 ANSI 和 Unicode 版本的 API 的使用

您可以使用 DllImportAttribute.EntryPoint 字段按名称或序号指定 DLL 函数。如果函数在方法定义中的名称与入口点在 DLL 的名称相同,则不必用 EntryPoint 字段来显式地标识函数。否则,使用以下属性形式之一来指示名称或序号:

[DllImport("dllname",EntryPoint="Functionname")]

[DllImport("dllname", EntryPoint="#123")]

指定入口点名称时,您可以提供一个字符串来指示包含入口点的 DLL 的名称,或者也可以按序号来标识入口点。序号以 # 符号为前缀,如 #1。如果省略此字段,则公共语言运行库将使用以DllImportAttribute 标记的 .NET 方法的名称。

下面的示例演示如何使用 EntryPoint 字段将代码中的 MessageBoxA 替换为 MsgBox

using System.Runtime.InteropServices;
public class Win32 {
    [DllImport("user32.dll", EntryPoint="MessageBoxA")]
    public static extern int MsgBox(int hWnd, String text,String caption, uint type);
}   

3.2     CharSet

以下来自http://msdn.microsoft.com/zh-cn/library/7b93s42f(v=VS.80).aspx

DllImportAttribute.CharSet 字段控制字符串封送处理并确定平台调用在 DLL 中查找函数名的方式。本主题将介绍这两种行为。

对于采用字符串参数的函数,有些 API 将导出它们的两个版本:窄版本 (ANSI) 和宽版本 (Unicode)。例如,Win32 API 包含 MessageBox 函数的以下入口点名称:

MessageBoxA

提供单字节字符 ANSI 格式,其特征是在入口点名称后附加一个“A”。对 MessageBoxA 的调用始终会以 ANSI 格式封送字符串,它常见于 Windows 95 和 Windows 98 平台。

MessageBoxW

提供双字节字符 Unicode 格式,其特征是在入口点名称后附加一个“W”。对 MessageBoxW 的调用始终会以 Unicode 格式封送字符串,它常见于 Windows NT、Windows 2000 和 Windows XP 平台。

CharSet 字段接受以下值:

CharSet.Ansi(默认值)

(1)字符串封送处理

平台调用将字符串从托管格式 (Unicode) 封送为 ANSI 格式。

(2)名称匹配

在 DllImportAttribute.ExactSpelling 字段为 true(它是 Visual Basic 2005 中的默认值)时,平台调用将只搜索您指定的名称。例如,如果指定MessageBox,则平台调用将搜索 MessageBox,如果它找不到完全相同的拼写则失败。

当 ExactSpelling 字段为 false(它是 C++ 和 C# 中的默认值)时,平台调用将首先搜索未处理的别名 (MessageBox),如果找不到未处理的别名,则将搜索已处理的名称 (MessageBoxA)。请注意,ANSI 名称匹配行为与 Unicode 名称匹配行为不同。

CharSet.Unicode

(1)字符串封送处理

平台调用会将字符串从托管格式 (Unicode) 复制为 Unicode 格式。

(2)名称匹配

当 ExactSpelling 字段为 true(它是 Visual Basic 2005 中的默认值)时,平台调用将只搜索您指定的名称。例如,如果指定MessageBox,则平台调用将搜索 MessageBox,如果它找不到完全相同的拼写则失败。

当 ExactSpelling 字段为 false(它是 C++ 和 C# 中的默认值)时,平台调用将首先搜索已处理的名称 (MessageBoxW),如果找不到已处理的名称,则将搜索未处理的别名 (MessageBox)。请注意,Unicode 名称匹配行为与 ANSI 名称匹配行为不同。

CharSet.Auto

平台调用在运行时根据目标平台在 ANSI 和 Unicode 格式之间进行选择。

下面的示例演示用于指定字符集的 MessageBox 函数的三个托管定义。在第一个定义中,通过省略,使 CharSet 字段默认为 ANSI 字符集。

[DllImport("user32.dll")]
public static extern int MessageBoxA(int hWnd, String text, Stringcaption, uint type);

[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern int MessageBoxW(int hWnd, String text, Stringcaption, uint type);

[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBox(int hWnd, String text, String caption,uint type);

CharSet.Ansi 和 CharSet.Unicode的名称匹配规则大不相同。对于 Ansi 来说,如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethod”。如果 DLL 中没有“MyMethod”,但存在“MyMethodA”,则返回“MyMethodA”。对于 Unicode 来说则正好相反。如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,则返回“MyMethod”。如果使用的是 Auto,则匹配规则与平台有关(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。如果 ExactSpelling 设置为 true,则只有当 DLL 中存在“MyMethod”时才返回“MyMethod”。

如果 DLL 函数不以任何方式处理文本,则可以忽略 DllImportAttribute 的 CharSet 属性。然而,当 Char 或 String 数据是等式的一部分时,应该将 CharSet 属性设置为 CharSet.Auto。这样可以使 CLR 根据宿主 OS 使用适当的字符集。如果没有显式地设置 CharSet 属性,则其默认值为 CharSet.Ansi。这个默认值是有缺点的,因为对于在 Windows 2000、Windows XP 和 Windows NT上进行的 interop 调用,它会消极地影响文本参数封送处理的性能。

应该显式地选择 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto 的唯一情况是:您显式地指定了一个导出函数,而该函数特定于这两种 Win32 OS 中的某一种。ReadDirectoryChangesW API 函数就是这样的一个例子,它只存在于基于 Windows NT 的操作系统中,并且只支持 Unicode;在这种情况下,您应该显式地使用 CharSet.Unicode。

有时,Windows API 是否有字符集关系并不明显。一种决不会有错的确认方法是在 Platform SDK 中检查该函数的 C 语言头文件。(如果您无法肯定要看哪个头文件,则可以查看 Platform SDK 文档中列出的每个 API 函数的头文件。)如果您发现该 API 函数确实定义为一个映射到以 A 或 W 结尾的函数名的宏,则字符集与您尝试调用的函数有关系。Windows API 函数的一个例子是在 WinUser.h 中声明的 GetMessage API,您也许会惊讶地发现它有 A 和 W 两种版本。

3.3    SetLastError

SetLastError 错误处理非常重要,但在编程时经常被遗忘。当您进行 P/Invoke 调用时,也会面临其他的挑战 — 处理托管代码中 Windows API 错误处理和异常之间的区别。我可以给您一点建议。

如果您正在使用 P/Invoke 调用 Windows API 函数,而对于该函数,您使用 GetLastError 来查找扩展的错误信息,则应该在外部方法的DllImportAttribute 中将 SetLastError 属性设置为 true。这适用于大多数外部方法。

这会导致 CLR 在每次调用外部方法之后缓存由 API 函数设置的错误。然后,在包装方法中,可以通过调用类库的System.Runtime.InteropServices.Marshal 类型中定义的 Marshal.GetLastWin32Error 方法来获取缓存的错误值。我的建议是检查这些期望来自 API 函数的错误值,并为这些值引发一个可感知的异常。对于其他所有失败情况(包括根本就没意料到的失败情况),则引发在 System.ComponentModel 命名空间中定义的Win32Exception,并将 Marshal.GetLastWin32Error 返回的值传递给它。

3.4    CallingConvention

CallingConvention.Cdecl : 调用方清理堆栈。它使您能够调用具有 varargs 的函数。

CallingConvention.StdCall : 被调用方清理堆栈。它是从托管代码调用非托管函数的默认约定。

CallingConvention 字段的默认值为 Winapi,而后者又默认为 StdCall 约定。

可能是最不重要的一个 DllImportAttribute 属性是 CallingConvention。通过此属性,可以给 CLR 指示应该将哪种函数调用约定用于堆栈中的参数。CallingConvention.Winapi 的默认值是最好的选择,它在大多数情况下都可行。然而,如果该调用不起作用,则可以检查 Platform SDK 中的声明头文件,看看您调用的 API 函数是否是一个不符合调用约定标准的异常 API。

通常,本机函数(例如 Windows API 函数或 C- 运行时 DLL 函数)的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。大多数 Windows API 函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。相反,许多 C-运行时 DLL 函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。

幸运的是,要让 P/Invoke 调用工作只需要让外围设备理解调用约定即可。通常,从默认值CallingConvention.Winapi 开始是最好的选择。然后,在 C 运行时 DLL 函数和少数函数中,可能需要将约定更改为 CallingConvention.Cdecl。

3.5    ExactSpelling

ExactSpelling 指示是否应修改非托管 DLL 中的入口点的名称,以与 CharSet 字段中指定的 CharSet 值相对应。如果为 true,则当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Ansi 值时,向方法名称中追加字母 A,当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Unicode 值时,向方法的名称中追加字母 W。此字段的默认值是 false。

3.6    PreserveSig

PreserveSig指示托管方法签名不应转换成返回 HRESULT、并且可能有一个对应于返回值的附加 [out, retval]参数的非托管签名。

4      参数类型

l  数值型直接用对应的就可。(DWORD ->int , WORD -> Int16)

l  API中字符串指针类型 -> .net中string

l  API中句柄 (dWord) ->.net中IntPtr

l  API中结构 -> .net中结构或者类。注意这种情况下,要先用StructLayout特性限定声明结构或类


5      Win32 API 中几个常用的 DLL

DLL

内容说明

GDI32.dll

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

Kernel32.dll

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

User32.dll

用于消息处理、计时器、菜单和通信的 Windows 管理函数。

6      wince 中的DLL

DLL

内容说明

coredll.dll

一般的一个可执行文件的内容都包含一个PE头,系统根据PE的信息找到入口函数,通过执行入口函数中的代码来执行可执行程序。托管程序的文件相对于非托管程序还包含了一个CLR表头文件以及其他CLR需要的信息

1.非托管程序的执行过程

在非托管程序中,可执行里面保存的是机器代码,CPU可以直接加载并执行,当系统加载了可执行程序后,系统就将可执行文件的段基址加上偏移地址形成实际的物理地址,并直接加载到内存中运行。

2.托管程序的执行过程

托管程序的可执行文件中,包括是中间语言以及元数据,当然不能直接运行,必须启动CLR,由CLR对中间语言进行即时编译机器代码,并加载到内存里面执行(具体过程“程序在进入入口函数前会提前跳转到MSCoree.dll中,调用它的代码来启动CLR并完成一些初始化工作)。当然,IL中的方法并不是每次被调用都会被编译一次,而是它只有在第一次调用时才进行编译,即时编译会将方法名称以及对应的入口存放在映射表中,当下次调用该方法时,会直接从映射表里去而不是再编译一次。

相关文章:

  • 什么是回调函数?
  • ShowWindow与UpdateWindow
  • VS2015无法打开源文件#include “iostream.h“的解决方法
  • BHO
  • 开放平台
  • js(=>) 箭头函数
  • JavaScript中的回调函数(callback)
  • com教程(使用VS2015编写ATL示例)
  • 控制台console使用MFC库函数,Cout输出CString的方法
  • PDFsharp使用介绍
  • 详解开源免费且稳定实用的.NET PDF打印组件itextSharp
  • 机器人是什么
  • C++中L和_T()之区别
  • 关于 wcout 输出中文的问题
  • 非静态成员必须与特定对象相对
  • 30秒的PHP代码片段(1)数组 - Array
  • bootstrap创建登录注册页面
  • ES6 学习笔记(一)let,const和解构赋值
  • IOS评论框不贴底(ios12新bug)
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • Java应用性能调优
  • nginx 配置多 域名 + 多 https
  • php的插入排序,通过双层for循环
  • python3 使用 asyncio 代替线程
  • Python连接Oracle
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 回顾2016
  • 检测对象或数组
  • 前端之React实战:创建跨平台的项目架构
  • 手机端车牌号码键盘的vue组件
  • 一起参Ember.js讨论、问答社区。
  • 由插件封装引出的一丢丢思考
  • 2017年360最后一道编程题
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​决定德拉瓦州地区版图的关键历史事件
  • #HarmonyOS:基础语法
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (poj1.3.2)1791(构造法模拟)
  • (实战篇)如何缓存数据
  • (五)c52学习之旅-静态数码管
  • (转)nsfocus-绿盟科技笔试题目
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET单元测试
  • .NET企业级应用架构设计系列之技术选型
  • /boot 内存空间不够
  • ::什么意思
  • @KafkaListener注解详解(一)| 常用参数详解
  • @test注解_Spring 自定义注解你了解过吗?
  • @Transaction注解失效的几种场景(附有示例代码)
  • [@Controller]4 详解@ModelAttribute