??xml version="1.0" encoding="utf-8" standalone="yes"?>
HEAP: Free Heap block xxxxxxxx modified at xxxxxxxx after it was freed
GFlags是Windows debug tools 工具包下的一个工?在Windows 2000的Resource Kit中也可以扑־到。用来设|一些调试属性,M上分?个别SystemQKernel和Image File。我们设|好Path环境变量,其指向Debug tools工具的目录下?/p>
下蝲安装 gflags:
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx
http://www.ithov.com/Soft/system/systest/38210.shtml
GFlags
- 老牌的PageHeap配置工具Q有命o行和GUI两种操作方式Q功能比较全Q包含在Windbg调试器安装包内。同样在Windows 2000 Professional SP2 以上可用?/span>
一些用GFlags命o行的例子Q?/span>
配置正常堆Q?/span>
"C:\Program Files\Debugging Tools for Windows (x86)\gflags.exe" /p /enable qq.exe
配置完全堆Q?/span>
"C:\Program Files\Debugging Tools for Windows (x86)\gflags.exe" /p /enable qq.exe /full
列出当前启动了页堆的q程列表Q?/span>
"C:\Program Files\Debugging Tools for Windows (x86)\gflags.exe" /p
取消堆讄Q?/span>
"C:\Program Files\Debugging Tools for Windows (x86)\gflags.exe" /p /disable qq.exe
一些特D选项解释Q?/span>
/unaligned
q个选项只能用于完全堆。当我们从普通堆理器分配一块内存时Q内存L8字节寚w的,堆默认情况下也会用这个对齐规则,但是q会D分配的内存块的结不能跟边界精对齐,可能存在0-7个字节的间隙Q显Ӟ对位于间隙范围内的访问是不会被立卛_现。更准确的说Q读操作永q不能被发现Q写操作则要{到内存块释放时校验间隙I间内的填充信息时才发现?unaligned用于修正q个~陷Q它指定堆理器不必遵?字节寚w规则Q保证内存块N_寚w边界?/span>
需要注意的是,一些程序启用这个选项可能出现异常Q例如IE和QQ׃支持?/span>
/backwards
q个选项只能用于完全堆。这个选项使得分配的内存块头部与页边界寚wQ而不是尾部与边界寚wQ,通过q个选项来检查头部的讉K界?/span>
/debug
指定一启动q程即Attach到调试器Q对于那些不能自动生成dump的程序,是比较有用的选项?/span>
完全堆Q?/span>
当分配一块内存时Q通过调整内存块的分配位置Q其结恰好与pȝ分页边界寚wQ然后在边界处再多分配一个不可访问的作Z护区域。这P一旦出现内存读/写越界时Q进E就会CrashQ从而帮助及时检查内存越界?/span>
因ؓ每次分配的内存都要以q种形式布局Q尤其对于小片的内存分配Q即使分配一个字节,也要分配一个内存页Q和一个保留的虚拟内存(注意在目前的实现中,q个用作边界保护区域的页从来不会被提交)。这需要大量的内存Q到底一个进E需要多内存,很难估算Q因此在使用Page Heap前,臛_保证你的机器臛_讄?G虚拟内存以上?/span>
正常堆
正常堆原理与CRT调试内存分配函数cMQ通过分配量的填充信息,在释攑ֆ存块时检查填充区域。来内存是否被损坏Q此Ҏ的优Ҏ极大的减了内存耗用量。缺Ҏ只能在释攑֝时检,不太好跟t出错的代码位置?/span>
堆能处理的错误cdQ?/span>
错误cd 正常堆 整页?nbsp;
堆句柄无?nbsp; 立即发现 立即发现
堆内存块指针无效 立即发现 立即发现
多线E访问堆不同?nbsp; 立即发现 立即发现
假设重新分配q回相同地址(realloc) 90% 内存释放后发?nbsp; 90% 立即发现
内存块重复释?nbsp; 90% 立即发现 90% 立即发现
讉K已释攄内存?nbsp; 90% 在实际释攑发现 90% 立即发现
讉K块结之后的内容 在释攑发现 立即发现
讉K块开始之前的内容 在释攑发现 立即发现
以下是D例:
前期工作Q?gflagsQ默认安装在C:\Program Files\Debugging Tools for Windows (x86)Q加入到path
案例1Q?/strong>
int _tmain(int argc, _TCHAR* argv[])
{
char *p = new char[8];
p[8] = 10;
delete[] p;
return 0;
}
E序本n是有问题的。数l已l越界,但是debug模式下ƈ不报错,release模式下也很大可能是不crash的?/p>
在命令提C符下运行:
>gflags /p /enable test.exe /full
在release模式q行test.exe。exception直接定位到 p[8] = 10; q一?/p>
案例2Q?/strong>
int _tmain(int argc, _TCHAR* argv[])
{
char *p = new char[9];
p[9] = 10;
delete[] p;
return 0;
}
以上代码和案?仅有一点不同,是数组大小。但是如果运?br>gflags /p /enable test.exe /full
在release模式下ƈ不会出现exceptionq定位到 p[9] = 10;
原因是没有设|?/unaligned 参数Q具体看说明。案?中,数组?字节大小Q按内存8字节寚w的说法,q块内存应该?/p>
16字节Q后面还?字节的空_所?p[9] = 10; q不会生exception。设|?/unaligned 参数Q禁?字节寚wQ就
可以跟踪?p[9] = 10; q个exception
>gflags /p /enable test.exe /full /unaligned
案例3Q?br>class A
{
public:
int a;
void del(){
delete this;
a = 10;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A* a = new A();
a->del();
return 0;
}
在debug模式下可能生exception:
HEAP: Free Heap block xxxxxxxx modified at xxxxxxxx after it was freed
在release模式下运行ƈ不报错,但是E序本n是有问题的,delete this; 之后Q又l成员变?a=10;
q显然是不对的?/p>
>gflags /p /enable test.exe /full
此时在debug下运行程序,会生exceptionQƈ定位?nbsp; a = 10;
首先一U比较直观简z的方式是用WinDbg{调试器直接attach到需要调试的q程Q调试完毕之后再detach卛_。但是这U方式有个缺点就是执行debugger命o时必dbreakq个q程Q执行完debug命o之后又得赶紧F5让他l箋q行Q因你break住的时候意味着整个q程也已l被你挂赗另外也l常会由于First Chance Excetpion而自动breakQ你得时ȝ意避免长旉break整个q程。所以这L调试方式Ҏ间是个很大的考验Q往往没有充裕的时间来做仔l分析?/p>
另一U方式则是在出现问题的时候,比如CPU持箋长时?00%Q内存突然暴涨等非正常情况下Q通过Ҏ务进Esnapshot抓取一个dump文gQ完成dump之后先deatchQ让q程l箋q行。然后用windbg{工h分析q个抓取到的dump文g?/p>
那么如何在不l止q程的情况下抓取dump文g呢?Debugging Tools for Windows里提供了一个非常好的工Padplus.vbs。从名字可以看出Q实际上是一个vb脚本Q只是对cdb调试器作的一个包装脚本?/p>
其\径与Debugging Tools for Windows的安装\径相同,使用的方法也很简单,如下所C?
adplus.vbs -hang -p 1234 -o d:\dump
其中-hang指明使用hang模式Q亦卛_q程q行q程中附加上去snapshot抓取一个dump文gQ完成之后detach。与之对应的?crash崩溃模式Q用户先启动adplusQ然后由它启动要监控的程序,在出现异常崩溃时自动生成dump文gQ或者通过Ctrl-CZؓ发出抓取指o。但?crash模式在抓取完成之后,被监控的q程必ȝ止。因此我们在q里只选用-hang模式?/p>
-p是要调试的进EIDQ比如ASP.NET应用U程池,在Win2003下就是w3wp.exe
-o 指定要output的dump文g路径?/p>
另外Q与adpluscM的,有个UserDump工具Q但是抓取用h式的q程Q而adplus则是内核模式和用h式两者皆可?/p>
而L周至的Dr. WastonQ则会在q程崩溃之后的自动时候抓取dump文gQ一样可以用于windbg{调试器来事后分析程序崩溃时的状态?/p>
Q=Q=Q=Q=Q=Q=Q=Q=Q=Q=
0:000> !dumpheap -stat
No export dumpheap found
======解决ҎQ?br>.load clr20\sos.dllQ你要先执行的。sos.dll在默认的c:\windows\microsoft.net\framework\v2.....下面Q你复制到c:\program files\debugging tools for windows下面的clr20目录下面Qclr20是你手工创徏的)
Q=Q=Q=Q=Q=Q=Q=Q=Q=Q=Q=Q?br>
?NET下开发时Q最基本的调试方法就是用Visual Studio的单步调试。但是对于一些特D情况,特别是涉及到CLR内部的时候用这U方式就达不到目的了?
如果要查看运行时内存使用情况QIL代码QCLR信息{可以用以下两U方式:
1、用VS2005 + sos.dll
2、用Windbg + sos.dll
W二U方式功能更加强大,下面我就通过实际操作展示一下怎么使用q种Ҏ得到q行时ArrayList内部的倹{?
有h可能会说Q我直接用Visual Studio的单步调试岂不是更快Q当Ӟq个只是一个演C,通过q个演示是ؓ以后的高U调试打下基
在操作之前,先熟悉一下基本知识:
A、用VS2005 + sos.dll调试
1、需要在目->属?>调试-〉启用非托管代码调试
2、打开调试-〉窗?〉即?
3、在xH口中输?!load sos 加蝲调试模块
4、输入其它调试语?
B、用Windbg + sos.dll
1、去微Y的网站下载最新的Windbg
2、打开Windbg在File-〉Symbol File Path ...H口中输?srv*c:\symbols*http://msdl.microsoft.com/download/symbols
3、运行需要调试的E序Q然后在Windbg中File-〉Attach to Process中选择刚才q行的程?
4、在出现的CommandH口中就可以输入调试语句
5、常用调试语句:
lm //查看加蝲了哪些模?
.load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll //加蝲调试模块
ld TestClass //加蝲调试W号
!name2ee TestClass.exe TestClass.Program.test //昄testҎ相关的地址
!dumpmt -md 00976d48 //得到cȝ成员函数详细信息
!dumpil 00973028 // 昄q个Ҏ被编译器~译之后的IL代码
!dumpheap -stat //该命令显C程序中所有对象的l计信息Q显C的大小是对象本w的大小Q不包括对象里面值的大小
!dumpheap -mt 790fcb30 //该命令显CMethodTable 790fcb30的详l信?
!gcroot 012919b8 //来显CZ个实例的所属关p?
!dumpobj(do) 012a3904 //昄一个对象的具体内容Q看对象里面有什么,值是什?
!ObjSize 012a1ba4 //对象实际在内存中的大?
!eeheap -gc //查看托管堆的情况(包括大小)
!DumpArray //查看数组信息
下面来看看具体的调试步骤:
1、我们的试代码
namespace TestClass
{
class Program
{
[STAThread]
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add("aaaa");
list.Add("bbbb");
Console.ReadLine();
}
}
}很简单,是一个ArrayList
q行q个E序(开始执行,不调?Q然后进入WindbgQAttach到这个进E?
2、查看所有堆栈信?
0:004> !dumpheap -stat
MT Count TotalSize Class Name
7910062c 1 12 System.Security.Permissions.SecurityPermission
7918e284 1 16 System.IO.TextReader+SyncTextReader
79102d10 1 20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
79102cb4 1 20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
79101d30 1 20 System.Text.InternalEncoderBestFitFallback
79100a7c 1 20 Microsoft.Win32.SafeHandles.SafeFileHandle
79105cd4 1 24 System.Collections.ArrayList
......
7912ad90 11 9036 System.Object[]
790fcb30 2083 131492 System.String
Total 2202 objects
除了我们的ArrayList外,q有很多其它的系l信息,先不用管?
3、查看我们的ArrayList的信?
0:004> !dumpheap -mt 79105cd4
Address MT Size
012a1b88 79105cd4 24
total 1 objects
Statistics:
MT Count TotalSize Class Name
79105cd4 1 24 System.Collections.ArrayList
Total 1 objects
4、查看对应地址内部实际的?
0:004> !do 012a1b88
Name: System.Collections.ArrayList
MethodTable: 79105cd4
EEClass: 79105c28
Size: 24(0x18) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
7912ad90 40008df 4 System.Object[] 0 instance 012a1bb0 _items
791018e0 40008e0 c System.Int32 1 instance 2 _size
791018e0 40008e1 10 System.Int32 1 instance 2 _version
790fc35c 40008e2 8 System.Object 0 instance 00000000 _syncRoot
7912ad90 40008e3 1c0 System.Object[] 0 shared static emptyArray
>> Domain:Value 00149c58:012a1ba0 <<
可以看到ArrayList的大ؓ2Q具体的g存在地址012a1bb0中,是一个System.Object[]cd的数l?
5、查看数l信?
0:004> !DumpArray 012a1bb0
Name: System.Object[]
MethodTable: 7912ad90
EEClass: 7912b304
Size: 32(0x20) bytes
Array: Rank 1, Number of elements 4, Type CLASS
Element Methodtable: 790fc35c
[0] 012a1b50
[1] 012a1b6c
[2] null
[3] null
6、查看数l内对象的?
0:004> !do 012a1b50
Name: System.String
MethodTable: 790fcb30
EEClass: 790fca90
Size: 26(0x1a) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: aaaa
Fields:
MT Field Offset Type VT Attr Value Name
791018e0 4000096 4 System.Int32 1 instance 5 m_arrayLength
791018e0 4000097 8 System.Int32 1 instance 4 m_stringLength
790fe534 4000098 c System.Char 1 instance 61 m_firstChar
790fcb30 4000099 10 System.String 0 shared static Empty
>> Domain:Value 00149c58:790d81bc <<
7912b1d8 400009a 14 System.Char[] 0 shared static WhitespaceChars
>> Domain:Value 00149c58:012a16f0 <<
Q=Q=Q=Q=Q=Q=Q=Q=Q=Q=
windbg使用ȝ
【抓dump?br>1、一般抓?br>adplus -hang -p 3230 -quiet ?230 pidq程Qhang模式Q相当于把那个进E暂停住Q取内存快照
adplus -crash -pn w3wp -quiet 抓w3wpq程Qcrash模式Q当那个q程崩溃l束的时候自动抓取当时的内存
adplus -hang -iis -quiet 抓IIS相关q程Q包括其上host的web应用Q以及iis自n
2、抓window服务
http://support.microsoft.com/kb/824344/zh-cn
3、远E抓
http://blog.joycode.com/tingwang/archive/2006/08/11/79763.aspx
4、抓蓝屏和死机的dump
电脑无故重启或者蓝屏会在C:\WINDOWS\Minidump\下保存一个minidumpQ但是这个minidump可用的命令很,一般只?analyze –v看到是哪个进E引LQ还有相关的驱动模块基本定位问题了?br>5、IIS回收的时候抓
http://blog.yesky.com/blog/omakey/archive/2006/12/17/1618015.html
6、计划Q务抓
比如一个进Ev来后不知道它什么时候会意外崩溃Q可以在计划d里用crash里抓Q当那个q程意外l止的时候,cdb可以直接附加上去Q抓取当时的dumpQ如果要抓一些会自动重启的进E,而且要抓每次重启前的dumpQ可以参考附录里一节?/p>
【常用命令?br>1、先path C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727Q把.net路径讄为path环境变量Q一遍在windbg里可以直?load sosQ而不?load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll
2、ld demoQ加载你E序的pdb文gQ调?netE序一般要把kernel32和mscorwks的符号加载上Q关于这两个东西大家可以查资料,其是后者有哪些函数可以多了解一些?br>3、在windbg的file/symbol file path对话框里输入以下文字Q以便自动加载和下蝲W号
C:\WINDOWS\Symbols;d:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\symbols;.sympath SRV*d:\localsymbols*http://msdl.microsoft.com/download/symbols
其中有windows?net2.0和自动从|上下蝲的调试符P注意Ҏ自己的情况适当修改目录
【调试死锁?br>1?syncblkQ查看哪些线E拿C?br>2、~67e!clrstack 跛_某个拿到锁的U程看它正在q什么操作,q迟不肯释放?br>3?runaway 查看q个占有锁的U程q行了多长时间?br>4、~*e!clrstack查看所有线E的托管堆栈Q看看哪些是正在{待锁的Q比如hang在System.Threading.Monitor.Enter(System.Object)
5、~136s选择该线E,昄如下
0:000> ~136s eax=00005763 ebx=08deeb5c ecx=03eff0d4 edx=5570ab69 esi=08deeb5c edi=7ffd6000 eip=7c95ed54 esp=08deeb10 ebp=08deebb8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ntdll!KiFastSystemCallRet: 7c95ed54 c3 ret
扑ֈecx寄存器的|复制后ctrl+fQ向上查找,会找?syncblk的地方,如下
0:000> !syncblk Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner 1906 03ee4be4 5 1 03ee8f88 22c8 67 185e2ef0 System.Object 5390 052ca39c 3 1 05292b30 1dd4 49 1060d3ac System.Object 9372 0530702c 15 1 0012d3a8 1aa8 80 185e7704 System.Object 11428 03eff0d4 35 1 053b8fa8 169c 120 166acd98 System.Object 15278 0531c6b4 61 1 06bc1430 26d8 86 1a5bea88 System.Object
可以看到136U程{待的锁?20LE占着不放Q格式有点ؕQ凑合看Q,
6、有时候通过ecx寄存器找锁不是很定Q可以用~* kb来把所有线E堆栈打出来Q然后根?syncblk出来的同步快的值去搜烦大概有多个U程在等那个锁。因为同h{待锁,可等的状态不一P有的在Q里,有的锁已l升U,有的d试去拉K了,所以不一定当时ecx寄存器指向那块内存,具体如何扑ֈ某个正在{待锁的U程{待的锁的内存地址Q以及它正等待的q个锁被哪个U程拿着Q我q没琢磨律来Q但一般情况下Q如果有其它同步对象的话Q更难查?net里用我上面说的几步就能查出锁的问题了?/p>
【内存泄漏?br>1?dumpheap -stat看看哪些对象个数最多,占内存最大,
2、找到某个格式比较多的对象,可以看它的方法表Q然后用!dumpheap -mt 66398fa4去随机找几个对象的地址
3、用!do 1e5a22bc命oL看几个对象的状态,属性的值等Q看看正怸正常
4、用!gcroot -nostacks 1e5a22bcL看几个对象的Ҏ怸正常Q如果有些对象的根不是自己预先设计的那样Q很可能被自己没惛_的对象强引用了,所以GC无法回收它,泄漏了?br>【CPU癑ֈ百?br>主要用几个计数器?runaway命oQ具体见以下链接
http://www.cnblogs.com/onlytianc ... 7/06/03/769307.html
【线E池耗尽?br>!threadpool 能看到完成端口,U程池工作线E和timer回调各占U程池的情况?br>【其它?br>1?eestack -short -ee查看所有重?获取锁的Q托的Q停止ƈ允许回收?U程的dumpstackQ差不多相当于~*e!dumpstack
2?time 可以看到q程跑了多少旉
3?dso 查看当前U程里有哪些对象Q分析内存泄漏问题也怼用到
【小l?br>要想很好的用windbg排查.net问题Q首先要了解一些clr宿主的基知识Q以及IL的一些基Q还有简单的寄存器和汇编试Q再是有个好的思\Q最后就是经验和对代码逻辑的理解?/p>
【附录:写了一个自动抓dump的工P可在E序异常退出的时候抓dump?br>【用方法?br>1、先在cmd下运行以下命令确保计划Q务开着
net start "task scheduler"
2、执行以下命令安排自动抓?br>at 13:27 d:\myapp\autodump\processmon.exe
其中计划启动的时间和自动抓包的程序\径要Ҏ情况讄Q计划启动之前当前用户一定要注销?br>【相关配|?br><appSettings>
<add key="adplusPath" value="D:\MyApp\Debugging\adplus.vbs"/><!--adplus的\?->
<add key="ProcessName" value="w3wp"/><!--要抓dump的进E的名字Q可用部分名字,不用完整?->
</appSettings>
【源码?/p>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace ProcessMon
{
class Program
{
static readonly List<int> _dumpPIDs = new List<int>();
private static readonly string _processName = System.Configuration.ConfigurationManager.AppSettings["ProcessName"];
private static readonly string _adplusPath = System.Configuration.ConfigurationManager.AppSettings["adplusPath"];
static void Main(string[] args)
{
while(true)
{
Console.WriteLine("..");
ThreadProc();
Thread.Sleep(10000);
}
}
private static void ThreadProc()
{
foreach(Process vProcess in Process.GetProcesses())
{
try
{
string processName = vProcess.ProcessName.ToLower();
if (processName.IndexOf(_processName) >= 0)
{
Console.WriteLine("{0}-{1}", vProcess.ProcessName, vProcess.Id);
if (_dumpPIDs.Contains(vProcess.Id))
continue;
_dumpPIDs.Add(vProcess.Id);
DumpProcessDeg d = DumpProcess;
d.BeginInvoke(vProcess.Id, null, null);
DumpProcess(vProcess.Id);
}
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
}
private delegate void DumpProcessDeg(int pid);
static void DumpProcess(int pid)
{
ProcessStartInfo Info = new System.Diagnostics.ProcessStartInfo();
Info.FileName = _adplusPath;
Info.Arguments = string.Format("-crash -p {0} -quiet",pid);
Info.WorkingDirectory = "C:\\";
Process Proc ;
try
{
Proc = Process.Start(Info);
}
catch(System.ComponentModel.Win32Exception e)
{
Console.WriteLine("pȝ找不到指定的E序文g。\r{0}", e);
return;
}
Proc.EnableRaisingEvents = true;
Console.WriteLine("外部E序的开始执行时_{0}", Proc.StartTime);
Proc.WaitForExit(60000);
if(Proc.HasExited == false){
Console.WriteLine("׃E序l止外部E序的运行!");
Proc.Kill();
}
else{
Console.WriteLine("由外部程序正帔R出!");
}
Console.WriteLine("外部E序的结束运行时_{0}", Proc.ExitTime);
Console.WriteLine("外部E序在结束运行时的返回|{0}", Proc.ExitCode);
}
}
}
===============
补充几个命o:
1?analyze -v Q用于分析挂掉线E的详细情ŞQ错误原因?/p>
2?locks Q列出全部资源用情c?/p>
3?locks -v 0x???????? Q特定地址的死锁分析?/p>
4?thread 0x????????Q特定线E详情?/p>
5?thread 0x????????Q{到某个线E堆栈?br>
要消除窗口对?必须清楚H口对象的构?在一个通常的程序中Q先创徏c++H口对象,然后由Windows创徏实际的窗口结?q返回句柄与c++对象q接.也就是说,H口对象包含c++H口对象和WindowsH口对象,两者通过句柄HWND联系.
现在,让我们看?正规"的窗口对象清除流E?所谓对象的清除是指释放对象所占的资源,H口对象中WindowsH口对象占有的是pȝ资源,c++对象占有的是内存资源.释放pȝ资源相对要简单一?调用虚函数DestroyWindow删除WindowsH口对象.如果DestroyWindow删除的是父窗?Windows会自动ؓ子窗口调用DestroyWindow.一般来?E序不必调用DestroyWindow.因ؓ当用户关闭窗口时,Windows便发送WM_CLOSE消息,WM_CLOSE的缺省消息处理函数CWnd::OnClose调用DestroyWindow.
到这?清除工作已经完成了一?屏幕上的H口已经不见?但是别忘?在内存中q有一个c++H口对象.让我们再看看c++对象清除的过E?当窗口被取消?H口最后发送的一个消息是WM_NCDESTROY.它缺省的消息处理函数CWnd::OnNcDestroy把c++H口对象与句柄HWND分离,q调用一个很重要的虚函数PostNcDestroy.q个函数是搞清窗口对象清除的关键.Cwnd中的PostNcDestroy什么都不做.有些MFCH口cM重蝲?q加入delete this代码删除c++对象.q些H口cd常是以new操作W徏立在堆中?׃重蝲了PostNcDestroy,使窗口有自动清除功能.因此,我们不用兛_清除问题?另外的一些MFCH口cM般是以变量Ş式创建的,MFC没有Z没必要ؓ它们重蝲PostNcDestroy函数.
不具备自动清除功能的H口c?一般在堆栈中创建或嵌入于其它c++对象?
所有标准的Windows控gc?如CStatic, CEdit, CListBox{等)
由CWndcȝ接派生出来的子窗口对?如用户定制的控g)
拆分H口c?CSplitterWnd)
~省的控制条c?CControlBar的派生类)
对话框类(CDialog)在堆栈上创徏的模态对话框c?/p>
所有的Windows通用对话?除CFindReplaceDialog)
由ClassWizard创徏的对话框
h自动清除功能的窗口类,一般在堆中创徏:
L架窗口类(直接或间接从CFrameWndcL?
视图c?直接或间接从CViewcL?
从某U程度上来说,MFC?服务到家"使初学者有些找不着?不过,不得不承?MFCq的很漂?
谈到q里,我们应该明白c++里一条重要的准则:用DestroyWindow清除H口对象,不要?delete".
对于不具备自动清除功能的H口cM?delete"?"delete"先调用析构函数里的DestroyWindow,׃在析构函C,虚机制不起作?q里只能调用本地版本(Cwndc?DestroyWindow函数,昄q不是我们想要的.对于有自动清除功能的H口c?好象问题更严重一?前面提到了重载的PostNcDestroy已经含有?delete this",q样c++对象p释放了两?
很多?vc++同vb一?是一个完全可视化的?不用在看c++的书?通过上面对窗口对象的清除的介l?可以发现,WindowsE序是与Windows紧密l合?而且牉|到很多c++的知?如虚函数、析构函数、new操作W等).要对vc++有进一步理?必须理解Windows机制,深入学习c++.
一、与加速键表有关的几个API函数和结构?/p>
操作加速键表用的几个API函数Q关于这几个函数的详l说明请参考有关书c)Q?br> HACCEL LoadAccelerators(HINSTANCE hInstance, LPCTSTR lpTableNAme);
LoadAccelerators函数从程序的资源中加载一?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表,加蝲成功后返回一个加速健表的句柄。其中:
hInstance 应用E序的实例句柄?br> lpTableName 指向加速键表名U字W串的指针?/p>
HACCEL CreateAcceleratorTable(LPACCEL lpaccl, int cEntries);
CreateAcceleratorTable函数Ҏ一个ACCELl构数组创徏一?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表。该函数与LoadAccelerators不同的是QLoadAccelerators函数加蝲?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表在E序l束后系l会自动该加速键表从内存中清除,但CreateAcceleratorTable函数创徏?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表需要用函数DestoryAceleratorTable函数q行清除?br> lpaccl 一个指向ACCELl构数组的指针?br> cEntries 数组中元素的个数?/p>
BOOL DestoryAcceleratorTable(HACCEL hAccel);
DestoryAcceleratorTable函数清除由CreateAcceleratorTable函数创徏?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表,成功则返回TRUE。其中:
hAccel 需要清除的加速键表句柄?/p>
int TranslateAccelerator(HWND hWd, HACCEL hAccTable, LPMSG lpMsg);
TranslateAccelerator函数负责译加速键。其中:
hWnd H口句柄Q翻译后的消息将被发往该窗?br> hAccTable 加速键表句柄?br> lpMsg 指向MSGl构的指针?/p>
ACCELl构的定义:
typedef struct tagACCEL{
BYTE fVirt;
WORD key;
WORD cmd;
}ACCEL,*LPACCEL;
其中Q?br> fVirt 加速键的标记?br> key 键的代码。如fVirt成员包含FVIRTKEY标志Q则key指一个虚键码Q否则是一个ASCII码?br> cmd 命oIDP该参数将被放入WMQCOMMAND或WMQSYSCOMMAND消息的wParam参数的低位字发至H口?/p>
二、在windows下如何?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表?/p>
在window下?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表一般有两种ҎQ?Q创Z?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键资源Q在E序中用API函数LoadAccelerators来将加速键表加载入内存。ƈ在消息@环中使用API函数TranslateAccelerator来翻译该加速键表?、在E序中填充一个ACCEL数组。然后调用API函数CreateAcceleratorTable来创建加速表Q翻?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键同上Q但不要忘记在退出程序前使用API函数DestoryAcceleratorTable来清除它。下面分别给Z个例子:
/*?:使用LoadAccelerators?br> 假设你已l徏立了一?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键资源QID为IDRQACCEL?br> 假设你已l定义了初始化函数InitApplication(HINSTANCE hInstance,int nCmdShow),
该函数执行注册窗口类和创建窗口操作?br>*/
#include <windows.h>
#include "rc/resource.h"
BOOL InitApplication(HINSTANCE hInstance,int nCmdShow);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HANDLE hAccelTable;
// 初始化应用程序,q生成主H口.
if (!InitApplication(hInstance, nCmdShow))
{
return FALSE; // 初始化失?br> }
//使用函数LoadAccelerators从程序资源中加蝲加速键?br> hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCEL));
// 取得q分发消息直到接收到 WM_QUIT 消息.
while (GetMessage(&msg, NULL, 0, 0))
{
//在分发消息前首先试着?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表进行翻译,如果是一?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键则由
//TranslateAccelerator函数q行译Q不再l处理该消息?br> if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam; // Returns the value from PostQuitMessage
}
/*?:使用CreateAcceleratorTable?br> 假设你已l定义了初始化函数InitApplication(HINSTANCE hInstance,int nCmdShow),
该函数执行注册窗口类和创建窗口操作?br>*/
#include <windows.h>
#include "rc/resource.h"
Qdefine ID_CMD_A 0x00000230
Qdefine ID_CMD_B 0x00000231
Qdefine ID_CMD_C 0x00000232
Qdefine ID_CMD_D 0x00000233
Qdefine ID_CMD_E 0x00000234
Qdefine ID_CMD_F 0x00000235
Qdefine ID_CMD_G 0x00000236
//定义了七?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键,请在消息回调函数中处理这七个命oID?br>static ACCEL accel[]={
{FVIRTKEY|FCONTROL,VK_F5,ID_CMD_A},
{FVIRTKEY|FCONTROL,VK_F6,ID_CMD_B},
{FVIRTKEY|FCONTROL,VK_HOME,ID_CMD_C},
{FVIRTKEY|FCONTROL,VK_END,ID_CMD_D},
{FVIRTKEY|FCONTROL,"G",ID_CMD_E},
{FVIRTKEY|FCONTROL,VK_SPACE,ID_CMD_F},
{FVIRTKEY|FCONTROL,"K",ID_CMD_G},
};
BOOL InitApplication(HINSTANCE hInstance,int nCmdShow);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HANDLE hAccelTable;
// 初始化应用程序,q生成主H口.
if (!InitApplication(hInstance, nCmdShow))
{
return FALSE; // 初始化失?br> }
//使用函数CreateAcceleratorTable从数laccel中加?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键?br> hAccelTable = CreateAcceleratorTable(accel, sizeof(accel)/sizeof(ACCEL));
// 取得q分发消息直到接收到 WM_QUIT 消息.
while (GetMessage(&msg, NULL, 0, 0))
{
//在分发消息前首先试着?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表进行翻译,如果是一?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键则由
//TranslateAccelerator函数q行译Q不再l处理该消息?br> if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
//删除加速键?br> DestoryAcceleratorTable(hAccelTable);
return msg.wParam; // Returns the value from PostQuitMessage
}
在MFCE序设计中,有关?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表的操作已经被CFrameWndc进行了装。通常Q我们的E序的主框架cCMainFrame从CFrameWndcL生(SDI界面E序Q,或者从CMDIFrameWndcL生(MDI界面E序Q,而CMDIFrameWndcM是从CFrameWndcL生的。所以,我们q不用去兛_那个资源号ؓIDR_MAINFRAME?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表是如何加蝲的,如果你只是需要一个这L静态的加速键表的话?br> 那么我们能不能用自q加速键表呢Q答案是Q可以的?br> 创徏加速键表的Ҏ同前。由于在MFC中,WinMain函数被隐藏,我们不能直接修改WinMain函数Q所以,加速键表的创徏在CMainFrame的OnCreate函数中创建?br> 1、在CMainFramecMd一个HACCELcd保护成员变量Qm_hMyAccel。在构造函C初始化ؓNULL?br> 2、在CMainFrame::OnCreate函数?“return 0;”句前增加创徏加速键表的代码。返回的加速键表句柄保存在m_hMyAccel中:
Ҏ1Q?IDRQMYACCELZ定义?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表资源号)
m_hMyAccel=LoadAccelerators(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDR_MYACCEL));
Ҏ2Q?accel同前面的例子一样在本文件的开头部分进行定?
m_hMyAccel=CreateAcceleratorTable(accel,sizeof(accel)/sizeof(ACCEL));
3、如果是使用CreateAccelTable函数创徏的,则重载虚拟函数DestoryWindow()。在该函数的“return CMDIFrameWnd::DestroyWindow();”前增加如下代?
if(m_hMyAccel!=NULL){
DestroyAcceleratorTable(m_hMyAccel);
m_hMyAccel=NULL;
}
通过前面的三步,加速键表已l能正确地被创徏和删除了Q但是它q没有工作。下面就是要让我们刚才所创徏?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表工作v来?br> 在APIE序设计中大家已l知道了加速键是如何工作的。也是在消息还没有分发出去之前先检查是不是一?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键。我们通过API函数TranslateAccelerator来实现。在MFC中,消息机制已经被封装了Q我们不能去修改消息循环。但是,框架在分发消息前会调用虚拟函数PerTranslateMessageQƈ且如果该函数q回TRUEQ则不再处理该消息。这正是我们所需要的?br> 让我们再回到CMainFramec,生成PerTranslateMessage函数的覆盖版本。修改函C如下Q?/p>
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
if(m_hMyAccel&&TranslateAccelerator(m_hWnd, m_hMyAccel, pMsg))
return TRUE;
return CMDIFrameWnd::PreTranslateMessage(pMsg);
}
好了Q现在我们的加速键已经能正常地工作了。以上方法用在其它窗口同h效(如对话框中,你不妨在对话框中试试Q?/p>
三、可在运行时~辑?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表?/p>
通过上面的叙qͼ你可能已l对可编辑的加速键表有了一定的轮廓。其实其实现思想很简单:只要对一个ACCEL数组q行修改Q然后重新生?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表就行了?br> Z区分命oQ我们ؓ每个命o取一个名字。在CMainFramecd义的前面加上下面的一个结构定义:
typedef struct{
char cCmd[32];
ACCEL accel;
}ACCELITEM,*LPACCELITEM;
我们用该l构来保?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表的数据。在CMainFramecMd两个保护成员变量Q?/p>
LPACCELITEM m_lpAccel;
DWORD m_dwAccelCount;
在构造函Cm_lpAccel初始化ؓNULLQm_dwAccelCount初始化ؓ0。在数组accel的定义下面增加一个字W串数组的静态变量的定义Q用来指定命令的名称,请仿照下面自己定义,个数多少不限Q字W串长度不要过31个字W)Q?/p>
static char strCmd[][32]={
"Command One",
"Command Two",
"Command Three",
"Command Four",
"Command Five"
};
在CMainFramecMd一个保护成员函数LoadAccel(),该函数用来将加速键表装入,定义如下Q?/p>
BOOL CMainFrame::LoadAccel()
{
ASSERT(m_hActAccel==NULL);
ASSERT(m_lpAccel==NULL);
m_dwAccelCount=sizeof(accel)/sizeof(ACCEL);
m_lpAccel=new ACCELITEM[m_dwAccelCount];
memset(m_lpAccel,0,sizeof(ACCELITEM)*m_dwAccelCount);
DWORD dwCmdStr=sizeof(strCmd)/sizeof(char[32]);
for(DWORD dw=0;dw<m_dwAccelCount;dw++){
m_lpAccel[dw].accel=accel[dw];
strcpy(m_lpAccel[dw].cCmd,dw<dwCmdStr?strCmd[dw]:"Command Unknow");
}
m_hActAccel=CreateAcceleratorTable(accel,m_dwAccelCount);
return TRUE;
}
删除OnCreate函数中原来创?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表的代码Q改成对LoadAccel()的调用:
LoadAccel();
在CMainFrame的析构函C增加以下内容Q?/p>
if(m_lpAccel)
delete[] m_lpAccel;
Z能够?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键数据q行~辑Q我们在工程中添加一个对话框资源QID?#8220;IDD_ACCELEDIT”Q在对话框上攄三个成组框(Group BoxQ,左边一个标题ؓ“命o”Q中间一个标题ؓ“l合?#8221;Q右边一个标题ؓ“虚键?#8221;。在左边成组框中攄一个列表框QList BoxQ,ID?#8220;IDC_LST_CMD”。在中间成组框中攄三个栔R框QCheck BoxQ,上下排列。上面的栔R框的ID?#8220;IDC_CHK_ALT”Q标题ؓ“Alt”QƈN?#8220;Group”属性;中间的核选框的ID?#8220;IDC_CHK_SHIFT”Q标题ؓ“Shift”Q取?#8220;Group”属性;下面的核选框的ID?#8220;IDC_CHK_CTRL”Q标题ؓ“Ctrl”Q取?#8220;Group”属性。在双的成l框中放|一个列表框QID?#8220;IDC_LST_KEY”。调整各个控件的寸及位|至合适。设|控件的TAB跌{序Q从左至叟뀁从上到下。依ơؓQ左成组框、IDC_LST_CMD列表框、中成组框、IDC_CHK_ALT栔R框、IDC_CHK_SHIFT栔R框、IDC_CHK_CTRL栔R框、右成组框、IDC_LST_KEY列表框、OK按钮、CANCEL按钮?br> 打开cd|为对话框新徏一个类Q类名ؓ“CDlgEditAccel”。ؓ两个列表框和三个栔R框映射变量Q如下:
控gID:ID_LST_CMDQ类?Control/CListBoxQ名U?m_LstCmdQ?br> 控gID:ID_LST_KEYQ类?Control/CListBoxQ名U?m_LstKeyQ?br> 控gID:ID_CHK_ALTQ类?Control/CButtonQ名U?m_ChkAltQ?br> 控gID:ID_CHK_SHIFTQ类?Control/CButtonQ名U?m_ChkShiftQ?br> 控gID:ID_CHK_CTRLQ类?Control/CButtonQ名U?m_ChkCtrlQ?/p>
在类CDlgEditAccel的定义前加入下面的定义:
typedef struct tag_KeyName{
CString m_strName;
WORD m_wID;
tag_KeyName(CString str,WORD id){m_strName=str,m_wID=id;}
}KEYNAME,*LPKEYNAME;
typedef struct tag_ActAccel{
CString m_strCmd;
ACCEL m_Accel;
}ACTACCEL,*LPACTACCEL;
class CActAccelList
{
public:
CActAccelList();
~CActAccelList();
protected:
LPACTACCEL m_lpActAccel;
int m_iCount;
public:
ACTACCEL& operator[](int index);
BOOL SetSize(int iSize);
int GetSize();
};
在文件DlgEditAccel.cpp文g中定义类CActAccelList的成员函敎ͼ代码如下Q?br>CActAccelList::CActAccelList()
{
m_lpActAccel=NULL;
m_iCount=0;
}
CActAccelList::~CActAccelList()
{
if(m_iCount>0&&m_lpActAccel)
delete[] m_lpActAccel;
}
int CActAccelList::GetSize()
{
return m_iCount;
}
ACTACCEL& CActAccelList::operator []( int index)
{
if(!(index>=0&&index<m_iCount))
AfxThrowMemoryException();
return m_lpActAccel[index];
}
BOOL CActAccelList::SetSize(int iSize)
{
if(iSize<0)
return FALSE;
if(m_iCount>0&&m_lpActAccel)
delete[] m_lpActAccel;
m_iCount=0;
m_lpActAccel=new ACTACCEL[iSize];
if(m_lpActAccel==NULL)
return FALSE;
m_iCount=iSize;
return TRUE;
}
在DlgAccelEdit.cpp文g头部定义全局变量Q-数组keyQ?br>static KEYNAME key[]={
KEYNAME("Left mouse button",VK_LBUTTON),
KEYNAME("Right mouse button",VK_RBUTTON),
KEYNAME("Control-break processing",VK_CANCEL),
KEYNAME("Middle mouse button",VK_MBUTTON),
KEYNAME("Back Space",VK_BACK),
KEYNAME("Tab",VK_TAB),
KEYNAME("Clear",VK_CLEAR),
KEYNAME("Enter",VK_RETURN),
KEYNAME("Shift",VK_SHIFT),
KEYNAME("Ctrl",VK_CONTROL),
KEYNAME("Alt",VK_MENU),
KEYNAME("Pause",VK_PAUSE),
KEYNAME("Caps Lock",VK_CAPITAL),
KEYNAME("Esc",VK_ESCAPE),
KEYNAME("Space",VK_SPACE),
KEYNAME("Page Up",VK_PRIOR),
KEYNAME("Page Down",VK_NEXT),
KEYNAME("End",VK_END),
KEYNAME("Home",VK_HOME),
KEYNAME("Left",VK_LEFT),
KEYNAME("Up",VK_UP),
KEYNAME("Right",VK_RIGHT),
KEYNAME("Down",VK_DOWN),
KEYNAME("Select",VK_SELECT),
KEYNAME("Excute",VK_EXECUTE),
KEYNAME("Print Screen",VK_SNAPSHOT),
KEYNAME("Insert",VK_INSERT),
KEYNAME("Delete",VK_DELETE),
KEYNAME("Help",VK_HELP),
KEYNAME("0",'0'),
KEYNAME("1",'1'),
KEYNAME("2",'2'),
KEYNAME("3",'3'),
KEYNAME("4",'4'),
KEYNAME("5",'5'),
KEYNAME("6",'6'),
KEYNAME("7",'7'),
KEYNAME("8",'8'),
KEYNAME("9",'9'),
KEYNAME("A",'A'),
KEYNAME("B",'B'),
KEYNAME("C",'C'),
KEYNAME("D",'D'),
KEYNAME("E",'E'),
KEYNAME("F",'F'),
KEYNAME("G",'G'),
KEYNAME("H",'H'),
KEYNAME("I",'I'),
KEYNAME("J",'J'),
KEYNAME("K",'K'),
KEYNAME("L",'L'),
KEYNAME("M",'M'),
KEYNAME("N",'N'),
KEYNAME("O",'O'),
KEYNAME("P",'P'),
KEYNAME("Q",'Q'),
KEYNAME("R",'R'),
KEYNAME("S",'S'),
KEYNAME("T",'T'),
KEYNAME("U",'U'),
KEYNAME("V",'V'),
KEYNAME("W",'W'),
KEYNAME("X",'X'),
KEYNAME("Y",'Y'),
KEYNAME("Z",'Z'),
KEYNAME("Left windows",VK_LWIN),
KEYNAME("Right windows",VK_RWIN),
KEYNAME("Applications",VK_APPS),
KEYNAME("Numeric keypad 0", VK_NUMPAD0),
KEYNAME("Numeric keypad 1", VK_NUMPAD1),
KEYNAME("Numeric keypad 2", VK_NUMPAD2),
KEYNAME("Numeric keypad 3", VK_NUMPAD3),
KEYNAME("Numeric keypad 4", VK_NUMPAD4),
KEYNAME("Numeric keypad 5", VK_NUMPAD5),
KEYNAME("Numeric keypad 6", VK_NUMPAD6),
KEYNAME("Numeric keypad 7", VK_NUMPAD7),
KEYNAME("Numeric keypad 8", VK_NUMPAD8),
KEYNAME("Numeric keypad 9", VK_NUMPAD9),
KEYNAME("Multiply",VK_MULTIPLY),
KEYNAME("Add",VK_ADD),
KEYNAME("Separator",VK_SEPARATOR),
KEYNAME("Subtract",VK_SUBTRACT),
KEYNAME("Decimal Point",VK_DECIMAL),
KEYNAME("Divide",VK_DIVIDE),
KEYNAME("F1",VK_F1),
KEYNAME("F2",VK_F2),
KEYNAME("F3",VK_F3),
KEYNAME("F4",VK_F4),
KEYNAME("F5",VK_F5),
KEYNAME("F6",VK_F6),
KEYNAME("F7",VK_F7),
KEYNAME("F8",VK_F8),
KEYNAME("F9",VK_F9),
KEYNAME("F10",VK_F10),
KEYNAME("F11",VK_F11),
KEYNAME("F12",VK_F12),
KEYNAME("F13",VK_F13),
KEYNAME("F14",VK_F14),
KEYNAME("F15",VK_F15),
KEYNAME("F16",VK_F16),
KEYNAME("F17",VK_F17),
KEYNAME("F18",VK_F18),
KEYNAME("F19",VK_F19),
KEYNAME("F20",VK_F20),
KEYNAME("F21",VK_F21),
KEYNAME("F22",VK_F22),
KEYNAME("F23",VK_F23),
KEYNAME("F24",VK_F24),
KEYNAME("Attn",VK_ATTN),
KEYNAME("CrSel",VK_CRSEL),
KEYNAME("ExSel",VK_EXSEL),
KEYNAME("Erase",VK_EREOF),
KEYNAME("Play",VK_PLAY),
KEYNAME("Zoom",VK_ZOOM),
KEYNAME("Reserved for future use",VK_NONAME ),
KEYNAME("PA1",VK_PA1),
KEYNAME("Clear(OEM)",VK_OEM_CLEAR ),
KEYNAME("file://",'//'),
KEYNAME("-",'-'),
KEYNAME("=",'='),
KEYNAME("[",'['),
KEYNAME("]",']'),
KEYNAME(";",';'),
KEYNAME("\'",'\''),
KEYNAME(",",','),
KEYNAME(".",'.'),
KEYNAME("/",'/'),
KEYNAME("`",'`')
};
在类CDlgAccelEdit中响应Windows消息QWM_INITDIALODQ代码如下:
BOOL CDlgEditAccel::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
SetWindowText("Edit Accelerator Table");
int iCount=m_AccelList.GetSize();
int i;
for(i=0;i<iCount;i++){
m_LstCmd.AddString(m_AccelList[i].m_strCmd);
}
for(i=0;i<sizeof(key)/sizeof(KEYNAME);i++)
{
m_LstKey.AddString(key[i].m_strName);
}
m_LstCmd.SetCurSel(0);
OnSelchangeLstCmd();
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
为类CDglAccelEdit增加一个保护成员函敎ͼ
void SaveChange(int index=-1);
在文件DlgAccelEdit中定义该函数Q代码如下:
void CDlgEditAccel::SaveChange(int index)
{
if(index>=0||(index=m_LstCmd.GetCurSel())>=0)
{
if(m_LstKey.GetCurSel()<0){
AfxMessageBox("你必需选择一个键?");
return;
}
BYTE btCmp=((m_ChkAlt.GetCheck()==1)?FALT:NULL)|
((m_ChkCtrl.GetCheck()==1)?FCONTROL:NULL)|
((m_ChkShift.GetCheck()==1)?FSHIFT:NULL)|FVIRTKEY;
WORD wKey=key[m_LstKey.GetCurSel()].m_wID;
m_AccelList[index].m_Accel.fVirt=btCmp;
m_AccelList[index].m_Accel.key=wKey;
return;
}
}
响应列表框ID_LST_CMD的通知消息“LBN_SELCHANGE”Q函C码如下:
void CDlgEditAccel::OnSelchangeLstCmd()
{
// TODO: Add your control notification handler code here
int iCmd=m_LstCmd.GetCurSel();
WORD wKey=m_AccelList[iCmd].m_Accel.key;
BYTE btCmp=m_AccelList[iCmd].m_Accel.fVirt;
m_ChkAlt.SetCheck(btCmp&FALT);
m_ChkCtrl.SetCheck(btCmp&FCONTROL);
m_ChkShift.SetCheck(btCmp&FSHIFT);
int iCount=sizeof(key)/sizeof(KEYNAME);
int id=-1;
for(int i=0;i<iCount;i++)
{
if(key[i].m_wID==wKey){
id=i;
break;
}
}
m_LstKey.SetCurSel(id);
}
响应列表框ID_LST_KEY的通知消息“LBN_SELCHANGE”Q函C码如下:
void CDlgEditAccel::OnSelchangeLstKey()
{
SaveChange();
}
响应栔R框ID_CHK_ALT的通知消息“BN_CLICKED”Q函C码如下:
void CDlgEditAccel::OnChkAlt()
{
SaveChange();
}
响应栔R框ID_CHK_SHIFT的通知消息“BN_CLICKED”Q函C码如下:
void CDlgEditAccel::OnChkShift()
{
SaveChange();
}
响应栔R框ID_CHK_CTRL的通知消息“BN_CLICKED”Q函C码如下:
void CDlgEditAccel::OnChkCtrl()
{
SaveChange();
}
xQ用于编?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键的对话框已经完成。我们现在要做的是在程序中打开对话框来~辑了。让我们回到CMainFramecM。我们将在该cM响应一个命令来打开~辑对话框,?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表进行编辑。完成后ҎOK回到ȝ序中Q然后更?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表。这样后Q我们刚刚编辑好?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表就开始v作用了?br> 首先Q打开菜单。在“查看”后增加一?#8220;工具(&T)”下拉菜单Q在下拉菜单中增加一个了菜单Q?#8220;~辑加速键?&A)...”QID?#8220;ID_TOOL_ACCELEDIT”。打开cd|选择CMainFramec,响应“ID_TOOL_ACCELEDIT”命o。编辑响应函数如下:
void CMainFrame::OnToolAcceledit()
{
// TODO: Add your command handler code here
CDlgEditAccel dlg;
DWORD i;
dlg.m_AccelList.SetSize(m_dwAccelCount);
for(i=0;i<m_dwAccelCount;i++){
dlg.m_AccelList[i].m_strCmd=m_lpAccel[i].cCmd;
dlg.m_AccelList[i].m_Accel=m_lpAccel[i].accel;
}
if(IDOK==dlg.DoModal()){
ACCEL* pAccel=new ACCEL[m_dwAccelCount];
for(DWORD dw=0;dw<m_dwAccelCount;dw++){
m_lpAccel[dw].accel=pAccel[dw]=dlg.m_AccelList[dw].m_Accel;
}
if(m_hActAccel!=NULL)
DestroyAcceleratorTable(m_hActAccel);
m_hActAccel=CreateAcceleratorTable(pAccel,m_dwAccelCount);
}
}
在该文g的头部包含类CDlgEditAccel的头文gQ?/p>
#include "DlgEditAccel.h"
xQ可~辑?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表已l完成了。不妨试试看?/p>
三、将加速键表保存至文gQƈ在程序运行时自动从文件中加蝲?/p>
上面我们实现了可~辑?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表。但是,当程序退出后Q我们编辑过?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键数据消׃Q下ơ运行程序时q是和以前一样了。将加速键表保存v来以备下ơ用,q是我们所希望的。下面讨论的Ҏ是用文件来保存我们?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表?br> 首先Q在MainFrm.cpp文g的头部定义一个全局字符Ԍ也就是用于保?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键的文件名Q?/p>
static char strAccelFileName[]="Accel.cus";
定义一个DWORDg为文件头Q我们通过该DWORD值来判断是否是一个有效格式的文gQ?/p>
#define ACCEL_FILE_HEAD 0x00434341
其次QؓCMainFramecL加一个保护的成员函数Q?#8220;BOOL SaveAccel()”Q编辑其代码如下Q?br>BOOL CMainFrame::SaveAccel()
{
char lpAppName[256];
char lpAppPath[256];
char* lpFilePart=NULL;
GetModuleFileName(AfxGetInstanceHandle(),lpAppName,255);
GetFullPathName(lpAppName,255,lpAppPath,&lpFilePart);
TRACE1("Application File Name:%s.\n",lpAppName);
strcpy(lpFilePart,strAccelFileName);
TRACE1("Accelerator File Name:%s.\n",lpAppPath);
try
{
DWORD dwHead=ACCEL_FILE_HEAD;
DWORD dwCount=m_dwAccelCount;
CFile fAccel(lpAppPath,CFile::modeCreate|CFile::modeWrite);
fAccel.SeekToBegin();
fAccel.Write(&dwHead,sizeof(DWORD));
fAccel.Write(&dwCount,sizeof(DWORD));
for(DWORD dw=0;dw<dwCount;dw++)
{
fAccel.Write(m_lpAccel+dw,sizeof(ACCELITEM));
}
return TRUE;
}
catch(CFileException* e)
{
char buf[256];
char buf2[512];
int iError;
iError=e->m_cause;
e->GetErrorMessage(buf,256);
sprintf(buf2,"?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表保存到文g \"%s\" 中时发生错误!\n"
"???%d\n"
"错误描述:%s\n"
"\0",
strAccelFileName,iError,buf);
AfxMessageBox(buf2,MB_OK|MB_ICONSTOP|MB_DEFBUTTON1);
return FALSE;
}
}
再次Q修改LoadAccel()函数如下Q?/p>
BOOL CMainFrame::LoadAccel()
{
char lpAppName[256];
char lpAppPath[256];
char* lpFilePart=NULL;
GetModuleFileName(AfxGetInstanceHandle(),lpAppName,255);
GetFullPathName(lpAppName,255,lpAppPath,&lpFilePart);
TRACE1("Application File Name:%s.\n",lpAppName);
strcpy(lpFilePart,strAccelFileName);
TRACE1("Accelerator File Name:%s.\n",lpAppPath);
try
{
DWORD dwHead;
DWORD dwCount;
CFile fAccel(lpAppPath,CFile::modeRead);
fAccel.SeekToBegin();
if(fAccel.Read(&dwHead,sizeof(DWORD))!=sizeof(DWORD))
AfxThrowFileException(CFileException::endOfFile,12,lpAppPath);
if(dwHead!=ACCEL_FILE_HEAD)
AfxThrowFileException(CFileException::invalidFile,13,lpAppPath);
if(fAccel.Read(&dwCount,sizeof(DWORD))!=sizeof(DWORD))
AfxThrowFileException(CFileException::endOfFile,12,lpAppPath);
ASSERT(m_lpAccel==NULL);
m_lpAccel=new ACCELITEM[dwCount];
memset(m_lpAccel,0,sizeof(ACCELITEM)*dwCount);
m_dwAccelCount=dwCount;
for(DWORD dw=0;dw<dwCount;dw++)
{
if(fAccel.Read(m_lpAccel+dw,sizeof(ACCELITEM))!=sizeof(ACCELITEM))
AfxThrowFileException(CFileException::endOfFile,12,lpAppPath);
}
ACCEL* pAccel=new ACCEL[dwCount];
for(dw=0;dw<dwCount;dw++){
pAccel[dw]=m_lpAccel[dw].accel;
}
m_hActAccel=CreateAcceleratorTable(pAccel,dwCount);
return TRUE;
}
catch(CFileException* e)
{
char buf[256];
char buf2[512];
int iError;
iError=e->m_cause;
e->GetErrorMessage(buf,256);
sprintf(buf2,"从文?\"%s\" 中加?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表时发生错误!\n"
"???%d\n"
"错误描述:%s\n"
"是否生成默认?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键?\n\0",
strAccelFileName,iError,buf);
if(IDYES==AfxMessageBox(buf2,MB_YESNO|MB_SYSTEMMODAL|MB_ICONSTOP|MB_DEFBUTTON1))
{
ASSERT(m_hActAccel==NULL);
ASSERT(m_lpAccel==NULL);
m_dwAccelCount=sizeof(accel)/sizeof(ACCEL);
m_lpAccel=new ACCELITEM[m_dwAccelCount];
memset(m_lpAccel,0,sizeof(ACCELITEM)*m_dwAccelCount);
DWORD dwCmdStr=sizeof(strCmd)/sizeof(char[30]);
for(DWORD dw=0;dw<m_dwAccelCount;dw++){
m_lpAccel[dw].accel=accel[dw];
strcpy(m_lpAccel[dw].cCmd,dw<dwCmdStr?strCmd[dw]:"Command Unknow");
}
m_hActAccel=CreateAcceleratorTable(accel,m_dwAccelCount);
SaveAccel();
return TRUE;
}
return FALSE;
}
}
最后,在DestroyWindow()函数头部增加对SaveAccel()函数的调用:
SaveAccel();
...
好了Q你自己?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键数据已经能保存在文g中了Qƈ能从中正加载。如果文件不存在或程序读取时发现错误则提醒你是否建立~省?strong style="COLOR: black; BACKGROUND-COLOR: #a0ffff">加速键表,如你认的话则生成缺省的加速键表ƈ立刻保存x件中?br> ׃水^有限Q其中难免有误,Ƣ迎批评指正、发表你的意见。本Z胜感Ȁ?br>