日志分类:WIN程序开发

如果你想学好编程!就一定要看这个!对无论有多厉害的你有很多帮助!

时间:2014年03月06日作者:小侃评论次数:0

此处仅以C#为例,其他编程语言也同样适用。

把C#当成一门新的语言学习;  
.看《C#入门经典》和《C#高级编程》;  
.不要被VC、BCB、BC、MC、TC等词汇所迷惑——他们都是集成开发环境,而我们要学的是一门语言;  
.不要放过任何一个看上去很简单的小编程问题——他们往往并不那么简单,或者可以引伸出很多知识点  
.会用Visual vs,并不说明你会C#;  
.学c#并不难,长期坚持实践和不遗余力的博览群书;  
.如果不是天才的话,想学编程就不要想玩游戏! 
.看Visual vs的书,是学不了C#语言的;  
.浮躁的人容易说:XX语言不行了,应该学YY;——是你自己不行了吧!?  
.浮躁的人容易问:我到底该学什么;——别问,学就对了;  
.浮躁的人容易问:XX有钱途吗;——建议你去抢银行;  
.浮躁的人容易问:XX和YY哪个好;——告诉你吧,都好——只要你学就行;  
.浮躁的人分两种:a)只观望而不学的人;b)只学而不坚持的人;  
.把时髦的技术挂在嘴边,还不如把过时的技术记在心里;  
.C#不仅仅是支持面向对象的程序设计语言;  
.学习编程最好的方法之一就是阅读源代码;  
.在任何时刻都不要认为自己手中的书已经足够了;  
.看得懂的书,请仔细看;看不懂的书,请硬着头皮看;  
.别指望看第一遍书就能记住和掌握什么——请看第二遍、第三遍;  
.和别人一起讨论有意义的C#知识点,而不是争吵XX行不行或者YY与ZZ哪个好;  
.请不要认为学过XX语言再改学C#会有什么问题——你只不过又在学一门全新的语言而已;  
.读完了《C#高级编程》以后再来认定自己是不是已经对C#入门了;  
.学习编程的秘诀是:编程,编程,再编程;  
.记住:面向对象技术不只是C#专有的;  
.请把书上的程序例子亲手输入到电脑上实践,即使配套光盘中有源代码;  
.把在书中看到的有意义的例子扩充;  
.请重视C#中的异常处理技术,并将其切实的运用到自己的程序中;  
.经常回顾自己以前写过的程序,并尝试重写,把自己学到的新知识运用进去;  
.不要漏掉书中任何一个练习题——请全部做完并记录下解题思路;  
.C#语言和C#的集成开发环境要同时学习和掌握;  
.既然决定了学C#,就请坚持学下去,因为学习程序设计语言的目的是掌握程序设计技术,而程序设计技术是跨语言的;  
.就让C#语言的各种平台和开发环境去激烈的竞争吧,我们要以学习C#语言本身为主;  
.当你写C#程序写到一半却发现自己用的方法很拙劣时,请不要马上停手;请尽快将余下的部分粗略的完成以保证这个设计的完整性,然后分析 

自己的错误并重新设计和编写;  
.决不要因为程序“很小”就不遵循某些你不熟练的规则——好习惯是培养出来的,而不是一次记住的;  
.每学到一个C#难点的时候,尝试着对别人讲解这个知识点并让他理解——你能讲清楚才说明你真的理解了;  
.记录下在和别人交流时发现的自己忽视或不理解的知识点;  
.请不断的对自己写的程序提出更高的要求,哪怕你的程序版本号会变成XX;  
.保存好你写过的所有的程序——那是你最好的积累之一;  
.请不要做浮躁的人;  
.请热爱C#! 

深入解析和破解C#的readonly只读字段及与const的区别

时间:2014年02月28日作者:小侃评论次数:0

 

 

 

 

返回目录

请允许我再唠叨几句const和readonly

readonly 关键字与 const 关键字不同。 const 字段只能在该字段的声明中初始化。 readonly 字段可以在声明或构造函数中初始化。 因此,根据所使用的构造函数,readonly 字段可能具有不同的值。 另外,const 字段为编译时常数,而 readonly 字段可用于运行时常数。

其实大家都懂的,我就不多废话,直接重点:

Const:
1>必须在定义时就进行赋值,并且赋值为常量,不能赋值为变量。
2>const常量隐含是static的,所以只能用类名来进行方法.
3>常量的类型可以为简单类型,枚举类型,各种引用类型(包括string)。这里引用类型的常量,如果不是string类型,则只允许赋以null。常量的类型不能为普通的struct类型。如果要实现struct类型的保持不变的量,可以用readonly。
4>const可以修饰方法内的局部变量。
 
ReadOnly:
1>readonly域可以是各种类型。
2>readonly域可以用变量或表达式进行赋值
3>readonly不能修饰局部变量。
4>readonly域并不隐含static性质。
5>readonly域要求定义时初始化。如果没有初始化,自动取默认值(0,false,null等)。如果要赋值,可以定义时初始化,也可以在构造方法中赋值,包括使用out及ref参数进行处理。但它最多只能被赋值一次。赋值以后,其值不能被修改。
 
可以看出,readonly要求的只读性时考虑”运行时时只读的”,而const要求的只读性是”编译时就是常量”。

 

好了,下面开始修改readonly字段。

 

 

返回目录

计策1:反间计 —— 反射修改

要说什么方法不用我们另外做手脚直接了当快速轻松得修改readonly只读属性莫过于使用.NET中的正规方式,当你直接对readonly字段进行“=”赋值操作然后编译,VS肯定给你个错误拒绝编译。但是另一种动态赋值方式——反射你有没有试过呢???

 

好吧或许你曾经问过某高手说readonly字段能不能被修改,他一定会回答:“笨蛋,这还用问,能修改还叫readonly吗?”。于是聪明人知道了并记住了(或许聪明人根本不会问这样的问题)。但是……一个笨的很笨的小笨他太笨了所以还是想试试,于是先用第一种直接静态赋值方式,VS编译错误。又尝试另一种运行后的动态赋值,于是他成功了!

 

是的,静态方式编译器不允许,但是动态赋值——反射却背叛了它,运行后用反射想怎么改就怎么改!

看下面代码:

class Program

{

static readonly object data = 1;

 

static void Main()

{

Console.WriteLine(data);

mod();

Console.WriteLine(data);

}

 

static void mod()

{

//得到静态的非公有字段信息

var fieldInfo = typeof(Program).GetField(“data”, BindingFlags.Static | BindingFlags.NonPublic);

fieldInfo.SetValue(null, “MOD”);

}

}

 

 

输出:

1

MOD

 

值被成功修改了。

 

 

返回目录

计策2:借刀杀人——调节字段偏移位置的结构体来修改

如果你经常用C#调用非托管代码的话,P/Invoke和各种Marshal操作你会很熟悉。而另一个常用到的System.Runtime.InteropServices的StructLayout和FieldOffset特性是用来定义一个显示的字段内存布局的结构体。

 

没人可以阻止我们把两个字段对在一个位置,然后用一个非readonly字段去彻底控制一个readonly字段!!!

 

代码

using System;

using System.Runtime.InteropServices;

 

namespace Mgen.TTC

{

[StructLayout(LayoutKind.Explicit)]

struct a

{

[FieldOffset(0)]

public string s;

[FieldOffset(0)]

public readonly string READ_ONLY;

//readonly字段

 

public a(string arg)

{

//初始化readonly字段

s = READ_ONLY = arg;

}

}

 

class Program

{

static void Main()

{

//此时oa的readonly字段被初始化成字符串:”常量”

a oa = new a(“常量”);

 

Console.WriteLine(oa.READ_ONLY);

oa.s = “你被改了……”;

Console.WriteLine(oa.READ_ONLY);

}

}

}

输出:

常量

你被改了……

 

很好,这里修改变量s就等价于修改常量字段:READ_ONLY。两个字段是对在同一个内存位置的,然后通过修改一个非readonly字段去间接修改一个readonly字段。

 

 

 

 

返回目录

计策3:无中生有——使用ilasm创建强行修改语句

当你使用Visual Studio(或其他C#编译器)时对readonly字段进行赋值肯定结果是编译错误。于是你很想强行通过这条语句(于是你又非常使劲得按了下Ctrl + F5,结果……),好吧C#编译器都一个样,无法去改变它,但是C#幕后的IL并非如此。接下来让我们用ilasm.exe创建一个对readonly字段进行直接赋值的程序。

ilasm的编译需要一个IL文件,这个IL文件就是文本文件,你可以自己写IL,不过这个太麻烦了,我们可以用IL反编译程序ildasm.exe来把一个托管程序集的IL代码框架先生成,然后导出,最后加入自己的代码再用ilasm编译。

 

首先创建一个C#控制台程序,然后加一个readonly只读字段,编译(这样做我们就不用从头开始写IL)。如下:

class Program

{

static readonly string s = “hehe”;

static void Main(string[] args)

{ }

}

接着用ildasm.exe打开这个程序。

(ildasm在Windows SDK中,Windows SDK路径在:%PROGRAMFILES%\Microsoft SDKs\Windows)

 

在File菜单选择Dump(或直接按Ctrl+D)。选择OK将IL文件保存。

 

然后用文本编辑器打开这个IL文件,找到Main函数(可以通过查找文字:Main)。在主函数.entrypoint指令下加入下列指令:

代表4条C#语句:输出s,修改s,再输出s,Console.Read()。

(注意下面的ConsoleApplication1.Program::s代表s字段的完整命名空间,根据你的程序做适当修改)

.maxstack  8

IL_0000:  ldsfld     string ConsoleApplication1.Program::s              //s进栈

IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)   //输出s

IL_000a:  ldstr      bytearray (AB 88 EE 4F 39 65 )                     //字符串被修改”

IL_000f:  stsfld     string ConsoleApplication1.Program::s              //将”被修改”赋值给s

IL_0014:  ldsfld     string ConsoleApplication1.Program::s              //s进栈

IL_0019:  call       void [mscorlib]System.Console::WriteLine(string)   //输出s

IL_001e:  call       int32 [mscorlib]System.Console::Read()             //Console.Read()

IL_0023:  pop

IL_0024:  ret

 

接下来就用ilasm.exe来编译IL文件。

(ilasm在.NET Framework安装目录下,路径:%SYSTEMROOT%\microsoft.net\framework\)

 

将刚才修改后的IL文件路径作为第一个参数传入ilasm.exe中,ilasm便会将IL编译成可执行文件。然后运行这个exe。

 

程序会输出:

hehe

被修改

 

你可以在Reflector(或ILSpy)中打开这个程序,它的C#代码正好是C#编译器所不允许编译的结果:

internal class Program

{

// Fields

private static readonly string s = “hehe”;

 

// Methods

private static void Main(string[] args)

{

Console.WriteLine(s);

s = “被修改”;

Console.WriteLine(s);

Console.Read();

}

}

 

 

 

 

返回目录

为什么?——翻阅CLI标准:initonly修饰符

 

注意:

下文会引用Common Language Infrastructure (CLI) 标准的内容,如果你想亲自看一下CLI标准的内容。可以在这里下载:http://www.ecma-international.org/publications/standards/Ecma-335.htm

 

 

看完了上面的东西,你一定会问为什么?为什么执行环境没有去有效得保护一个readonly字段?为什么IL允许去修改一个readonly字段而C#不可以?

 

让我们开始分析一个readonly字段。如下C#代码:

readonly int i;

 

接下来,你应该猜到了,又开始IL了……

(汗……别抱怨怎么又得从IL上说起,我也不想这样做,其实IL也挺无奈的,人家很低调的,但总被一些人给不停得揭露出来……仿佛他们研究得很深奥似的……(我在自嘲,没说别人))

 

上面代码会被编译成下面的IL:

.field private initonly int32 i

 

可以看出来,readonly字段就是一个带有initonly的字段,一切的不同就在这个initonly修饰符上。

 

根据CLI标准(ECMA-334),第二部分:元数据(Partition II Metadata),16. 定义和引用字段(16 Defining and referencing fields),1. 字段的属性(Attributes of field),2. 字段约定属性(Field contract attributes)中对initonly修饰符的描述

initonly marks fields which are constant after they are initialized. These fields shall only be mutated inside a constructor. If the field is a static field, then it shall be mutated only inside the type initializer of the type in which it was declared. If it is an instance field, then it shall be mutated only in one of the instance constructors of the type in which it was defined. It shall not be mutated in any other method or in any other constructor, including constructors of derived classes.

[Note: The use of ldflda or ldsflda on an initonly field makes code unverifiable.  In unverifiable code, the VES need not check whether initonly fields are mutated outside the constructors. The VES need not report any errors if a method changes the value of a constant. However, such code is not valid. end note]

上面第一段就是在说readonly的用法:initonly字段在初始化后就成为常量。这种字段只能在构造函数中改变(进行赋值)。如果是静态字段,只能在当前类静态构造函数中或初始设定项。如果是非静态字段,只能在当前类的非静态构造函数中使用(或类型初始设定项),不能在其他方法或者其他构造函数中,包括派生类的构造函数。

 

第二段找到了重点(以Note开始)。对initonly字段使用ldflda和ldsflda会使代码无法验证。在无法通过验证的代码环境中,VES不需要检查initonly字段是否在构造函数外发生改变,VES也不需要报错如果一个方法改变了这个只读字段的值。但是,这种代码不是合法的。

 

其中VES(Virtual Execution System):虚拟执行系统是指代码的运行环境,而微软的CLI执行就是CLR。

 

IL指令:ldflda(ldsflda用于静态字段)用于将字段的地址进栈。当使用ref/out关键字时,或者调用值类型的成员时,CLR都会把相应对象的地址压入栈中。(其实上面提到ldflda/ldsflda是指去修改readonly字段,所以不一定只限于ldflda/ldsflda,当然也没准其他IL指令会间接用到ldflda/ldsflda)

 

OK,那么其实CLI标准没有强制要求执行环境必须要确保readonly只读字段必须遵守行为准则,只要readonly字段被非法修改,代码将会无法验证(unverifiable)。下面的问题就是无法验证的代码又是一个什么概念?

 

 

 

返回目录

捕获MSIL的代码验证错误

当MSIL被JIT编译时,如果程序集没有被赋予跳过代码验证的权限时,那么代码必须要通过代码验证过程,代码验证直接确保了程序的类型安全,使得对象可以绝对隔离并且免受恶意破坏,代码验证所要做的是如下具体3点:

  • 对类型的引用与被引用的类型严格兼容。
  • 在对象上只调用正确定义的操作。
  • 标识与声称的要求一致。

(强烈建议阅读MSDN – 托管执行过程 – 将 MSIL 编译为本机代码 – 代码验证(http://msdn.microsoft.com/zh-cn/library/k5532s8a.aspx))

 

上面提到的“跳过代码验证的权限”就是SecurityPermissionFlag.SkipVerification枚举值,而本机运行的.NET程序集默认是被给予全部权限的,因此会包含这个权限。IL代码验证是不发生的。接下来要做的就是通过创建一个没有“跳过代码验证的”应用程序域,在这个应用程序域中运行上面用IL创建的那个修改只读字段的程序集,看看会发生什么情况。

 

创建另一个控制台应用程序(最好把刚才的IL编译的修改只读字段的程序拷贝到此程序的编译目录下,这样可以用相对路径),然后创建一个应用程序域,并执行刚才的程序集:

static void Main()

{

//创建一个没有跳过代码验证权限的应用程序域

var appdomain = CreateSandbox();

//testApp.exe是刚才用IL编译的修改readonly只读字段的程序的路径

appdomain.ExecuteAssembly(“testApp.exe”);

}

 

static AppDomain CreateSandbox()

{

//初始化空的权限集

var pset = new PermissionSet(PermissionState.None);

//添加Execution(执行)安全权限,没有此权限代码不会执行

pset.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

//添加其他权限

pset.AddPermission(new FileIOPermission(PermissionState.Unrestricted));

pset.AddPermission(new UIPermission(PermissionState.Unrestricted));

 

 

//必须设置AppDomainSetup.ApplicationBase,否则无法成功创建应用程序域

var adSetup = new AppDomainSetup();

adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

 

var domain = AppDomain.CreateDomain(“新的AppDomain”,

null,

adSetup,

pset);

 

return domain;

}

 

 

运行结果:有异常抛出。很好,这正是我们想要的结果:

Unhandled Exception: System.Security.VerificationException: Operation could dest

abilize the runtime.

at ConsoleApplication1()      //用IL编译的程序的主函数

at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args

)

at System.AppDomain.ExecuteAssembly(String assemblyFile, String[] args)

at System.AppDomain.ExecuteAssembly(String assemblyFile)

at System.AppDomain.ExecuteAssembly(String assemblyFile)

at Mgen.TTC.Program.Main()    //当前程序的主函数

可以看到System.Security.VerificationException异常被抛出。”operation could destabilize the runtime”:指操作可能使运行时刻不稳定。因为这个用IL编译的程序集包含上面提到过的“无法验证”的代码,而当前执行的应用程序域又没有被给予“跳过代码验证的安全权限”,因此代码验证此刻会发生,当程序集运行时,JIT边编译边进行代码验证,而觉察到这个“修改只读属性”的语句,它打破了代码验证所强制的运行时刻类型安全,显然破坏了运行时刻稳定性,接着马上抛出VerificationException异常,程序集执行失败。

 

 

如果你把上面的应用程序域的限制权限改成全部权限:

var pset = new PermissionSet(PermissionState.Unrestricted);

重新运行一下程序,这次没有任何异常抛出,另一个程序集的只读字段会继续被修改并输出正确结果。

hehe

被修改

 

 

 

 

返回目录

总结

如果你仔细看了文章,那么“总结”对你来意义不大,因为你肯定已经自己理解了。当然如果你没时间全看完,总结告诉你的是(请从上往下看):

  • readonly对象本质上不是常量,是字段。
  • 标准(CLI标准)没有规定readonly字段不能被修改。
  • 因为标准不强求,更何况readonly对象是字段,所以IL和反射都可以修改它。
  • 当然编译器还是要尽量阻止它被修改(如果编译器都允许它被修改,那readonly这个关键字还有什么用?)
  • 一旦readonly字段被修改了,该代码就属于无法验证代码。
  • MSIL在被JIT编译时会验证代码,如果遇到无法验证代码会抛出异常。
  • 当然有专门的权限可以使程序代码跳过(可以理解成不进行)代码验证。
  • 默认情况下本机程序拥有所有权限,所以修改readonly字段没有异常。当在部分信任的环境下程序集可以收到System.Security.VerificationException代表代码验证异常。

 

C#高级编程:Invalidate()方法学习

时间:2014年02月21日作者:小侃评论次数:0

Invalidate()是System.Windows.Forms.Form的一个成员,它把客户窗口区域标记为无效,因此在需要重新绘制时,它可以确保引发Paint事件。Invalidate()有两个重载方法:可以给它传送一个矩形,指定(使用页面坐标)需要重新绘制哪个窗口区域,如果不提供任何参数,它就把整个客户区域标记为无效。

为什么要这么做如果知道需要绘制某些内容,为什么不调用OnPaint()或直接完成绘制任务的其他方法一般情况下,最好不要直接调用绘图例程,如果代码要完成某些绘图任务,此时一般应调用Invalidate()。其原因如下所示:

 

● 绘图总是GDI+应用程序可以执行的一种处理器密集型的任务。在其他工作的中间进行绘图会妨碍其他工作的进行。在前面的示例中,如果在LoadFile()方法中直接调用一个方法来完成绘图,LoadFile()方法就将在绘图工作完成后才能返回。在这段时间里,应用程序不会响应其他事件。另一方面,通过调用Invalidate(),在从LoadFile返回之前,就可以让Windows引发一个Paint事件。接着Windows就可以检查等待处理的事件了。其内部的工作方式是事件被当作消息队列中一个消息。Windows会定期检查该队列,如果其中有事件,Windows就选择它,并调用相应的事件处理程序。现在Paint事件是队列中的惟一事件,所以OnPaint()会被立即调用。但是,在一个比较复杂的应用程序中,可能会有其他事件,其中一些的优先权比OnPaint()高。特别是如果用户已决定退出应用程序,该事件就会用消息WM_QUIT来标记。

● 与第一个原因相关,如果有一个比较复杂的多线程应用程序,就会希望用一个线程处理所有的绘图操作。使用Invalidate()可以把所有的绘图操作传递到信息队列中,这有助于确保无论其他线程请求什么绘图操作,都由同一个线程完成所有的绘图操作(无论什么线程负责信息队列,都是由线程Application.Run()处理绘图操作)。

● 还有一个与性能有关的原因。假定在某一时刻有几个不同的屏幕绘制请求,也许代码仅能修改文档,以确保显示更新的文档,而同时用户刚刚移开另一个覆盖部分客户区域的窗口。调用Invalidate(),可以让Windows注意到发生的事件。Windows就会在需要时合并Paint事件,合并无效的区域,这样绘图操作就只执行一次。

● 最后,执行绘图的代码可能是应用程序中最复杂的代码部分,特别是当有一个比较专业化的用户界面时,就更是如此。需要长时间维护该代码的人员希望我们把所有的绘图代码都放在一个地方,且尽可能简单—— 如果程序的其他部分没有过多的路径进入该代码部分,维护就更容易。

其底线是最好把所有的绘图代码都放在OnPaint()例程中,或者在该方法中调用的其他方法中。但是要维持一个平衡。如果要在屏幕上替换一个字符,最好不要影响到已经绘制好的其他内容,此时可能不需要使用Invalidate(),而只需编写一个独立的绘图例程。

注意:

在非常复杂的应用程序中,甚至可以编写一个完整的类,专门负责在屏幕上绘图。几年前MFC仍是GDI密集型应用程序的标准技术,MFC就遵循这个模式,使用一个C++类CView完成绘图操作。但即使是这样,这个类也有一个成员函数OnDraw(),用作大多数绘图请求的入口点。

转自:http://www.tzwhx.com/newOperate/html/1/12/124/10475.html

C#中Invalidate() 方法

 

重载列表

使控件的特定区域无效并向控件发送绘制消息。

 

受 .NET Framework 精简版的支持。

 

[C#] public void Invalidate();[C++] public: void Invalidate();使控件的特定区域无效并向控件发送绘制消息。还可以使分配给该控件的子控件无效。

 

[C#] public void Invalidate(bool);使控件的指定区域无效(将其添加到控件的更新区域,下次绘制操作时将重新绘制更新区域),并向控件发送绘制消息。

 

受 .NET Framework 精简版的支持。

 

[C#] public void Invalidate(Rectangle);使控件的指定区域无效(将其添加到控件的更新区域,下次绘制操作时将重新绘制更新区域),并向控件发送绘制消息。

 

[C#] public void Invalidate(Region);使控件的指定区域无效(将其添加到控件的更新区域,下次绘制操作时将重新绘制更新区域),并向控件发送绘制消息。还可以使分配给该控件的子控件无效。

 

[C#] public void Invalidate(Rectangle, bool);使控件的指定区域无效(将其添加到控件的更新区域,下次绘制操作时将重新绘制更新区域),并向控件发送绘制消息。还可以使分配给该控件的子控件无效。

 

[C#] public void Invalidate(Region, bool);示例

[Visual Basic, C#, C++] 下面的示例使用户能够将图像或图像文件拖到窗体上,并使它在放置点显示。每次绘制窗体时,都重写 OnPaint 方法以重新绘制图像;否则图像将保持到下一次重新绘制。DragEnter 事件处理方法决定拖到窗体中的数据的类型,并提供适当的反馈。如果 Image 可以从该数据中创建,则 DragDrop 事件处理方法就会在该窗体上显示此图像。因为 DragEventArgs.X 和 DragEventArgs.Y 值为屏幕坐标,所以示例使用 PointToClient 方法将它们转换成工作区坐标。

C#结构体类型基础详解

时间:2014年02月21日作者:小侃评论次数:0

结构是使用 struct 关键字定义的,与类相似,都表示可以包含数据成员和函数成员的数据结构。
一般情况下,我们很少使用结构,而且很多人也并不建议使用结构,但作为.NET Framework 一般型別系统中的一个基本架构,还是有必要了解一下的。
结构的特征:
结构是一种值类型,并且不需要堆分配。
结构的实例化可以不使用 new 运算符。
在结构声明中,除非字段被声明为 const 或 static,否则无法初始化。
结构类型永远不是抽象的,并且始终是隐式密封的,因此在结构声明中不允许使用abstract和sealed修饰符。
结构不能声明默认构造函数(没有参数的构造函数)或析构函数,但可以声明带参数的构造函数。
结构可以实现接口,但不能从另一个结构或类继承,而且不能作为一个类的基类,所有结构都直接继承自 System.ValueType,后者继承自 System.Object。
结构在赋值时进行复制。 将结构赋值给新变量时,将复制所有数据,并且对新副本所做的任何修改不会更改原始副本的数据。 在使用值类型的集合(如 Dictionary<string, myStruct>)时,请务必记住这一点。
结构类型的变量直接包含了该结构的数据,而类类型的变量所包含的只是对相应数据的一个引用(被引用的数据称为“对象”)。但是结构仍可以通过ref和out参数引用方式传递给函数成员。
结构可用作可以为 null 的类型,因而可向其赋 null 值。

struct A
    {
        public int x;           //不能直接对其进行赋值
        public int y;
        public static string str = null;   //静态变量可以初始化
        public A(int x,int y)   //带参数的构造函数
        {
            this.x = x;
            this.y = y;
            Console.WriteLine("x={0},y={1},str={2}", x, y,str);
        }

    }
    class Program
    {
        static void Main(string[] args)
        {
            A a =new A(1,2);
            A a1 = a;
            a.x =10;
            Console.WriteLine("a1.x={0}",a1.x);
            Console.Read();
        }
    }

结果为:x=1,y=2,str=
a1.x=1
此时a1.x值为1是因为,将a赋值给a1是对值进行复制,因此,a1不会受到a.x赋值得改变而改变。
但如果A是类,这时a和a1里的x引用的是同一个地址,则a1.x的值会输出10。
结构的装箱与拆箱
我们知道,一个类类型的值可以转换为object类型或由该类实现的接口类型,这只需在编译时把对应的引用当作另一个类型处理即可。
与此类似,一个object 类型的值或者接口类型的值也可以被转换回类类型而不必更改相应的引用。当然,在这种情况下,需要进行运行时类型检查。
由于结构不是引用类型,上述操作对结构类型是以不同的方式实现的。
当结构类型的值被转换为object 类型或由该结构实现的接口类型时,就会执行一次装箱操作。
反之,当 object 类型的值或接口类型的值被转换回结构类型时,会执行一次拆箱操作。
与对类类型进行的相同操作相比,主要区别在于:
装箱操作会把相关的结构值复制为已被装箱的实例,而拆箱则会从已被装箱的实例中复制出一个结构值。
因此,在装箱或拆箱操作后,对“箱”外的结构进行的更改不会影响已被装箱的结构。

struct Program
    {
        static void Main(string[] args)
        {
            int i =1;
            object o = i;    //隐式装箱
            i =123;
            Console.WriteLine("i={0},o={1}",i,o);
            Console.Read();
        }
    }
//结果为:i=123,o=1

结构与构造函数
我们知道结构不能使用默认的构造函数,只能使用带参数的构造函数,当定义带参数的构造函数时,一定要完成结构所有字段的初始化,如果没有完成所有字段的初始化,编译时会发生错误­。
结构可以使用静态构造函数吗?
可以,结构的静态构造函数与类的静态构造函数所遵循的规则大体相同。
结构的静态构造函数何时将触发呢?
结构的实例成员被引用,结构的静态成员被引用,结构显示声明的构造函数被调用。
但是创建结构类型的默认值不会触发静态构造函数。
为什么结构不能自定义无参数的构造函数?
结构类型的构造函数与类的构造函数类似,用来初始化结构的成员变量,但是struct不能包含显式默认构造函数,
因为编译器将自动提供一个构造函数,此构造函数将结构中的每个字段初始化为默认值表中显示的默认值。
然而,只有当结构用new实例化时,才会调用此默认构造函数。对值类型调用默认构造函数不是必需的。

    struct A
    {
        static A()
        {
            Console.WriteLine("I am A.");
        }
        public void Fun()
        {

        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            A a=new A();
            a.Fun();      //结构的实例成员被引用
            Console.Read();
        }
    }
结果为:I am A.

结构与继承:
一个结构声明可以指定实现的接口列表,但是不能指定基类。
由于结构不支持类与结构的继承,所以结构成员的声明可访问性不能是protected或protected internal。
结构中的函数成员不能是 abstract或 virtual,因而 override修饰符只适用于重写从System.ValueType继承的方法。
为在设计编程语言时将结构设计成无继承性?­
其实类的继承是有相当的成本的 ——由于继承性,每个类需要用额外的数据空间来存储“继承图”来表示类的传承历史,
通俗地说来就是我们人类的家族家谱,里面存储着我们的祖宗十八代,只有这样我们才知道我们从哪里来的,而家谱肯定是需要额外的空间来存放的。
大家不要觉得这个存放“继承图”的空间很小,如果我们的程序需要用10000个点(Point)来存放游戏中的人物形体数据的话,
在一个场景中又有N个人,这个内存开销可不是小数目了。所以我们可以通过将点(Point)申明成 Struct而不是class来节约内存空间。

interface ITest
    {
        void Fun(int x,int y);
    }
    struct A:ITest
    {
        public void Fun(int x,int y)    //隐式实现接口里的方法
        {
            Console.WriteLine("x={0},y={1}", x, y);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            A a;                        //结构的实例化可以不使用new
            a.Fun(1, 2);
            Console.Read();
        }
    }
// 结果为:x=1,y=2

什么情况下结构的实例化可以不使用new?

当结构中没有参数时,结构的实例化可以不使用new;

当结构中有参数时,必须对结构中所有参数进行初始化后,才能不使用new对结构进行实例化。

什么时候使用结构?

结构体适合一些小型数据结构,这些数据结构包含的数据以创建结构后不修改的数据为主;

例如:struct类型适于表示Point、Rectangle和Color等轻量对象。

尽管可以将一个点表示为类,但在某些情况下,使用结构更有效。

如果声明一个10000个Point对象组成的数组,为了引用每个对象,则需分配更多内存;这种情况下,使用结构可以节约资源。­

定义的时候不会用到面向对象的一些特性;

结构体在不发生装箱拆箱的情况下性能比类类型是高很多的.

.net中字符串的常用方法

时间:2014年02月17日作者:小侃评论次数:0

字符串重要的一些属性:
1Length:获得字符串中字符的个数。
举例如下:
01.<FONTface=微软雅黑>string str = “hello”;
02.Console.WriteLine(str.Length);
03.//输出结果为:5
04.//即该字符串长度为5</FONT>
复制代码二、常用方法
1IsNullOrEmpty():判断字符串是否为空或者为null
举例如下:
01.<FONTface=微软雅黑>string str = “”;
02.if(string.IsNullOrEmpty(str))
03.{
04.Console.WriteLine(“该字符串为空,或为null”);
05.}
06.else
07.{
08.Console.WriteLine(str);
09.}
10.//输出结果为:该字符串为空,或为null
11.//这里,stringstr=null,输出结果也为:该字符串为空,或为null
12.//注意:该方法为静态方法,调用时必须 类名.方法名 调用。</FONT>
复制代码
2ToCharrArry():将string转换成char数组
举例如下:
01.   string str = “hello”;
02.   char[] c = str.ToCharArray();
03.   c[1] = ‘a’;
04.   Console.WriteLine(new string(c));
05.//输出结果为:hallo
06.//因为字符串的不可改变性,要修改字符串中的某个字符,必须ToCharArray()拆分成一个char数组。
复制代码
3ToLower() :将一个字符串转换成小写。
举例如下:
01.stringstr = “HELLO”;
02.strings = str.ToLower();
03.Console.WriteLine(s);
04.//输出结果:hello
05.//注意:必须声明一个变量来接受返回值(s),因为字符串的不可改变性。
复制代码
4Equals():将一个字符换转换成大写
举例如下:
01.stringstr = “hello”;
02.strings = str.Equals();
03.Console.WriteLine(s);
04.//输出结果为:HELLO
复制代码
5Equals():比较两个字符串是否相同
举例如下:
01.strings1 =”hello”;
02.strings2 = “HELLO”;
03.if(s1.Equals(s2))
04.{
05.Console.WriteLine(“两个字符串一样“);
06.}
07.else
08.{
09.Console.WriteLine(“两个字符串不一样“);
10.}
11.//输出结果:两个字符串不一样
12.//因为大小写不同,所以两个字符串是不一样
13.
14.
15.strings1 =”hello”;
16.strings2 = “HELLO”;
17.if(s1.Equals(s2StringComparison.OrdinalIgnoreCase))
18.{
19.Console.WriteLine(“两个字符串一样“);
20.}
21.else
22.{
23.Console.WriteLine(“两个字符串不一样“);
24.}
25.//输出结果:两个字符串一样。
26.//这里我们使用了Equals的第二个重载,该重载第二个参数StringComparison.OrdinalIgnoreCase是一个枚举//值,用来忽略大小写。
复制代码
6IndexOf():检索字符串中指定的字符的索引。
举例如下:
01.stringstr = “大家好,我叫小明,我今年12岁了!大家要记住我叫小明哦!“;
02.int n= str.IndexOf(“小明“);
03.Console.WriteLine(n);
04.//输出结果为:6
05.//以为“小明”,即的索引6、“明”的索引为7,即“小明”的索引为67
06.
07.
08.stringstr = “大家好,我叫小明,小明今年12岁了!“;
09.int n= str.IndexOf(“小明“,7);
10.Console.WriteLine(n);
11.//输出结果为:9
12.//这里我们发现9真好是第二个“小明”的索引。因为这里我们调用了IndexOf(string value,int startIndex)这个重载,即 startIndex=检索开始的地方。
13.
14.
15.stringstr = “大家好,我叫小明,小明今年12岁了!“;
16.int n= str.IndexOf(“小胖“);
17.Console.WriteLine(n);
18.//输出结果为:-1
19.//即如果检索不到相应的数据,则返回值为:-1
20.
21.//注意:1IndexOf返回值为int类型
22.              2IndexOf共有8个重载,不仅仅只能传入一个字符串,还能是一个char,还能指定要检索字符的类型等。。。希望自己在使用的过程中都了解一下这些重载。
23.
复制代码
7LastIndexOf():检索字符串中指定字符最后一个的索引。
举例如下:
01.stringstr = “大家好,我叫小明,小明今年12岁了!“;
02.int n= str.LastIndexOf(“小明“);
03.Console.WriteLine(n);
04.//输出结果为:9
05.//这里直接忽略第一个小明,返回最后一个小明的索引。
06.
复制代码
8Substring():截取字符串
举例如下:
01.stringstr = “大家好,我叫小明,小明今年12岁了!“;
02.stringstr1 = str.Substring(4);
03.Console.WriteLine(str1);
04.//输出结果为:我叫小明,小明今年12岁了!
05.//即从索引4开始截取(含4),截取到最后
06.
07.stringage = str.Substring(13,2);
08.Console.WriteLine(age);
09.//输出结果为:12
10.//即从索引13开始截取(13),截取2个字符
复制代码
9Split():将指定字符串按某指定的分隔符进行拆分,拆分将会形成一个字符串的数组并返回
01.//根据一个字符来分割
02.stringname = “||||“;
03.string[]strName = name.Split(‘-‘);
04.for(inti = 0;i < strName.Lengeh;i++)
05.{
06.    Console.Write(strName);
07.}
08.//输出结果为:小|明小|花小|胖小|
09.
10.//根据一个字符串数组来分割
11.string[]strName = name.Split(new string[]{“-“,”|”},StringSplitOptions.RemoveEmptyEntries);
12.for(inti = 0;i < strName.Lengeh;i++)
13.{
14.Console.Write(strName);
15.}
16.//输出结果为:小明小花小胖小美
17.
18.//补充:Split()6个重载,可以传一个char来分割,也可以传一个char[],还可以是string[]
19.
20.
复制代码
10Join():把一个字符串数组中的每个元素,用指定的字符进行连接
举例如下:
01.stringname = “||||“;
02.string[]strName = name.Split(new string[]{“-“,”|”},StringSplitOptions.RemoveEmptyEntries);
03.stringstr = string.Join(““, strName);
04.Console.WriteLine(str);
05.//输出结果: 小★明★小★花★小★胖★小★美
06.//注意:此方法为静态方法
复制代码
11Format():通过占位符来拼接字符串
举例如下:
01.stringname = “小明“;
02.stringsay = string.Format(“我叫{0}”,name);
03.Console.WriteLine(say);
04.//输出结果为:我叫小明
05.//注意:此方法为静态方法
复制代码
12Replce():用指定的字符替换字符串中指定的字符
01.stringsay = “我叫小明,我今年12岁了!“;
02.stringstr = say.Replcae(“小明“,”***”);
03.Console.WriteLine(str);
04.//输出结果为:我叫***,我今年12岁了!
复制代码
13Trim():去掉字符串两端的空格
举例如下:
01.stringname = “小明“;
02.stringstr = name.Trim();
03.Console.WriteLine(str);
04.//输出结果为:小明

在c#中的richTextBox中实现拖拽

时间:2014年02月07日作者:小侃评论次数:0

之前学过如何在TextBox实现文字的拖拽,但是今天在使用RichTextBox时却发现了好多问题,属性框里没有DragDrop和DragEnter事件,可是在帮助文档里发现了是有这个事件的,所以自己便用代码去实现这两个事件,可是在调用OnDragDrop()方法时,却发现没有,很是郁闷,又仔细再帮助文档中看了一番,却发现OnDragDrop是受保护的,突然间有点晕了,呵呵,想想微软不会这么瞎胡闹吧,richTextBox要比TextBox高级多,怎么会不能实现拖拽呢!呵呵,还是仔细的查看帮助文档吧,呵呵,果然被我发现了一个属性:EnableAutoDragDrop,唉,原来微软都帮我们写好了呀,只要你该一下属性值为True就OK了。

但是,这个时候你运行程序,你会发现文字部分可以成功拖拽了,但是我的RichTextBox有嵌入一些控件,默认的拖拽无法实现我的想法,于是就想去事件框里去看看DragDrop和DragEnter事件有了没,结果发现还是没有,就按F12到元数据表里看了一下他们的定义才发现,其实他们是可以在代码中访问并更改的,但是却不是看见的。

//
// Summary:
// Occurs when the user completes a drag-and-drop
[Browsable(false)]
public event DragEventHandler DragDrop;
//
// Summary:
// Occurs when an object is dragged into the control’s bounds.
[Browsable(false)]
public event DragEventHandler DragEnter;

C++与C#对比学习:内存管理

时间:2013年10月24日作者:小侃评论次数:0

我们知道计算机最重要的资源就是CPU和内存了.CPU的话我们貌似不能直接去操作,都是操作系统去管.而内存的话分为内核区和用户区.内核区是由操作系统管理,我们只能通过一些API去间接操作.而用户区就可以让应用程序去使用了.我们编程大部分时候就是在用户内存区中折腾来折腾去.

     C++内存分区

C++的内存一般分为栈(stack),堆(heap),自由存取区,全局/静态存储区,常量存储区,代码存储区.

栈(stack)

这是由系统自动去分配和管理,不用我们去操心.貌似大小就几M吧.有时我们也叫它堆栈.很容易和堆搞混了,还是看着英文爽点.另外这里的heap和数据结构中的堆不同,heap实际数据结构是指针链表.stack到就是数据结构中的栈.

堆(heap)

我们平时说的直接操作内存用的最多的就这个地方了,出问题出的最多的也就是这里.一般是用new去申请一块内存,用完了再delete掉.我们经常听到的啥内存泄露就是你new了一块内存后忘记delete掉了,或者delete时没有采取正确的方式,没能成功delete.

自由存储区

由malloc申请的存储区,用free来释放掉,这是兼容C的.在C++中用new时还会调用类的构造函数,而用malloc时只是给你分配内存,不会调用构造函数.反正在C++中基本上用new和delete就可以了,不用malloc也行的.另外有些人把自由存储区也划分到堆这一组.

全局/静态存储区

我们知道C++作为面向对象,所以一眼望过去都是一个个的类,啥变量,函数都是在类里面.但毕竟它不是纯面向对象.还有些特性是违反类的封装的,比如全局函数和全局变量.函数是存储在代码区.全局变量就存储在全局存储区.不过既然都说了是面向对象自然还是尽量按规矩办事好点,尽量别用全局变量.我们用全局变量时一般是因为某些变量不是属于某一个类的或某一个类的实例.如果是后一种情况我们可以用静态变量来替代.在类中用statics声明一个静态变量.静态变量直接用类名做前缀去访问,不能通过一个实例化的类去访问.它是保存在静态存储区.

常量存储区

像我们用const声明的变量就是常量.注意在C中有点不一样,const声明的变量可不叫常量,只有用define定义的才叫常量,而且这样的常量由于是在预编译阶段替换掉了,所以不用在内存中给常量分配内存了.另外除了用const显式指明是常量,还有种特殊情况隐式的是常量.char * pChar = “i am const”; //这个字符串就是一个常量,然后指针pChar指向它.

注意如果是这样string str = “i am string”; //这个双引号中的就不是常量了.它只是一般的变量值,存储在stack中.char*这是兼容C中的字符串,在C++可以不用它,用string好了.当然有时调用API或者和C交互还是要用到char*,这时可以把string ,char*互相转换下.

代码区

代码和函数就都保存在这个区.函数名相当于是个隐式的指针,我们调用某个函数时是使用函数名,函数名就指向代码区中具体的函数代码.

 

另外一种划分法是分为:文本段(.text), 数据段(.data), stack,heap四种.

其中文本段是只读的,所以源代码,函数都放这.另外常量也放在这里.

数据段就是保存全局变量或静态变量

stack和heap跟上面说的就一样了.

C#内存分区

由于直接操作内存会经常出问题,所以C#干脆连指针都不让你用,内存也不让你直接操作.CLR(common language runtime)有点像java的JVM,它给你去管理内存.

所以C#的内存分区没那么复杂,直接一个stack,和heap.stack跟C++差不多一样,还是系统去管.heap就是所谓的托管堆,由CLR替我们管理.当然heap里面可能还会分些啥区,但我们就不用去管那么多了,反正人家CLR给我们代劳了啊.C#中有值类型和引用类型的概念.所有基本类型加上struct都是值类型,是保存在stack中.而且string和数组还有自己定义的class都是分两部分保存,具体内容保存在heap中,但stack中保存有它们在heap中的内存地址(32位系统中就4字节,64位系统就8字节),觉得这其实非常像个指针了 .

 

C++直接操作内存与C#托管内存的优劣

很多人都讨论过这个问题,反正各执一词,各有各的道理.C++直接操作内存效率高,C#和Java中间隔了一层效率肯定差远了.但C#,java中不会再有那么多因直接操作内存而带来的问题.其实我们可以举个简单的比喻.就说玩QQ拖拉机或斗地主吧.你牌好时可以自己玩,牌差时不想玩就来个托管,让机器给你代劳.

如果你打牌技术非常厉害你自己打肯定比机器托管厉害,如果你不太会打牌还不如机器托管打的好.

用C++直接玩内存就相当于你自己打牌,而用C#,Java中的托管内存就相当于托管让机器给你玩牌.所以如果你技术非常厉害就用C++占优势点,不然的话用C#,java占优势点.

当然其实说到底语言只是一个工具,我们的目的是编写个有用的系统出来.所以如果一个系统本身的商业逻辑就非常的复杂的,比如大型web网站,你还要再纠缠在一些复杂的语法细节上就更复杂了 .所以用C#,java这样的开发效率更高.另外一个就是类库越多对我们来说也越好,很多事别人帮你做好了,你直接拿来用就好.而且很多类库都是些牛人写的,比你自己写的强多了.

SQLite简介,C#调用SQLite

时间:2013年10月24日作者:小侃评论次数:0

当我们用到海量数据时一般会用Oracle,SQL Server,DB2,Sybase,MySQL等数据库来保存和管理数据.如果只是程序中需要保存少量数据的话直接整到注册表里,或者保存到一个XML文件中.那如果数据量刚好不多不少,用Oracle这样的数据库有点小题大作,没有必要.有个XML保存的话存取速度又比较慢.咋整呢?这时用SQLite这个小型的嵌入式数据库就是非常理想的选择.它用起来也很简单方便.

SQLite不需要像Oracle等数据库一样得安装,配置,然后又是啥服务器端客户端啥的.它很简单就直接一个小小的文件,以db为后缀的文件.大小就几十K.你不用干其他啥事,把它拷过来直接用就行.像操作一个普通的txt文件一样.不过觉得把它当作一个文件还是有点不妥.我们应该可以这样理解,它有点像库函数,或COM组件,dll.然后提供了一些接口给你调用..SQLite是开源的,你要下载它和查看它的C源代码可以去官方网站http://www.sqlite.org/

当然有人会问那如果我们不在某个程序中通过接口调用SQLite,而只像一般的数据库那样通过图形界面操作咋整啊? 你可以用一个叫SQLiteBrowser的工具,下载下来解压缩,不用安装,直接双击里面的exe文件打开一个图形界面.然后点击菜单File –>open database,找到那个db文件就行.然后在图形界面上可以查看表中数据,新建表啥的.不过SQLite是没有啥权限控制的,用户名密码都没,谁都能拿来打开.所以里面要是保存啥秘密信息的话最好先加密后再保存.

SQLite是用C语言开发的,所以用C和C++去调用是一点问题都没.不过用C#也能调用,只不过要用到一个dll,这里我就讲下怎么用C#调用SQLite.

 

C#调用SQLite

1.首先得去网上下载一个叫System.Data.SQLite.dll的文件

2.跟添加其他dll一样,先Add Reference添加此dll

3.添加命名空间using System.Data.SQLite

4.接下来就是写代码了

string connectString = @”Data Source=D:\SQLite.db;Pooling=true;FailIfMissing=false”;       /*D:\sqlite.db就是sqlite数据库所在的目录,它的名字你可以随便改的*/

SQLiteConnection conn = new SQLiteConnection(connectString); //新建一个连接

conn.Open();  //打开连接,如果sqlite.db存在就正常打开,如果不存在则创建一个SQLite.db文件

SQLiteCommand cmd = conn.CreateCommand();

cmd.CommandText = “select * from orders”;   //数据库中要事先有个orders表

cmd.CommandType = CommandType.Text;

using (SQLiteDataReader reader = cmd.ExecuteReader())

{

while (reader.Read())

Console.WriteLine( reader[0].ToString());

}

 

用法其实跟平时用C#操作一般的数据库差不多.

另外如果要用到Linq的话得用到另外一个dll文件,System.Data.SQLite.Linq.dll

标签:分类:WIN程序开发

C#操作MySql,PostgreSQL

时间:2013年10月24日作者:小侃评论次数:0

我们都对MySql比较熟悉,相较而言PostgreSQL就没那么出名.其实它们是两个比较类似的关系型数据库.PostgreSQL原来的名字叫Postgres,做了一些改进后就改名为PostgreSQL了.详细介绍可以去官方网站看看

MySQL官网:http://www.mysql.com/

PostgreSQL官网:http://www.postgresql.org/

MySQL装好后一般没默认的图形操作界面,不过你可以下载个图形界面的工具MySql workbench.

PostgreSQL有个自带的图形界面的工具 pgAdmin III.

哎玩习惯了windows的人总希望操作啥玩艺都是图形界面的才爽,可能用多了linux,unix就不会那么依赖图形界面吧

 

C#操作MySql

跟操作其他数据库类似,先要整个相应的dll来.你可以网上下载个

MySql.Data.MySqlClient.dll

然后就是添加引用.引用命名空间

using MySql.Data.MySqlClient;

 

string connectString = @”server=localhost;userid=root;password=arwen;database=test”;   /*由于我的数据库在本地就用localhost了,可以替换成IP地址*/

MySqlConnection conn = new MySqlConnection(connectString);

conn.Open ();

MySqlCommand cmd = conn.CreateCommand();

cmd.CommandText = “select * from info”;

cmd.CommandType = CommandType.Text;

using (MySqlDataReader reader = cmd.ExecuteReader())

{

while (reader.Read())

Console.WriteLine(reader[0].ToString());

}

 

 

C#操作PostgreSQL

先去整个叫PostgreSql.Data.PostgreSqlClient.dll的文件.

然后添加引用,使用命名空间

using PostgreSql.Data.PostgreSqlClient;

 

string connectString = @”Server=localhost;Database=postgres;User ID=arwen;Password=arwen”;

PgConnection conn = new PgConnection(connectString);

conn.Open();

PgCommand cmd = conn.CreateCommand();

cmd.CommandText = “select * from test”;

cmd.CommandType = CommandType.Text;

using (PgDataReader reader = cmd.ExecuteReader())

{

while (reader.Read())

Console.WriteLine(reader[0].ToString());

}

win32窗体项目向导制作(VC++6.0)

时间:2013年10月02日作者:小侃评论次数:0

成品下载:Win32 DialogApp(VC创建窗体程序的快捷方式)

1.新建WIN32工程

打开VC++6.0,依次选择【File】【New】
在弹出窗口选择【Projects】选项卡
左侧选择【Win32 Application】,右侧【Project name】框中输入工程名(小编这里为win32app),【Location】框中选择储存路径,点击【OK】
在弹出菜单选择【A simple win32 application】,点击【Finish】

2.创建可视化对话框窗口资源(个性化定制)

依次选择【File】【New】
在弹出窗口选择【Files】选项卡
左侧选择【Resource Script】,右侧选中【Add to project】框,【File】中输入资源名(小编这里为resource),【Location】框中选择储存路径,点击【OK】
在vc窗口编辑区可看见resource.rc,右键选择【Insert】,在弹出对话框中选择【Dialog】,点击【New】
在绘制对话框右键选择【Properties】,在弹出框中更改【ID】为IDD_MAIN(亦可不该,但要记住ID值,后面显示对话框会用到),然后回车
经过自己一番DIY后,记得保存文件

3.编辑cpp文件定制个性模板

首先在工作区【FileView】选项卡中双击【工程名.cpp】文件(小编这里是win32app.cpp,只因为刚才新建的工程名为win32app)编辑,可以看到如下字段
#include”stdafx.h”

 

intAPIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
         // TODO: Place code here.

 

          return0;
}
要在程序运行时显示创建的对话框,还需将如下字段添加到WinMain 函数return语句之前:
DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, Main_Proc);
这儿的IDD_MAIN为前面设置的创建对话框资源Dialog的ID
注意:要使用创建的自定义资源,需要将创建过程生成的resource.h引入(直接使用会报错),所以还需要在文件头部添加如下代码:
#include “resource.h”

 

同时还要添加主窗口函数Main_Proc(注意这儿定义的函数名要和上述SialogBox中使用的参数名一致,否则会报错),以及初始化函数Main_OnInitDialog,退出函数Main_OnClose,和按钮事件响应函数Main_OnCommand。各函数定义如下:
//初始化处理函数
BOOL Main_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
          return TRUE;
}

 

//按钮动作响应函数
void Main_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{

 

}

 

//退出信息处理函数
void Main_OnClose(HWND hwnd)
{
    EndDialog(hwnd, 0);
}

 

//主窗口信息处理函数
//WM_COMMAND – 处理应用程序菜单;WM_PAINT – 绘制主窗口;WM_DESTORY – 发送退出信息并返回
BOOL WINAPI Main_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
          switch(uMsg)
    {
        HANDLE_MSG(hwnd, WM_INITDIALOG, Main_OnInitDialog);//消息分流器,定义在<windowsx.h>中
        HANDLE_MSG(hwnd, WM_COMMAND, Main_OnCommand);
                   HANDLE_MSG(hwnd,WM_CLOSE, Main_OnClose);
    }

 

          return FALSE;
}

 

注意:因为Main_Proc 函数中使用了消息分流器HANDLE_MSG,此宏定义在windowsx.h中,所以要在文件头部添加:
#include <windowsx.h>


4.
编辑头文件

实现函数声明,若上述四个函数顺序是【Main_OnInitDialog,Main_OnCommand,Main_OnCommand三个函数在Main_Proc之前,且这四个函数在WinMain之前】可以跳过此步骤
因为C语言函数必须先声明再使用,除非被调函数在主调函数前面!
新建dialog.h并添加到工程,在文件中添加如下字段声明函数:
//函数声明
BOOL WINAPI Main_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL Main_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam);
void Main_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);
void Main_OnClose(HWND hwnd);
同时在【工程名.cpp】头部添加
#include “dialog.h”


5.
制作专属Win32工程向导

打开VC++6.0,依次选择【File】【New】【Projects】【Custom AppWizard】
在右侧【Project name】框中输入工程名,【Location】框中选择储存路径,选择【OK】
在弹出菜单选择【An existing project】,输入框中表示显示在工程向导中的名字,点击【NEXT】,选择刚才创建Win32工程dsp文件,点击【Finish】
在工作区【FileView】选项卡中【Template Files】下编辑如下文件即可
【confirm.inf】文件中输入你想在向导窗口展示的文字及其排版
要是编辑器默认支持MIF类,需在【StdAfx.cpp】中#include “stdafx.h”前端加入#include <afxdisp.h>,然后【building】即可
否则会出现error LNK2001错误和error LNK1120错误,不添加只需每次创建工程后执行以下步骤即可避免此错误:
工作区【FileView】选项卡中【工程名files】(小编这里是win32app files)右键,选择【Settings】,然后在【General】选项卡下【Microsoft Foundation Classes】下选择【Not Using MFC】,点击【OK】即可