天天看點

.NET簡談互操作

本文出自 “专注C#、.NET” 博客,请务必保留此出处http://wangqingpei557.blog.51cto.com/1009349/595015

 

.NET簡談互操作

 

.NET简谈互操作(一:开篇介绍)

互操作系列文章:

.NET简谈互操作(一:开篇介绍)

.NET简谈互操作(二:先睹为快)

.NET简谈互操作(三:基础知识之DllImport特性)

.NET简谈互操作(四:基础知识之Dispose非托管内存)

.NET简谈互操作(五:基础知识之Dynamic平台调用)

.NET简谈互操作(六:基础知识之提升平台调用性能)

.NET简谈互操作(七:数据封送之介绍)

本人最近在学习非托管C++互操作的技术,有点小收获不敢私藏拿出来跟大家分享;作为.NET开发人员,我们有必要学习一些互操作方面的知识;尤其对一些高级程序员来说,掌握非托管的互操作能很好的增加我们的技术竞争力;由于互操作的内容很多,我打算用系列来讲,这篇就当是随便唠叨做入门介绍吧;

.NET平台的相关基础知识我就不浪费时间讲了,直接进入主题吧;.NET是在操作系统上的一层软件开发框架,是在Windows平台上的抽象封装,将复杂的软件开发过程简单化,让我们摆脱复杂重复的技术难点进行高效的应用系统开发;但是在工作过程中我们经常会遇到一些调用Win32的相关操作,比如跟用户界面相关的接口调用User32.dll框架,一些内核处理Kernel.dll框架,这些都是平台提供给我们的接口;要想软件在计算机上运行起来,不管多少层封装、多少层抽象最终还是要将我们编写的代码转换成机器码才能成功运行软件,只是减轻了我们程序员的工作量;[王清培版权所有,转载请给出署名]

1:

由于.NET应用程序是托管的,运行在受限的CLR环境中,所有的内存分配回收等等都是CLR负责管理;这样既有好处也有坏处,我们无法用程序来为所欲为,只能用.NET提供给我们的功能来编写,在没有P/Invoke(平台调用)的情况下,我们能做的事情真的很好,微软也考虑到如果.NET不提供对过去的Windows平台上技术的支持,必然将大大妨碍.NET技术的发展,对于一些成熟的技术如:COM(组件对象模型)、OLE(复合文档)、用户界面(User32)、内存管理(Kernel)等等都提供了很好的支持,而且相当简单,我们能很方便的进行调用非托管技术;[王清培版权所有,转载请给出署名]

要想很好的利用P/Invoke(平台调用)进行互操作,我们不单单要懂.NET平台的相关技术,我们也需要对非托管的技术有个简单的了解,比如C++中的变量的内存占用和非托管的内存占用大小区别、C++中的字符编码与.NET中的字符编码区别;任何两种不同技术之间的区别不仅仅是我们表面看上去的那些东西,更重要的在于它的运行与内存分配;在我们学习平台调用的时候,我们需要花上一点时间去学习一些非托管技术,比如COM的内存分配方法CoTaskMemAlloc,C++的内存分配NEW,C的内存分配malloc,这些分配方法的不同导致托管的封送拆收器的处理不同;进行互操作的时候,更多需要考虑的是两者之间的等价处理,如变量的内存大小布局是否一致;在托管中调用非托管代码返回一个指针类型,我们用IntPtr接受,然后用MarShal封送拆收器的帮助类,来进行对象转换;我讲的这些只是互操作中的冰山一角,当我们能熟练进行平台调用的时候我们所开发的软件类型将大大增加,不单单是Winform和asp.net之类的,我们也可以用.NET来开发一些大型的网络系统,更重要的是我们可以用.NET来开发一些部分.NET程序员望而却步的软件,同样是.NET程序员,这样一来我们就比同行多了一些筹码;

总结:这篇主要是简单介绍互操作要涉及的知识面,可能我这短短的几句话很难说清楚互操作的一些技术问题,不要急,请继续关注本人,下面我们将具体介绍互操作中的实质性的技术操作;

.NET简谈互操作(二:先睹为快)

互操作系列文章:

.NET简谈互操作(一:开篇介绍)

.NET简谈互操作(二:先睹为快)

.NET简谈互操作(三:基础知识之DllImport特性)

.NET简谈互操作(四:基础知识之Dispose非托管内存)

.NET简谈互操作(五:基础知识之Dynamic平台调用)

.NET简谈互操作(六:基础知识之提升平台调用性能)

.NET简谈互操作(七:数据封送之介绍)

我们继续.NET互操作学习,为了揭开互操作的神秘面纱,今天这篇文章我们就来先睹为快,让我们先来做个例子,基础的东西,我们陆续进行讲解;由于互操作牵扯到的东西非常多,比较复杂,我们要循环渐进的学习,为了给大家有一定的吸引力,让我们一边看一边能动手做起来;本篇文章用VisualStudio2010进行演示,将非托管代码暴露在我们眼前,它对我们来说不在有神秘感,我们通过.NETP/invoke(平台调用)很方便的进行调用,可能需要我们掌握一些C++的基础知识,但是也放心啦,有C语言的基础功,足够用了;我们开始吧;[王清培版权所有,转载请给出署名]

要想成功调用非托管代码我们需要一些准备工作;

1.需要知道非托管DLL文件有哪些导出函数是可以调用的,由于导出函数的方法的名称被重新整顿过了比如一个方法add(int number),整顿后为[email protected],为什么会这样我们后面进行讲解,这跟C++语法有点牵连,这里就不扯了;

2.在托管代码中定义非托管函数的申明,也就是我们.NET平台里的DLLImport特性,该对象是托管平台进行平台调用的核心对象,用它.NET引擎就知道该方法是在外部定义的;

3.用托管代码进行调用非托管方法;

下面我们就开始用VisualStudio2010进行演示,我们用Vs2010创建一个解决方案,里面包括托管与非托管两个项目;我给出非托管代码的创建图:

1:

选择VisualC++—>Win32—>Win32项目;[王清培版权所有,转载请给出署名]

2:

这样我们就创建了非托管C++的开发环境;下面我们来编写C++的代码;我拿我自己事先创建好的项目做演示;

3:

我的非托管项目是Win32DLL,创建好后会有一个和项目名称一样的.cpp文件,这个是源代码文件,我们只需要在里面写点非托管操作的代码就行了;

// Win32DLL.cpp : 定义 DLL 应用程序的导出函数。

//

#include"stdafx.h"

extern"C" _declspec(dllexport) int _stdcall add(int x,int y)

{

return x+y;

}

在文件Win32DLL.cpp文件里面我编写了一个add方法,仅仅作为演示使用;暂且我们不管他的具体语法;切换到托管代码中,我的代码如下:

using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

namespaceCSharp.Interop

{

public static class InteropClass

{

[DllImport("Win32DLL.dll", EntryPoint = "add", CharSet =CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

public static extern int AddNumber(int x, int y);

}

}

这样我们就可以直接调用AddNumber方法进行调用了;

using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

namespace CSharp

{

class Test1

{

static void Main(string[] args)

{

try

{

int count = Interop.InteropClass.AddNumber(10, 20);

Console.Write(count);

}

catch (DllNotFoundException errnot) { }

catch (EntryPointNotFoundException errpoint) { }

}

}

}

总结:本篇文章的重点是想让大家看一下托管与非托管的整个生命周期是怎么来的,对托管非托管的代码之间的协调有个自己的思路,后面将详细的说明互操作中涉及的细节;[王清培版权所有,转载请给出署名]

本文出自 “专注C#、.NET” 博客,请务必保留此出处http://wangqingpei557.blog.51cto.com/1009349/595559

 

.NET简谈互操作(三:基础知识之DllImport特性)

互操作系列文章:

.NET简谈互操作(一:开篇介绍)

.NET简谈互操作(二:先睹为快)

.NET简谈互操作(三:基础知识之DllImport特性)

.NET简谈互操作(四:基础知识之Dispose非托管内存)

.NET简谈互操作(五:基础知识之Dynamic平台调用)

.NET简谈互操作(六:基础知识之提升平台调用性能)

.NET简谈互操作(七:数据封送之介绍)

我们继续.NET互操作学习,上一篇文章中我们介绍了托管代码怎么与非托管代码C++之间的互操作;要想在托管代码中成功的进行非托管调用,要注意的细节还是很多的,下面我们就来介绍一下,在互操作方面托管代码要注意的必不可少的代码申明,为我们下面的互操作打下基础;

在.NET平台里面开发我们还是很幸运的,微软为我们做了很多很方便的东西,我们只需要了解一下就能快速上手;在互操作里面我们只需要借助DllImport特性就能很好的切入到非托管代码中,微软为我们处理了复杂的内存分配、动态函数查找、地址解析等等本应该我们自己去处理的事情;所谓工欲善其事,必先利其器;选择.NET是正确的;下面我们就来逐一讲解在互操作方面我们要用到的一些基本知识当然也是踏进互操作门槛的关键技术要点;[王清培版权所有,转载请给出署名]

DllImport特性

首先我们来了解一下DllImport代码特性,在进行互操作的时候,我们需要用DllImport来标识该方法是非托管的代码方法,在编译器编译的时候它能够正确的认识出被该特性标记的是外来代码段,所以能顺利的通过编译,当到达程序运行的时候,也能够正确的认识出该代码是引用非托管的代码,这样就让我们的CLR去加载非托管DLL文件,然后查找到入口点进行调用;我们拿上一篇文章中的示例来讲吧;

[DllImport("Win32DLL.dll",EntryPoint = "add", CharSet = CharSet.Auto, CallingConvention =CallingConvention.StdCall)]

public static extern int AddNumber(int x, int y);

这段代码申明了一个非托管代码AddNumber方法,我们来看DllImport特性的这几个属性具体什么意思;在DllImport构造函数里面有一个dllname的参数,请看图:

图1:

我们可以看到注释,这个构造函数的参数是非托管dll的名称,也就是我们所要用到的导入方法的具体位置;上图中的参数是 "Win32DLL.dll"字符串,也就是我们上篇文章中创建的非托管C++生成文件,在后面有几个相关属性,我们也逐一来解释;

DllImport特性中的EntryPoint可选属性;

图2:

EntryPoint属性是用来确定非托管方法的入口点是什么,在图1中,我的非托管代码的入口点名称是add,也就是说明一个问题,系统在识别非托管代码名称是靠Entrypoint属性来的,而不是我们在C#编辑器中申明的托管代码的名称AddNumber,系统只用Entrypoint来确定非托管入口点,而我们可以用随意的名称来定义非托管的调用名称,这往往是个好方法,由于我们托管代码经常要讲究一些对象化的编程方法,所以在命名方面也是至关重要的,不能随意起一个没有任何对象意义的名称,这样我们就可以定义自己的,复合当前上下文的,很形象的名称;[王清培版权所有,转载请给出署名]

DllImport特性中的CharSet可选属性;

图3:

CharSet属性是用来确定在托管与非托管调用的过程中用什么字符编码来封送数据,因为我们的.NET平台是采用的Unicode编码,而标准C++是采用的Ansi编码,在我们了解了非托管代码的编码方式之后,我们就很确定用什么编码,那么如果我们不清楚非托管代码是用什么语言编写的或者不清楚它的编码方式时,我们可以使用CharSet枚举中的auto值,让CLR自动为我们处理相关细节;

DllImport特性中的CallingConvention可选属性;

CallingConvention属性也是一个比较重要的属性,在平台调用的过程中起到查找入口点的作用,在托管代码进行非托管代码入口点查找时,会通过CallingConvention中的值进行确认非托管入口点的调用约定,上篇文章中我们提到了调用约定的一些概念,

extern"C" _declspec(dllexport) int _stdcall add(int x,int y)

{

return x+y;

}

这段是非托管C++代码,在这个方法前面有一个_stdcall的关键字,这个关键字的意思是说方法的调用约定,我们来看_stdcall调用的相关概念说明:函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈;意思是说,有被调用方来清理调用堆栈;

还有_declspec(dllexport)关键代码,这段意思是说将该方法导出为可调用方法,也就是说外来者是可以调用的方法,由于DLL文件不是每一个方法都可以调用的,只有编写者愿意才能使用;;[王清培版权所有,转载请给出署名]

extern"C"关键代码,是名称修饰的意思,在编译期间采用C编译选项来编译这个函数,由于C和C++是不同的语言,具体的语法也不相同,所以在C++中提供了选择的余地,让我们可以选择用什么编译方式来编译方法,如果我们用"C"代码修饰了之后,编译器会通过区分不同的调用约定来重新命名方法的名称;

摘:

__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个"@"符号和其参数的字节数,格式为[email protected],例如:function(int a, int b),其修饰名为:[email protected]

__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。

__fastcall调用约定在输出函数名前加上一个"@"符号,后面也是一个"@"符号和其参数的字节数,格式为@[email protected]。

(由于本人非C++出身所以未能总结出自己的一套理解思路,再此先用一下网摘的说明)

总结:本篇文章重点是讲解DLLImport关键特性的使用,在P/Invoke过程中的基本约定需要用该特性来表示,由于托管与非托管在实现方面是不一样的,这样怎么来协调这两个平台之间的调用,我想.NET为我们做好了; ;[王清培版权所有,转载请给出署名]

本文出自 “专注C#、.NET” 博客,请务必保留此出处http://wangqingpei557.blog.51cto.com/1009349/597282

 

.NET简谈互操作(四:基础知识之Dispose非托管内存)

互操作系列文章:

.NET简谈互操作(一:开篇介绍)

.NET简谈互操作(二:先睹为快)

.NET简谈互操作(三:基础知识之DllImport特性)

.NET简谈互操作(四:基础知识之Dispose非托管内存)

.NET简谈互操作(五:基础知识之Dynamic平台调用)

.NET简谈互操作(六:基础知识之提升平台调用性能)

.NET简谈互操作(七:数据封送之介绍)

我们继续.NET互操作学习。前一篇文章中我们学习了基础知识中的DllImport关键特性;我们继续学习基础知识中的内存释放相关技术;

在托管与非托管内存之间,是不允许直接调用进行相互操作的,这点我相信稍微有点.NET技术基础的人都能了解;上一篇文章中有位朋友提出了些问题,刚好我们在这里就当是学习来粗略的分析一下,问题大概是这样的:[王清培版权所有,转载请给出署名]

1.在.NET托管平台上的对象与非托管的对象之间是否能直接互换?

2.托管内存与非托管内存是否存在差异?如果存在差异是否有方法能进行等价转换或者叫做等价复制;

我们就上面两个问题来详细分析一下,由于本人也在学习这方面的知识所以理解的也不是很透彻,只能是一些微薄的猜测吧,大家一起来帮忙分析;

第一个问题:在.NET托管平台上的对象与非托管的对象之间是否能直接互换?其实第一个问题是隐藏在第二个问题里面的,首先我们要确定的是,“互换”与“转换”的概念,为了统一大家步伐,我们必须将“互换”与“转换”做一些定义;

“互换”:我假定有两块内存空间,每块内存空间存储不同的对象,比如:在一块非托管内存块中保存着Char*类型的指针,在另一块托管内存块中保存着String类型的值,由于Char*是指针类型,而我们的托管String是.NET平台类型,微小的变化就可能引起内存布局不同的可能;数据结构里面讲到,变量分为原子型和结构型,原子型变量都存在着字面值的概念,什么叫字面值就是我们人用来交流的数据值,比如:bool类型的true和false;两块内存中保存的东西是不一样的,高级语言在经历了一系列编译器处理之后,会确定下来内存中保存的数据是什么样子的;也就是说内存的分配原则是按照对象的类型来的;在托管与非托管的内存空间中,不同的平台所有的引用地址类型也各不相同,当然如果能成功的“互换”就说明能在托管与非托管之间进行直接数据访问了;总之互换是两个对象之间的彼此转换,是双向的;

“转换”:转换的概念我个人觉得来源于高级语言的语法解释而已,所谓转换其实也就是a到b的转换,将一种类型转换成另一种类型的动词描述,我们具体点打个比方:如果有一个对象是a,有一个对象是b,我想将a转换成b,就是将对象从一种状态转换成另一种状态;总之转换是单向的,只能是一种到另一种的转换;

针对上述我们分析的结果,由于时间比较紧,我们从第二个问题入手吧,因为本篇文章不是解决问题为主的;经过上面的分析我们确定托管内存与非托管内存的结构是不一样的,这种不一样并不是所有的对象类型都不一样,在.NET平台里面有一些如:int,char之类的平台等价类型,是可以直接互换的;如果是一些非等价类型,要想成功进行转换就必须得借助于托管对象关于互操作方面的知识了,由于这样一扯可能今天这篇文章是讲不完了,这里就粗略的过一下吧;我们下面进入今天的主题;其实有些概念真的不太好讲,你要说托管与非托管内存不一样,有人会问不一样在什么地方;真的没有说服性的理由;

关于非托管内存释放的问题

似乎今天的主题就是关于托管与非托管内存的问题,刚好能详细的说明上面的问题;要想在托管内存中释放非托管内存,没有那么简单;不同的代码库,调用的分配内存的方法不一样,算法也就不一样;C的分配与回收是malloc、free,C++的是new、delete,COM是CoTaskMemAlloc、CoTaskMemFree;在操作系统这么大的一个平台上存在着千千万万种内存操作方式,大家所属性的是上述几种,有可能那位技术牛人自己写了一套内存分配和回收的DLL,那么我们不可能让.NET一劳永逸,所以存在着这些不确定因素;(我穿插一句废话,其实不管我们所说的底层是什么样子的,哪怕真的越过了内核到了硬件抽象层甚至到了驱动部分,1就是1,2还是2,该走的路程还是会走,我们千万不要神秘话底层,只要我们抱着一颗探索的心什么都OK);

.NET平台的默认内存分配和回收都是基于COM(组件对象模型)的,由于COM是一套非托管年代的公用原则,所以微软只能做到这个位置了;如果非托管内存是用COM的CoTaskMemAlloc分配的那么.NET的封送拆收器会自动的释放掉那块内存;如果是非托管内存是采用C的或者C++或者其他的什么方式分配的.NET根本不知道你是怎么分配的,所以这个时候需要我们采用折中的办法来解决。非托管的内存释放只有非托管知道,所以在非托管中定义一个释放非托管资源的方法,然后在用.NET平台去调用这个非托管方法来进行释放内存;下面我们来看一个小例子,以说明问题为主;

图1:

这是非托管的代码,由于时间关系我就没有写具体的操作了;说明原理就行了;

图2:

这是在托管.NET平台上面定义的非托管代码调用关系;

图3:

这样一来,不管非托管的内存是采用什么方法分配的内存我们都能在托管中将其释放;首要的原则就是我们必须清楚非托管内存的分配方法;如果不清楚的情况下,默认是COM的释放方法;王清培版权所有,转载请给出署名

本文出自 “专注C#、.NET” 博客,请务必保留此出处http://wangqingpei557.blog.51cto.com/1009349/599284

 

.NET简谈互操作(五:基础知识之Dynamic平台调用)

互操作系列文章:

.NET简谈互操作(一:开篇介绍)

.NET简谈互操作(二:先睹为快)

.NET简谈互操作(三:基础知识之DllImport特性)

.NET简谈互操作(四:基础知识之Dispose非托管内存)

.NET简谈互操作(五:基础知识之Dynamic平台调用)

.NET简谈互操作(六:基础知识之提升平台调用性能)

.NET简谈互操作(七:数据封送之介绍)

我们继续.NET互操作学习。在上篇文章中我们学习了关于托管与非托管内存Dispose(释放)问题;下面我们继续学习基础知识中的Dynamic(动态)平台调用技术;

在前几篇文章中,我们都是采用按部就班的方式来调用非托管代码的,先定义非托管代码的托管定义,然后用DllImport来标识相关调用约定;这篇文章我们将介绍怎么通过动态的方式调用非托管代码;在进行讲解之前我们有必要简单的了解一下,托管代码调用非托管代码的大概的步骤或者说是相关细节吧;只有当我们脑子里有一套属于自己的理解思路时,文章才显的有价值;[王清培版权所有,转载请给出署名]

平台调用过程原理

文字使用始终没有图片的表达性强,我们还是来看图吧;

图1:

这幅图画的不是很全,但是大概能表达意思了;

当我们第一次调用非托管DLL文件的时候(穿插一下,这里就牵扯到为什么有些东西必须由操作系统来处理,为什么要有内核,就是用来处理一些我们平时不能随便动的东西,就拿LoadLibrary方法来讲,可能它就是进入了内核然后设置相关参数,帮我们保存了非托管DLL在内存的代理存根,当我们下次又进入到内核的时候,系统去检查一下,发现有过一次调用了,所以下次就去读取存根中的地址进行调用),系统会去加载非托管DLL文件到内存并设置相关数据,以便后期使用;动态调用的原理就是我们把这部分的工作自己手动来做,比如第一次调用非托管DLL肯定是要慢于后面调用的;所以在一些必要的场合下,我们真的有必要进行动态P/Invoke;

动态平台调用示例1

在托管的.NET中我们可以通过使用Win32API中的LoadLibrary方法来手动加载非托管DLL到内存来;

[DllImport("kernel32.dll",EntryPoint = "LoadLibrary")]

public static extern IntPtr LoadLibrary(string iplibfilenmae);

这样的操作就好比我们图1中的第一次调用过程要执行的操作;

[DllImport("Win32DLL.dll",EntryPoint = "add", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

private static extern int add(int x, int y);

同样我们还是申明非托管代码的定义,我们来看全部代码;

namespaceCSharp.Interop

{

/// <summary>

/// 动态平台调用,手动加载非托管DLL文件

/// </summary>

public static class DynamicPinvoke

{

[DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]

public static extern IntPtr LoadLibrary(string iplibfilenmae);

[DllImport("Win32DLL.dll", EntryPoint = "add", CharSet =CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

private static extern int add(int x, int y);

public static void Test()

{

string currentdirectory =Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

string dllpath = Path.Combine(currentdirectory, "Win32DLL.dll");

IntPtr dlladdr = LoadLibrary(dllpath);

if (dlladdr == IntPtr.Zero)

throw new DllNotFoundException(string.Format("在{0}未能找到相关DLL文件", dllpath));

int addnumber =add(10, 20);

Console.Write(addnumber);

}

}

}

动态平台调用示例2

第一个示例我们是省略了系统调用过程,我们手动调用LoadLibrary来加载;可能没啥大的变化,示例2是通过非托管函数委托来进行动态调用的;

都知道托管委托就好比非托管的函数指针,幸好微软为我们提供了委托来调用非托管方法,适合真的很强大;请看代码;

[UnmanagedFunctionPointer(CallingConvention.StdCall)][王清培版权所有,转载请给出署名]

delegate int add(int x, int y);

系统特性能改变代码的编译行为,所以我们有理由相信我们的add委托已经变成了非托管代码的引用;

namespaceCSharp.Interop

{

[UnmanagedFunctionPointer(CallingConvention.StdCall)]

delegate int add(int x, int y);

public static class DelegateInvoke

{

public static void Test()

{

string currentdirectory =Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

string dllpath = Path.Combine(currentdirectory, "Win32DLL.dll");

IntPtr dlladdr = Interop.DynamicPinvoke.LoadLibrary(dllpath);

if (dlladdr == IntPtr.Zero)

throw new DllNotFoundException(string.Format("在{0}未能找到相关DLL文件", dllpath));

IntPtr procadd = Win32Api.GetProcAddress(dlladdr, "[email protected]");

if (procadd == IntPtr.Zero)

throw new DllNotFoundException(string.Format("未能在内存中找到{0}入口点",Marshal.PtrToStringUni(dlladdr)));

add adddelegate = (add)Marshal.GetDelegateForFunctionPointer(procadd,typeof(add));

int result = adddelegate(10, 20);

bool isfree = Win32Api.FreeLibrary(dlladdr);

}

}

}

这段小小的代码里,深藏了很多技术细节;非托管代码导出调用方法时我们需要知道函数名被重整成啥样了,所以本人上传了PE文件查看器http://files.cnblogs.com/wangiqngpei557/PEinfo.zip,通过这个工具我们查看非托管代码的具体信息;这样便于我们调用;

Marshal是一个很强大的P/Invoke的类,可以将它看成是平台调用的体现吧,Marshal.GetDelegateForFunctionPointer方法是通过非托管内存指针获取UnmanagedFunctionPointer类型的委托;

总结:其实动态调用就是让我们竟可能的多去接触底层知识,一切都是可以理解的,只是功夫没到家;

 

.NET简谈互操作(六:基础知识之提升平台调用性能)

互操作系列文章:

.NET简谈互操作(一:开篇介绍)

.NET简谈互操作(二:先睹为快)

.NET简谈互操作(三:基础知识之DllImport特性)

.NET简谈互操作(四:基础知识之Dispose非托管内存)

.NET简谈互操作(五:基础知识之Dynamic平台调用)

.NET简谈互操作(六:基础知识之提升平台调用性能)

.NET简谈互操作(七:数据封送之介绍)

我们继续.NET互操作学习。本篇文章我们将来学习互操作基础知识中的最后一个知识点“提升平台调用的性能”;

在于非托管函数进行互操作的过程中,由于涉及的技术因数众多,因此程序的性能会受到这些因素的影响导致性能下降,本篇文章将来介绍在平台调用过程中提升性能的一些设计和编码方面的技巧;[王清培版权所有,转载请给出署名]

一:显示的制定要调用的非托管函数名称

我们在进行平台调用的时候,如果CLR无法在非托管DLL中找到与DllImport特性指定的函数名相同的非托管函数,那么CLR会尝试采用一些规则重新进行搜索。比如我们将sumA非托管函数的CharSet申明为CharSet.Ansi,那么CLR首先会通过根函数名(sum)进行搜索,如果在指定的非托管DLL中找到了此函数,就是用它。如果不能找到,就会使用带后缀A的函数(sumA)进行搜索。其实为什么会出现这种情况,原因来自于字符编码的不同,有的函数实现是采用Ansi方式,有的采用Unicode方式,这两种编码方式的不同最终导致数据在内存的存放也不同,所以在进行非托管调用的时候,我们需要注意;

非托管代码:

1.    extern "C" _declspec(dllexport) int _stdcalladdA(int x,int y) 

2.    { 

3.     return x+y; 

4.    }

托管代码申明1:

1.    [DllImport("Win32DLL.dll",EntryPoint ="add", CharSet = CharSet.Ansi, CallingConvention =CallingConvention.StdCall)] 

2.    public static extern int add(int x, int y); 

3.     

托管代码申明2:

1.    [DllImport("Win32DLL.dll",EntryPoint ="addA", CharSet = CharSet.Ansi, CallingConvention =CallingConvention.StdCall,ExactSpelling=true)] 

2.    public static extern int add(int x, int y); 

3.     

上面两段托管代码申明有些细微的不同的,第一个托管代码是我们常见的申明方式,而第二个是我们少见的申明,两种不同的申明方式对CLR的平台调用来说是不一样的,相对而言的调用过程也有少许的不同,经测试第二种的申明能提升少许的性能;第二种的代码申明中出现了ExactSpelling=true(显式的指定要调用的非托管函数的名称),这段代码的意思是说,我们强制使用EntryPoint申明的方法入口点,不允许CLR帮我们去动态的调整函数的名称在去查找入口名称,这样能省掉了CLR的查找时间;

二:对数据封送处理进行优化

在托管代码与非托管代码之间传递参数时,无论是传入还是传出,都要经过封送拆收器的封送处理。由于封送过程可能会涉及数据类型的转换,以及在非托管内存与非托管内存之间来回复制数据,所以封送处理也是影响平台调用性能的瓶颈之一。

CLR在进行数据封送时,只有两种选择的方式:要么锁定数据、要么复制数据。在默认的情况下CLR会在封送过程中复制数据,假如我们需要将一个Unicode字符串作为Ansi传递到非托管代码中时,首先CLR会将字符串复制一份出来,然后将复制出来的字符串进行转换成Ansi,然后在将转换后的Ansi字符串的内存地址传递给非托管代码;由于复制数据操作可能很浪费时间,所以封送数据也是影响性能的瓶颈之一;

数据封送还有一种就是锁定内存的方式,意思就是说CLR可以通过直接将托管对象锁定在垃圾回收堆上,已防止托管对象在函数调用生命周期内被回收,一旦托管对象被锁定,就可以直接将指向托管对象的指针传递给非托管代码中,这样就避免了复制数据的操作,达到优化的目的;

但是不是所有的数据类型都能被锁定的,要想能被锁定,必须具备一些跟平台相关的约定,我们来看要满足那些条件的对象才能被CLR锁定;

1.必须是托管代码调用非托管代码,也就是本机代码;

2.托管数据类型必须是可直接复制到本机结构(blittable)中的数据类型,或者能够在满足某些条件下转换成本机结构数据类型;

3.传递的不是引用(ref,out)参数;

4.被调用代码和调用代码必须处于同一线程上下文或者线程单元中;

经过我们上面的总结,我们就可以发现,要想减少封送拆收器的数据复制操作,我们可以用本机结构类型进行传递,所谓本机结构类型就是在托管内存中和非托管内存中的表示形式是完全一样的。[王清培版权所有,转载请给出署名]

所以在准备开发平台调用程序时,我们尽量的考虑使用本机数据结构;如:System.Byte:无符号8位整型、System.SByte:有符号8位整型;

总结:由于这篇文章涉及到了数据封送的相关技术,很快我们结束了基础部分的学习,下面我们将进入学习互操作数据封送相关技术;

本文出自 “专注C#、.NET” 博客,请务必保留此出处http://wangqingpei557.blog.51cto.com/1009349/601796

 

.NET简谈互操作(七:数据封送之介绍)

互操作系列文章:

.NET简谈互操作(一:开篇介绍)

.NET简谈互操作(二:先睹为快)

.NET简谈互操作(三:基础知识之DllImport特性)

.NET简谈互操作(四:基础知识之Dispose非托管内存)

.NET简谈互操作(五:基础知识之Dynamic平台调用)

.NET简谈互操作(六:基础知识之提升平台调用性能)

.NET简谈互操作(七:数据封送之介绍)

我们继续.NET互操作学习。互操作的基础知识已经差不多完了,当然一篇小小的文章很难全面的讲述互操作的方方面面,本人只是总结出关键的地方好让我们能入个门,在后期如果想要更深入的学习,肯定需要一本详细而全面的书籍才行。想要精通.NET互操作当然也少不了对非托管的技术学习,C++、COM等等,只有既熟悉.NET也熟悉非托管技术才能将互操作融会贯通。从这篇文章起我们将进入到.NET互操作的数据封送阶段,数据封送是.NET/Pinvoke关键的部分,任何托管代码想要和非托管代码互操作,少不了数据的传递返回;[王清培版权所有,转载请给出署名]

1:

在托管代码调用非托管代码的时候,数据经历了很复杂的封送。由于托管的数据类型与非托管的数据类型内存结构可能是不一样的,要想将托管数据参数传递到非托管代码中,并且能成功的接受到非托管的返回值,我们需要很严格的按照双方的数据类型约定来才行。比如在C++中的Char*是一个字符指针,当我们想要将字符串传递到非托管代码中时,我们需要考虑怎么将参数无差错的封送到非托管代码。而在托管C#中的String类型是一个引用类型,两者有相同点,也有不同点。

在我们用.NET/PInvoke进行Win32API的调用的时候,大多数的情况下我们是需要传递某种结构类型给API,然后在接受返回值;在数据封送的过程中,有些概念是我们平时不曾碰见的。.NET数据封送很智能,CLR的封送拆收器能通过识别我们传递的数据类型情况进行自动选择封送数据的方式,比如我们将一个Class的类类型传递给非托管API,默认的Class类是不能进行互操作使用的,必须加上相应的特性进行标记,编译器编译的时候能识别出这是要进行封送的数据类型;将Class类型进行封送时,封送拆收器会进行判断,如果我们传递给非托管代码的Class中的所有内部对象都是平台数据类型,那么CLR会将这个对象在内存中锁定,然后直接将内存地址封送给非托管代码,非托管代码直接对这数据进行操作。这是封送引用地址的方式,如果我们传递给非托管API的是非平台类型,那么CLR的封送拆收收器会将我们的托管类型复制出来进行非托管类型转换,然后将转换后的数据传递给非托管,这样的过程是复制数据的过程。互操作的数据封送基本上就是这两种,1数据的复制封送,2数据的内存地址封送;

下面我们用一副图来表达我上面所说的原理。

2:

如果托管的数据类型与非托管的数据类型在内存中是等价的,那么CLR进行封送的方式会很简单。如果托管的数据类型与非托管的数据类型是不等价的,那么CLR会进行相应复制转换操作,当然这样会丢失数据内存泄漏都是有可能的,但是我们不用怕,.NET为我们做好了很好的互操作桥梁,我们只要对要封送的数据进行一系列的设置就能很成功的进行数据封送了;

总结:这篇文章主要给大家介绍一下,关于托管与非托管的数据封送相关的概念,下面我们将学习互操作的数据封送;

本文出自 “专注C#、.NET” 博客,请务必保留此出处http://wangqingpei557.blog.51cto.com/1009349/603056

 

 

 

繼續閱讀