1.平台互操作性和不安全的代码:C#功能强大,但有些时候,它的表现仍然有些“力不从心”,所以我们只能摒弃它所提供的所有安全性,转而退回到内存地址和指针的世界。

  C#通过3种方式对此提供支持。

(1)第一种方式是通过平台调用(Platform Invoke,P/Invoke)来调用非托管代码DLL所公开的API。

(2)第二种方式是通过不安全的代码,它允许我们访问内存指针和地址。很多情况下,代码需要综合运用这两种方式。

(3)第三种方式是通过COM Interop(COM互操作)。

2.平台调用:

(1)外部函数的声明:确定了要调用的目标函数以后,P/Invoke的下一步便是用托管代码声明函数,和普通方法一样,必须在一个类中声明目标API,但要为它添加extern修饰符,从而把它声明为外部函数,extern方法始终是静态方法,因此不包含任何实现。相反,附加在方法声明之前的DllImport特性指向实现。该特性需要定义该函数的DLL的“名称”,导入的DLL必须在路径内,其中包含可执行文件的目录,以使其能够加载成功。“运行时”根据方法名来判断函数名,然而也可以用EntryPoint具名参数来重写默认行为,明确提供一个函数名。

(2)参数的数据类型:在确定目标DLL和导出函数,那么要标识或创建与外部函数中的非托管数据类型对应的托管数据类型。

3.为顺序布局使用StructLayoutAttribute:有些API涉及的类型没有对应的托管类型,要调用这些API,需要托管代码重新声明类型。例如,可以使用托管代码来声明非托管的COLORREF struct,如ColorRef结构清单。代码中声明的关键之处在于StructLayoutAttribute,默认情况下,托管代码可以优化类型的内存布局,所以,内存布局可能不是从一个字段到另一个字段顺序存储。为了强制顺序布局,使类型能够直接映射,而且可以在托管和非托管代码之间逐位地复制,你需要添加StructLayoutAttribute特性,并指定LayoutKind.Sequential枚举值。

4.平台调用(P/Invoke)的错误处理:Win32 API编程的一个不便之处在于,错误经常以不一致的方式来报告,如有API返回0、1、false等,有API以out参数来处理,非托管代码中的Win32错误报告很少通过异常来生成。P/Invoke设计者为此提供了相应的处理机制,要启用这一机制,DllImport特性的SetLastError具名参数要设为true,这样就可以实例化一个System.ComponentModel.Win32Exception。在P/Invoke调用之后,会自动用Win32错误数据来初始化它,如VirtualMemoryManger类的代码清单。这样一来,开发人员就可以提供每个API使用的自定义错误检查,同时仍然可以使用一种标准方式来报告错误。

5.使用SafeHandle:很多时候,P/Invoke会涉及一个资源,比如窗口句柄(Window handle),等等。在用完此类资源之后,代码需要清理它们。但是,不要强迫开发人员记住这一点,并每次都人工编写代码,而是应该提供实现IDisposable接口和终结器的类。为了对此提供内建的支持,如下面的VirtualMemoryPtr类,该类派生自System.Runtime.InteropServices.SafeHandle。SafeHandle类包含两个抽象成员:IsInvalid和ReleaseHandle()。在后者中,你可以放入对资源进行清理的代码,前者则指出是否执行了资源清理代码。可查看VirtualMemoryPtr类代码清单。

6.P/Invoke指导原则:

(1)核实确实没有托管类型已经公开你想要的API。

(2)将API外部方法定义为private,或者在简单的情况下定义为Internal。

(3)围绕外部方法提供公共包装方法,执行数据类型转换和错误处理。

(4)重载包装方法,并通过为外部方法调用插入默认值,减少所需的参数数目。

(5)在声明API的同时,使用enum或const为API提供常量值。

(6)针对支持GetLastError()的所有P/Invoke方法,务必将SetLastError命名特性的值设为true。这样一来,就可以通过System.ComponentModel.Win32Exception报告错误。

(7)将句柄之类的资源包装,包装在从System.Runtime.InteropServices.SafeHandle派生或者支持IDisposable的类中。

(8)非托管代码中的函数指针映射到托管代码中的委托实例。通常,这需要声明一个特定的委托类型,它与非托管函数指针的签名是匹配的。

(9)将输入/输出参数和输出参数映射到ref参数,而不是依赖于指针。

7.不安全的代码:可以使用unsafe用作类型或者类型内部的特定成员的修饰符。unsafe修饰符对生成的CIL代码本身没有影响。它只是一个预编译指令,作用是向编译器指明允许在不安全的代码块内操作指针和地址。

8.指针的声明:由于指针(本身只是恰好指向内存地址的一些整形值)不会被垃圾回收,所以C#不允许非托管类型之外的被引用物类型。换言之,类型不能是引用类型,不能是泛型类型,而且内部不能包含引用类型。如 byte* pData;指针是一种全新的类型,和结构、枚举、类不同,指针的终极基类不是System.Object,甚至不能转换成System.Object,相反,它们能转换成System.IntPtr(后者能转换成System.Object)。

9.指针的赋值:我们需要使用地址运算符(&)来获取值类型的地址。无论哪种方法,为了将一些数据的地址赋值给一个指针,要求如下。

(1)数据必须属于一个变量。

(2)数据必须是一个非托管类型。

(3)变量需要用fixed固定,不能移动。

  如 byte* pData = &bytes[0];//编译错误,数据可能发生移动,需要固定。

  如 byte[] bytes = new bytes[24]; fixed (byte* pData = &bytes[0]){}//编译正确

10.指针的解引用:为了访问指针引用的一个类型值,要求你解引用指针,即在指针类型之前添加一个间接寻址运算符*。如 byte data = *pData;不能对void*类型的指针应用解引用运算符,void*数据类型代表的是指向一个未知类型的指针。由于数据类型未知,所以不能解引用到另一种类型。相反,为了访问void*引用的数据,必须把它转换成其他任何指针类型的变量,然后对后一种类型执行解引用。

[StructLayout(LayoutKind.Sequential)]
public struct ColorRef
{
public byte Red;
public byte Green;
public byte Blue;
private byte Unused; public ColorRef(byte red, byte green, byte blue) : this()
{
Red = red;
Green = green;
Blue = blue;
Unused = ;
}
} public class VirtualMemoryManger
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, IntPtr dwFreeType); [DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess")]
internal static extern IntPtr GetCurrentProcessHandle(); [DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, AllocationType flAllocationType, uint flProtect); [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualProtectEx(IntPtr hPorcess, IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, ref uint lpflOldProtect); public static IntPtr AllocExecutionBlock(int size, IntPtr hProcess)
{
IntPtr codeBytesPtr = VirtualAllocEx(hProcess, IntPtr.Zero, (IntPtr)size, AllocationType.Reserve | AllocationType.Commit, (uint)ProtectionOptions.PageExecuteReadWrite);
if (codeBytesPtr == IntPtr.Zero)
{
throw new Win32Exception();
}
uint lpflOldProtect = ;
if (!VirtualProtectEx(hProcess, codeBytesPtr, (IntPtr)size, (uint)ProtectionOptions.PageExecuteReadWrite, ref lpflOldProtect))
{
throw new Win32Exception();
}
return codeBytesPtr;
} public static IntPtr AllocExecutionBlock(int size)
{
//通常应该将方法封装到公共包装里面,从而降低P/Invoke API调用的复杂性,这样可以增强API的可用性,同时更有利于转向面向对象的类型结构。
return AllocExecutionBlock(size, GetCurrentProcessHandle());
} public static bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize)
{
bool result = VirtualFreeEx(hProcess, lpAddress, dwSize, (IntPtr)MemoryFreeType.Decommit);
if (!result)
{
throw new Win32Exception();
}
return result;
} public static bool VirtualFreeEx(IntPtr lpAddress, IntPtr dwSize)
{
//无论错误处理、struct、还是常量值,优秀的API开发人员都应该提供一个简化的托管API,降低层的Win32API包装起来。
return VirtualFreeEx(GetCurrentProcessHandle(), lpAddress, dwSize);
}
} public class VirtualMemoryPtr:SafeHandle
{
public readonly IntPtr AllocatedPointer;
private readonly IntPtr ProcessHandle;
private readonly IntPtr MemorySize;
private bool Disposed;
public VirtualMemoryPtr(int memorySize) : base(IntPtr.Zero, true)
{
ProcessHandle = VirtualMemoryManger.GetCurrentProcessHandle();
MemorySize = (IntPtr) memorySize;
AllocatedPointer = VirtualMemoryManger.AllocExecutionBlock(memorySize, ProcessHandle);
Disposed = false;
} public static implicit operator IntPtr(VirtualMemoryPtr virtualAMemoryPointer)
{
return virtualAMemoryPointer.AllocatedPointer;
} protected override bool ReleaseHandle()
{
if (!Disposed)
{
Disposed = true;
GC.SuppressFinalize(this);
VirtualMemoryManger.VirtualFreeEx(ProcessHandle,AllocatedPointer,MemorySize);
}
return true;
} public override bool IsInvalid { get { return Disposed; } }
} [Flags]
public enum AllocationType
{
Reserve = 0x2000,
Commit = 0x1000,
Reset = 0x8000,
Physical = 0x400000,
TopDown = 0x100000,
} [Flags]
public enum ProtectionOptions
{
PageExecuteReadWrite = 0x40,
PageExecuteRead = 0x20,
Execute = 0x10
} [Flags]
public enum MemoryFreeType
{
Decommit = 0x4000,
Release = 0x8000
}

C#学习笔记15的更多相关文章

  1. Ext.Net学习笔记15:Ext.Net GridPanel 汇总(Summary)用法

    Ext.Net学习笔记15:Ext.Net GridPanel 汇总(Summary)用法 Summary的用法和Group一样简单,分为两步: 启用Summary功能 在Feature标签内,添加如 ...

  2. SQL反模式学习笔记15 分组

    目标:查询得到每组的max(或者min等其他聚合函数)值,并且得到这个行的其他字段 反模式:引用非分组列 单值规则:跟在Select之后的选择列表中的每一列,对于每个分组来说都必须返回且仅返回一直值. ...

  3. 并发编程学习笔记(15)----Executor框架的使用

    Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...

  4. [原创]java WEB学习笔记15:域对象的属性操作(pageContext,request,session,application) 及 请求的重定向和转发

    本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...

  5. Beego 学习笔记15:布局页面

    页面布局 1>     一个html页面由:head部分,body部分,内部css,内部js,外联css,外联的js这几部分组成.因此,一个布局文件也就需要针对这些进行拆分. 2>     ...

  6. Adaptive AUTOSAR 学习笔记 15 - 持久化 Persistency

    本系列学习笔记基于 AUTOSAR Adaptive Platform 官方文档 R20-11 版本 AUTOSAR_EXP_PlatformDesign.pdf.作者:Zijian/TENG 原文地 ...

  7. [学习笔记]15个QA让你快速入门51单片机开发

    一.C语言相关 Q1:sbit与sfr代表是什么?有什么作用? Q2:#define OSC_FREQ  22118400L这句宏命令里的“L”是什么意思? Q3:我粘贴了别人的代码,怎么发现没有un ...

  8. 【设计模式】学习笔记15:代理模式(Proxy Pattern)

    本文出自   http://blog.csdn.net/shuangde800 本笔记内容: 1. JAVA远程代理调用(RMI) 2. 代理模式 走进代理模式 在上一篇的状态模式中,我们实现了一个糖 ...

  9. Linux下汇编语言学习笔记15 ---

    这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...

  10. tornado 学习笔记15 _ServerRequestAdapter分析

         继承于HTTPMessageDeletegate,是HTTPMessageDeletegate的一种实现,用于处理请求消息. 15.1 构造函数 def __init__(self, ser ...

随机推荐

  1. python 导入模块与使用

    学习python之前一定要了解导入模块是怎么导入的,至少在看代码时不知道能很快的了解别人写的东西是哪里来. Python默认仅安装部分基础或核心模块,启动时也仅加载了基础模块,在需要是再显式的加载其他 ...

  2. Event(补交作业)

    三种方法可以创建Eventhandler 1.

  3. vue-cli中的webpack打包配置

    如下: assetsSubDirectory: 'static', assetsPublicPath: '/projectName/', 其中assetsSubDirectory是代表服务器上的实际路 ...

  4. javascript 判断对象的内置类型

    判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法.在toString方法被调用时,会执行下面的操作步骤:1. 获取this对象的[[Class ...

  5. 转一个财务方面常用到的数字金额转成汉字大写金额 php类

    系统里有牵扯到财务.合同等方面的处理时,常常需要把数字金额转成汉字大写金额(貌似这样正规),转一个转换的php class吧!<?php// 诸海加(ALPHA .z)// 2000-7-19 ...

  6. 分布式文件系统之FastDFS

    环境引入: 在一个大型的教育官网,会拥有大量优质的视频教程,并且免费提供给用户去下载,文件太多如果高效存储?用户访问量大如何保证下载速度?分布式文件系统是解决这些问题的有效方法之一 一.什么是文件系统 ...

  7. portmap安装

  8. python计算π及进度条显示

    今天老师布置了一个课后作业,去尽可能的准确计算π的值,还要显示时间和进度条,对于python小白的我,当然是综合书上和网上的知识,自己做了一个小程序,代码如下: 一.写代码的准备工作:用pip下载第三 ...

  9. POJ 2253 Frogger ,poj3660Cow Contest(判断绝对顺序)(最短路,floyed)

    POJ 2253 Frogger题目意思就是求所有路径中最大路径中的最小值. #include<iostream> #include<cstdio> #include<s ...

  10. js map()初步学习

    //array.map(callback,thisObject?),callback需要有return值 //map:'映射' 被映射成新的数组  eg1: let data = [3,4,2]; l ...