C#编写不安全代码 和应用程序域
来源:互联网 发布:电信iptv机顶盒装软件 编辑:程序博客网 时间:2024/04/28 14:08
这篇文章的原文大家可以在 http://www.codeproject.com/csharp/unsafe_prog.asp 找到,作者是Kumar Gaurav Khanna,我仅仅对原文进行翻译,以下是文章的正文
在运行库的控制下执行的代码称作托管代码。相反,在运行库之外运行的代码称作非托管代码。COM 组件、ActiveX 接口和 Win32 API 函数都是非托管代码的示例。
有一个特别的主题始终吸引着最多的C/C++程序员,并且它总被认为太过复杂和困难而难以理解:指针!
然而,当讨论C#时,我遇见的大多数人都持有一种观点,认为在C#中没有指针的概念(如果允许我补充的话,他们的这种观点还非常牢固)。事实上,C#是“抹掉”了这个概念。尽管如此,C#编程中的不安全代码讨论的全是关于编程中指针方面的内容。并非如它字面上的意思那样,在编程中使用指针并没有什么不安全。
它之所以被这样称作不安全代码,是因为它不同于常规的.NET开发,不安全代码编程对程序员有特别的要求。在这篇文章中,我将先从两个很容易混淆的概念开始讨论,不安全代码及托管代码。然后将讨论如何编写不安全代码,也就是如何在C#中使用指针。
不安全代码 还是 非托管代码?这是一个问题
托管代码是指在CLR运行环境的监控下运行的代码。CLR运行环境将负责处理各种“家务”工作,比如:
#管理对象的内存
#执行类型检查
#进行内存垃圾回收
以上只提到了一部分。用户不用自己处理上面提到的工作。用户不用自己去直接操作内存,因为CLR运行环境将处理这些问题。
另一方面,所谓非托管代码是指在CLR环境以外运行的代码。这个概念最好的例子就是我们传统的WIN 32 DLL例如Kernel32.dll , user32.dll 和 安装在我们系统上的COM组件。如何为其分配内存空间,如何释放内存,怎么(如果需要)进行类型检测这些工作由自己来做。典型的C++编程中内存分配指针指向也是非托管代码的另一例子,因为你作为程序员需要自己来负责处理这些工作:
#调用内存分配函数
#确保生成的正确性
#确保当任务结束时候内存被释放
如果你留意的话,你会发现正如上面所说的那样,这些"家务"工作都由CLR运行环境处理,从而将程序员从这些繁重的工作中解放出来.
不安全代码是托管代码与非托管代码之间的纽带
不安全代码在CLR托管环境的监管下运行,就像托管代码那样,但允许你通过使用指针直接访问内存,就像非托管代码中的做法那样.这样,你同时获得了两个世界里最好的东西.你也许要写的程序需要使用传统WIN 32 DLL中的函数,这些函数需要使用指针.这个时候就轮到不安全代码来帮助你了.
现在,我们已经讨论了区别,让我们开始编码吧...毫无疑问,这是最棒的部分,你认为呢?
不安全代码内部
编写不安全代码需要使用两个特殊的关键字:unsafe 和 fixed .我们回忆会知道,总共由3种指针的操作运算:
*
&
->
在任意的语句,代码段,或函数中使用上面的的指针操作运算符,通过使用unsafe 关键字标识为不安全代码,如下面的例子所示:
public unsafe void Triple(int *pInt)
{
*pInt=(*pInt)*3;
}
上面的函数做的事情是将变量的值乘以3然后再对其赋值.值得注意的是包含需要乘以3的变量的地址的在函数中的使用.函数完成其工作.因为函数使用了"*"指针运算符,因此函数被标记为不安全代码,内存被直接的操作.
但是,还有一个问题.回忆我们之前的讨论,不安全代码是托管代码,因此将在CLR托管环境的监管下运行.现在,CLR运行环境可以有权利移动内存中的对象.这是一个可以减少内存碎片的原因.但这样的操作,对程序员来说是不知道的,是对程序员透明的,被指针指向的变量的内存可能被重现安排倒另外的内存位置.(这是由CLR完成的)
因此,如果 *pInt指向的变量的原始地址是1001 , CLR执行了一些内存重新安排以便减少内存碎片后,该变量的地址之前为1001 , 在重新进行内存安排后可能存储在内存中地址为2003的位置.这会是一个大灾难 , 因为指针指向的1001地址什么都没有了,指针变成了无效的.可能这是在.NET下指针使用被弱化的一个原因.你怎么认为呢?
使用固定指针
我们马上来讨论fixed关键字.当在一段代码中使用该关键字时,就告诉了CLR不用去动内存中的那些"问题"对象,它就不会去动它们了.这样,当在C#中使用指针时,使用fixed关键字就很好的避免了在运行时指针无效的问题.让我们来看看它是怎么做的:
using System;
class CData
{
public int x;
}
class CProgram
{
unsafe static void SetVal(int *pInt)
{
*pInt=1979;
}
public unsafe static void Main()
{
CData d = new CData();
Console.WriteLine("Previous value: {0}", d.x);
fixed(int *p=&d.x)
{
SetVal(p);
}
Console.WriteLine("New value: {0}", d.x);
}
}
我们在这里做了这些事情,在"固定"代码段,将CData类的x域的地址赋值给了整形变量指针p.现在,因为CLR被告知当"固定"代码段执行过程中不允许CLR移动其位置,当"固定"代码段的语句在执行时,指针所指向的变量在内存中的位置将不会改变,内存中的变量将不会被CLR重新部署内存位置.
这就是在C#中指针的使用.确保该函数是用unsafe标注的,确保被指向的对象用fixed标注,你也就已经有了在C#中使用指针的能力!
源文档 <http://blog.csdn.net/gamer_gerald/archive/2007/02/19/1511866.aspx>
编写不安全代码
MSDN:"尽管实际上对 C 或 C++ 中的每种指针类型构造,C# 都设置了与之对应的引用类型,但仍然会有一些场合需要访问指针类型。例如,当需要与基础操作系统进行交互、访问内存映射设备,或实现一些以时间为关键的算法时,若没有访问指针的手段,就不可能或者至少很难完成。为了满足这样的需求,C# 提供了编写不安全代码的能力。
在不安全代码中,可以声明和操作指针,可以在指针和整型之间执行转换,还可以获取变量的地址,等等。在某种意义上,编写不安全代码很像在 C# 程序中编写 C 代码。"
不安全代码必须用修饰符 unsafe
明确地标记。
这里所谓的编写"不安全代码",就是要跳出.net CLR的限制,自己进行地址分配和垃圾回收.在C++里面,我们定义一个简单的指针,至少要做三件事,1.给他分配内存,2.保证类型转换正确3将内存空间释放,而在.net环境里,CLR将程序员从这些事情里解放出来,用户不再需要直接手工地进行内存操作。但是有时候,比如调用windows底层函数,或是效率上的原因使我们需要自己去操作地址空间,本文主要是说明在c#里面指针的用法.
指针的使用、操作内存
1.& 和 *
c++里面,我们很熟悉这两个东西.在c#里面他们也一样可以用,只不过含有他们的代码如果不在unsafe
标记下,编译器将会将它拒之门外.当然如果条件编译参数没有/unsafe
也是无法编译通过的(如果用vs.net集成编译环境,则在项目属性页-代码生成节将"允许不安全代码块"设置成true).
&可以取得变量的地址,但是并不是所有的变量,托管类型,将无法取得其地址.c#里面,普通的值类型都是可以取得地址的,比如struct,int,long等,而class是无法取得其地址的,另外string是比较特殊的类型,虽然是值类型,但它也是受管的.这里插一下另一个运算符,sizeof,它也是仅可用于unsafe模式下.
看下面这段代码,里面简单的用到了*,&,sizeof,还有c#里很少见但c++里大家很熟的->:
class Class1 { struct Point { public int x; public int y; } public static unsafe void Main() { Point pt = new Point(); Point* pt1 = &pt; int* px = &(pt1->x); int* py = &(pt1->y); Console.WriteLine("Address of pt is :0x{0:X} ",(uint)&pt); Console.WriteLine("size of the struct :{0} ",sizeof(Point)); Console.WriteLine("Address of pt.x is :0x{0:X} ",(uint)&(pt.x)); Console.WriteLine("Address of pt.y is :0x{0:X} ",(uint)&(pt.y)); Console.WriteLine("Address of px is :0x{0:X} ",(uint)&(*px)); Console.WriteLine("Address of py is :0x{0:X} ",(uint)&(*py)); Console.ReadLine(); } }
我这里运行的输出结果是:
Address of pt is :0x12F698size of the struct :8Address of pt.x is :0x12F698Address of pt.y is :0x12F69CAddress of px is :0x12F698Address of py is :0x12F69C
可以看出struct的首地址与第一个成员变量的地址相同,而这个struct的长度是8个字节(=4+4).
2.fixed
虽然在unsafe模式下可以使用指针,但是unsafe的代码仍然是受管代码.CLR会对它的对象进行管理以及垃圾回收,CLR在这个过程中就会对内存进行重定位,可能过一段时间后,根据指针指向的地址就找不到原来的对象了,岂不是说指针在c#里没有什么实际的作用?别急,还有fixed.
fixed 语句设置指向托管变量的指针并在fixed里的语句块执行期间“锁定”该变量(或者是几个变量)。如果没有 fixed 语句,则指向托管变量的指针将作用很小,因为垃圾回收可能不可预知地重定位变量。(实际上,除非在 fixed 语句中,否则 C# 不允许设置指向托管变量的指针。)
看一段与刚才类似的代码,不同的地方是这里的输出地址的语句都在fixed块里.为什么不直接像第一个例子那样直接输出呢?这是因为我们对Point进行了一个小小的改动,它不再是struct了,它现在是class!它是托管类型了,它的成员都没有固定的地址.但是在fixed块里,它的地址是固定的.
class Point { public static int x; public int y; } public static unsafe void Main() { Point pt = new Point(); int[] arr = new int[10]; //如果不用fixed语句,无论是静态成员还是实例成员,都将无法取得其地址。 //int* ps = &CPoint.StaticField; //PrintAddress(ps); fixed (int* p = &Point.x) Console.WriteLine("Address is 0x{0:X}",(int)p); fixed (int* p = &pt.y) Console.WriteLine("Address is 0x{0:X}",(int)p); fixed (int* p1 = &arr[0],p2 = arr) { Console.WriteLine("Address is 0x{0:X}",(int)p1); Console.WriteLine("Address is 0x{0:X}",(int)p2); } Console.ReadLine(); }
我这里运行的输出结果是:
Address is 0x3D5404Address is 0x4BF1968Address is 0x4BF1978Address is 0x4BF1978
3.分配内存
在堆栈上分配内存
c#提供stackalloc ,在堆栈上而不是在堆上分配一个内存块,语句为 type * ptr = stackalloc type [ expr ];它的大小足以包含 type 类型的 expr 元素;该块的地址存储在 ptr 指针中。此内存不受垃圾回收的制约,因此不必使用fixed将其固定。此内存块的生存期仅限于定义该内存块的方法的生存期。如果内存空间不足,将会抛出System.StackOverflowException异常.
以下是一段示例程序(form msdn),
public static unsafe void Main() { int* fib = stackalloc int[100]; int* p = fib; *p++ = *p++ = 1; //fib[0]=fib[1]=1 for (int i=2; i<100; ++i, ++p) *p = p[-1] + p[-2];//fib[i]=fib[i-1]+fib[i-2]; for (int i=0; i<10; ++i) Console.WriteLine (fib[i]); Console.ReadLine();}
在堆上分配内存
既然有stackalloc,有没有heapalloc呢?答案是没有,c#没有提供这样的语法.想在堆上动态分配内存,只能靠自己想办法了.通过Kernel32.dll里的HeapAlloc()和HeapFree()可以达到这个目的.看下面的代码:
using System;using System.Runtime.InteropServices;public unsafe class Memory{ const int HEAP_ZERO_MEMORY = 0x00000008;//内存起始地址 //获得进程堆的句柄 [DllImport("kernel32")] static extern int GetProcessHeap(); //内存分配 [DllImport("kernel32")] static extern void* HeapAlloc(int hHeap, int flags, int size); //内存释放 [DllImport("kernel32")] static extern bool HeapFree(int hHeap, int flags, void* block); static int ph = GetProcessHeap();//获得进程堆的句柄 private Memory() {} public static void* Alloc(int size) //内存分配 { void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size); if (result == null) throw new OutOfMemoryException(); return result; } public static void Free(void* block) //内存释放 { if (!HeapFree(ph, 0, block)) throw new InvalidOperationException(); }}class Test{ unsafe static void Main() { byte* buffer = (byte*)Memory.Alloc(256); for (int i = 0; i < 256; i++) buffer[i] = (byte)i; for (int i = 0; i < 256; i++) Console.WriteLine(buffer[i]); Memory.Free(buffer); Console.ReadLine(); }
}
C#中调用非托管DLL的API详解:http://hi.baidu.com/%DA%E4%C4%B0%B0%EB%D4%B5%BE%FD/blog/item/54b9b63dcce956e83d6d97ad.html
C#非托管代码怎么用(以关机程序为例):http://hi.baidu.com/%DA%E4%C4%B0%B0%EB%D4%B5%BE%FD/blog/item/dbd743c2f76add1d0ef477da.html
将C++托管扩展项目从纯粹的中间语言转换成混合模式:http://www.vckbase.com/document/viewdoc/?id=1403
细致解说C#里使用指针:http://www.va1314.com/bcsl/?viewthread-9508.html
应用程序域
在.NET平台下,程序集并没有直接承载在进程中(而传统的win32程序是直接承载的)。实际上.NET可执行程序承载在进程的一个逻辑分区中,术语称为应用程序域(也称AppDomain)。可见,一个进程可以包含多个应用程序域,每一个应用程序域中承载一个.NET可执行程序,这样的好处如下:
应用程序域是.NET平台操作系统独立性的关键特性。这种逻辑分区将不同操作系统加载可执行程序的差异抽象化了。
和一个完整的进程相比,应用程序域的CPU和内存占用要小的多。因此CLR加载和卸载应用程序域比起完整的进程来说也快的多。
应用程序域为承载的应用程序提供了深度隔离。如果进程中一个应用程序域失败了,其他的应用程序域也能保持正常。
源文档 <http://www.cnblogs.com/longer/archive/2009/02/21/1395276.html>
- C#编写不安全代码 和应用程序域
- C#编写不安全代码初探
- 使用C#编写不安全代码(翻译)
- C#的不安全代码和指针
- C# 不安全代码
- C# 不安全代码
- C#梳理【不安全代码】
- c#不安全代码之指针
- C# 之不安全代码(1)
- C#中使用不安全代码
- c# 中使用不安全代码操作指针
- C#动态内存分配《不安全代码》
- C#基础—不安全代码(unsafe code)
- C#基础之unsafe code(不安全代码)
- 25 C# 第二十章 平台互操作性和不安全的代码
- C#托管和非托管的资源(五)——不安全的代码
- C#和JAVA中编写事务代码
- C#中编写多线程应用程序
- 统计一个指定文件夹下的文件数目。
- myeclipse注册码 算法
- 取模MOD和求余REM……JAVA的%原来只是REM
- [JAVA]判断字符串是否为乱码
- sqlserver2005安装时报性能监视器计数器错误
- C#编写不安全代码 和应用程序域
- Activity详细介绍及其生命周期
- OpenXml 生成Office Word的 擴展類
- ttOptEstimateStats用法
- S2SH整合
- hdu 3884 Hinanai Tenshi’s peach garden
- 模块编译时出现not in prelink map错误
- 各大硬件官方网站
- C# GZipStream 压缩 解压