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. [Objective-C语言教程]函数(11)

    函数是一组一起执行任务的语句. 每个Objective-C程序都有一个C函数,也就是main()函数,所有最简单的程序都可以定义为函数. 可将代码划分为单独的函数.如何在不同的函数之间划分代码取决于程 ...

  2. nginx高性能WEB服务器系列之一简介及安装

    nginx系列友情链接:nginx高性能WEB服务器系列之一简介及安装https://www.cnblogs.com/maxtgood/p/9597596.htmlnginx高性能WEB服务器系列之二 ...

  3. maven项目报错

    [root@kube-master iff]# kubectl logs iff-dm-3029278244-9qrp6 -n iffjava.lang.IllegalArgumentExceptio ...

  4. sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit set

    使用root 登录,然后执行: chown root:root /usr/bin/sudo chmod 4755 /usr/bin/sudo reboot

  5. 标准结构篇:2)O型橡胶密封圈

    本章目的:设计出符合行业要求的O型橡胶密封圈,不必再为一而再,再而三的测试漏水而烦恼. 1.前言 O型橡胶密封圈,简称O型圈,是密封圈的一种,也是最有代表性的标准结构件.顾名思义,它的目的在于密封.密 ...

  6. stark - 介绍

    总结下自己寒假所写的stark组件. 介绍: stark组件,是一个帮助开发者快速实现数据库表的增删改查+的组件. 目标: 1min 中完成实现一张表的增删改查等功能. 目录: stark - 1 ⇲ ...

  7. 阿里云redisA迁移redisB迁移

    ./redis-port restore --input=./xxx.rdb --target=r-2zedc7c8e0557dsf4.redis.rds.aliyuncs.com:6379 --au ...

  8. Docker - 故障排查指南

    这阵子开始捣鼓 Docker,遇到过不少问题,下面记录下问题以及解决方案 一.Docker 报 Failed to start Docker Application Container Engine ...

  9. 映射网络驱动器 net use

    net use z: \\10.1.1.1\Software 12345678 /user:admin net use z: /del 然后文件夹Software权限

  10. lfs

      LFS──Linux from Scratch,就是一种从网上直接下载源码,从头编译LINUX的安装方式.它不是发行版,只是一个菜谱,告诉你到哪里去买菜(下载源码),怎么把这些生东西( raw c ...