C# 类与结构的区别

这个问题可能很多初学者都知道,用C#写了这么久的程序,类和结构用了很久却并不知道他们之间的本质区别。 在很多时候,他们只是声明的时候关键字的不同,一个是class,一个是struct而已。 本质上的区别在于: 类 类是属于引用类型,引用类型是在堆上面分配地址。 类对象之间的赋值是通过复制引用实现的。 除非明确的对类进行了sealed,否则类是可以继承其他类和接口且本身也可被继承。 内部结构区别: 有默认的构造函数。 有析构函数。 可以使用abstract和sealed。 有protected修饰符。 必须使用new初始化。 结构 结构属于值类型,值类型在堆栈上分配地址。 所有基类型都是结构类型,如int 对应的是System.int32结构。 堆栈的执行效率更高,但资源有限,不适合处理逻辑复杂的对象。 结构体对象之间赋值是创建新的结构。 不能从另外的结构继承,本身也不能够被继承 内部结构区别: 没有默认的构造函数,但是能够添加构造函数。 没有abstract和sealed。 不能有protected修饰符。 可以不使用new初始化。 在结构初始化实例字段是错误的。 谈了这么多,那么什么时候使用类,什么时候使用结构体呢? 堆栈空间有限,对于有大量逻辑的对象,使用类比结构体好一些。 在表示抽象和多级别层次的对象时,使用类是更好的选择。 在大多数情况下对象的类型只是数据时,使用结构是最好的选择。

WinForm 在线程中启动新的窗口

由于工程需要在线程当中加载一个新的WinForm窗口,但是直接在线程中加载会立即闪退,我认为是线程执行完成之后自动销毁了new的窗口。 1 new Thread(()=>{new Form2().Show();}).Start(); 之后谷歌了一番,有人提到使用异步委托在线程中来加载新的窗口: 1 new Thread(()=>{BeginInvoke(new MethodInvoker(()=> { new Form2().Show(); }));}).Start(); 使用如上方法能够正常运行。

C# 实现插件功能

在平常的应用开发过程当中,插件化的软件更容易维护与扩展,插件作者只需要遵循指定的接口规范即可快速为软件添加新的功能。而在C#当中我们更能够轻松实现上述的功能,首先我们定义一个软件与插件所遵循的接口: 1 2 3 4 public interface IPlug { public int Func1(int a,int b); } 之后我们新建一个类库,用于实现我们的插件功能,我们仅需要在类库中实现上述接口即可。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /*加法插件*/ public class addclass : IPlug { public int Func1(int a,int b) { return a+b; } } /*减法插件*/ public class subclass : IPlug { public int Func1(int a,int b) { return a-b; } } 现在我们有两个插件了,那么我们怎么才能在主程序当中动态加载这些插件并且调用呢? 这里我们就需要用到C#的反射特性了,通过反射我们能够知道一个对象他有什么字段,有什么方法。 我们来编写一个插件管理类,叫做PlugManager. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public static class PlugManager { // 用于存放加载成功的插件 public static List<IPlug> m_PlugsList = new List<IPlug>(); public static int Load() { m_PlugList.

用户态与内核态

该文章转载自http://my.oschina.net/liubin/blog/27795 究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分时候我们在写程序时关注的重点和着眼的角度放在了实现的功能和代码的逻辑性上,先看一个 1.例子: 1 2 3 4 5 6 7 8 9 void testfork() { if(0 == fork()) { printf("create new process success!\n"); } printf("testfork ok\n"); } 这段代码很简单,从功能的角度来看,就是实际执行了一个fork(),生成一个新的进程,从逻辑的角度看,就是判断了如果fork()返回的是则打印相关语句,然后函数最后再打印一句表示执行完整个testfork()函数。代码的执行逻辑和功能上看就是如此简单,一共四行代码,从上到下一句一句执行而已,完全看不出来哪里有体现出用户态和进程态的概念。 如果说前面两种是静态观察的角度看的话,我们还可以从动态的角度来看这段代码,即它被转换成CPU执行的指令后加载执行的过程,这时这段程序就是一个动态执行的指令序列。而究竟加载了哪些代码,如何加载就是和操作系统密切相关了。 2.特权级 熟悉Unix/Linux系统的人都知道,fork的工作实际上是以系统调用的方式完成相应功能的,具体的工作是由sys_fork负责实施。其实无论是不是Unix或者Linux,对于任何操作系统来说,创建一个新的进程都是属于核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等,这些显然不能随便让哪个程序就能去做,于是就自然引出特权级别的概念,显然,最关键性的权力必须由高特权级的程序来执行,这样才可以做到集中管理,减少有限资源的访问和使用冲突。 特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查,相关的概念有 CPL、DPL和RPL,这里不再过多阐述。硬件已经提供了一套特权级使用的相关机制,软件自然就是好好利用的问题,这属于操作系统要做的事情,对于 Unix/Linux来说,只使用了0级特权级和3级特权级。也就是说在Unix/Linux系统中,一条工作在级特权级的指令具有了CPU能提供的最高权力,而一条工作在3级特权级的指令具有CPU提供的最低或者说最基本权力。 3.用户态和内核态 现在我们从特权级的调度来理解用户态和内核态就比较好理解了,当程序运行在3级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运行在级特权级上时,就可以称之为运行在内核态。 虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,比如上面例子中的testfork()就不能直接调用 sys_fork(),因为前者是工作在用户态,属于用户态程序,而sys_fork()是工作在内核态,属于内核态程序。 当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如testfork()最初运行在用户态进程下,当它调用fork()最终触发 sys_fork()的执行时,就切换到了内核态。 4.用户态与内核态切换的三种方式 系统调用 这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。 异常 当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。 外围设备中断 当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。 这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。 5.具体的切换操作 从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,关于它们的具体区别这里不再赘述。关于中断处理机制的细节和步骤这里也不做过多分析,涉及到由用户态切换到内核态的步骤主要包括: 从当前进程的描述符中提取其内核栈的ss0及esp0信息。 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始 执行中断处理程序,这时就转到了内核态的程序执行了。

mmap 的使用浅解

mmap是Unix提供的一个有用的功能,这个功能允许程序共享内存,也被称为内存映射。这个函数的作用是建立一段可以被两个或者更多个程序读写的内存,一个程序对它的修改可以被其他程序所看见。 这个功能经常被用于读写硬盘上的文件,mmap可以将文件映射到内存,这样你可以更加方便的对文件内容进行更新操作。 mmap函数原型: 1 2 3 #include <sys/mman.h> void *mmap(void *addr,size_t len,int prot,int flags,int fildes,off_t off); addr:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。 len:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理 prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过位或运算合理地组合在一起 PROT_EXEC //页内容可以被执行 PROT_READ //页内容可以被读取 PROT_WRITE //页可以被写入 PROT_NONE //页不可访问 flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体 MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。 MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到**msync()**或者**munmap()*被调用,文件实际上*不会被更新。 MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。 MAP_DENYWRITE //这个标志被忽略。 MAP_EXECUTABLE //同上 MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。 MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。 MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。 MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。 MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。 MAP_FILE //兼容标志,被忽略。 MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。 MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。 MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。 fildes:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。 off:被映射对象内容的起点。 你可以对addr参数来请求使用某个特定的地址,如果他的取值为零将会自动进行分配,这是一种推荐的做法否则会降低程序的可移植性,因为在不同的系统上的可用地址范围是不一样的。 下面我们介绍第二个函数,他的作用是把某个部分或整段中的修改写回被映射的文件中。(或者从映射文件中读出) 1 2 #include <sys/mman.h> int msync(void *addr,size_t len,int flags); addr:文件映射到进程空间的地址;
Built with Hugo
主题 StackJimmy 设计