road420

导航

<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

统计

常用链接

留言簿(2)

随笔档案

文章档案

搜索

最新评论

阅读排行榜

评论排行榜

#

进程通信

Win32应用程序中进程间通信方法分析与比较

来源:Intetnet

1 进程与进程通信

进程是装入内存并准备执行的程序,每个进程都有私有的虚拟地址空间,由代码、数据以及它可利用的系统资源(如文件、管道等)组成。多进程/多线程是Windows操作系统的一个基本特征。Microsoft Win32应用编程接口(Application Programming Interface, API)提供了大量支持应用程序间数据共享和交换的机制,这些机制行使的活动称为进程间通信(InterProcess Communication, IPC),进程通信就是指不同进程间进行数据共享和数据交换。
正因为使用Win32 API进行进程通信方式有多种,如何选择恰当的通信方式就成为应用开发中的一个重要问题,下面本文将对Win32中进程通信的几种方法加以分析和比较。

2 进程通信方法

2.1

文件映射


文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作,只需简单的指针操作就可读取和修改文件的内容。
Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。
应用程序有三种方法来使多个进程共享一个文件映射对象。
(1)继承:第一个进程建立文件映射对象,它的子进程继承该对象的句柄。
(2)命名文件映射:第一个进程在建立文件映射对象时可以给该对象指定一个名字(可与文件名不同)。第二个进程可通过这个名字打开此文件映射对象。另外,第一个进程也可以通过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。
(3)句柄复制:第一个进程建立文件映射对象,然后通过其它IPC机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。
文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步
2.2

共享内存


Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操作系统页面文件访问内存,其它进程打开该文件映射对象就可以访问该内存块。由于共享内存是用文件映射实现的,所以它也有较好的安全性,也只能运行于同一计算机上的进程之间。
2.3

匿名管道


管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一管道的两端点既可读也可写。
匿名管道(Anonymous Pipe)是在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。
匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。
2.4

命名管道


命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
2.5

邮件槽


邮件槽(Mailslots)提供进程间单向通信能力,任何进程都能建立邮件槽成为邮件槽服务器。其它进程,称为邮件槽客户,可以通过邮件槽的名字给邮件槽服务器进程发送消息。进来的消息一直放在邮件槽中,直到服务器进程读取它为止。一个进程既可以是邮件槽服务器也可以是邮件槽客户,因此可建立多个邮件槽实现进程间的双向通信。
通过邮件槽可以给本地计算机上的邮件槽、其它计算机上的邮件槽或指定网络区域中所有计算机上有同样名字的邮件槽发送消息。广播通信的消息长度不能超过400字节,非广播消息的长度则受邮件槽服务器指定的最大消息长度的限制。
邮件槽与命名管道相似,不过它传输数据是通过不可靠的数据报(如TCP/IP协议中的UDP包)完成的,一旦网络发生错误则无法保证消息正确地接收,而命名管道传输数据则是建立在可靠连接基础上的。不过邮件槽有简化的编程接口和给指定网络区域内的所有计算机广播消息的能力,所以邮件槽不失为应用程序发送和接收消息的另一种选择。
2.6

剪贴板


剪贴板(Clipped Board)实质是Win32 API中一组用来传输数据的函数和消息,为Windows应用程序之间进行数据共享提供了一个中介,Windows已建立的剪切(复制)-粘贴的机制为不同应用程序之间共享不同格式数据提供了一条捷径。当用户在应用程序中执行剪切或复制操作时,应用程序把选取的数据用一种或多种格式放在剪贴板上。然后任何其它应用程序都可以从剪贴板上拾取数据,从给定格式中选择适合自己的格式。
剪贴板是一个非常松散的交换媒介,可以支持任何数据格式,每一格式由一无符号整数标识,对标准(预定义)剪贴板格式,该值是Win32 API定义的常量;对非标准格式可以使用Register Clipboard Format函数注册为新的剪贴板格式。利用剪贴板进行交换的数据只需在数据格式上一致或都可以转化为某种格式就行。但剪贴板只能在基于Windows的程序中使用,不能在网络上使用。
2.7 动态数据交换
动态数据交换(DDE)是使用共享内存在应用程序之间进行数据交换的一种进程间通信形式。应用程序可以使用DDE进行一次性数据传输,也可以当出现新数据时,通过发送更新值在应用程序间动态交换数据。
DDE和剪贴板一样既支持标准数据格式(如文本、位图等),又可以支持自己定义的数据格式。但它们的数据传输机制却不同,一个明显区别是剪贴板操作几乎总是用作对用户指定操作的一次性应答-如从菜单中选择Paste命令。尽管DDE也可以由用户启动,但它继续发挥作用一般不必用户进一步干预。DDE有三种数据交换方式:
(1) 冷链:数据交换是一次性数据传输,与剪贴板相同。
(2) 温链:当数据交换时服务器通知客户,然后客户必须请求新的数据。
(3) 热链:当数据交换时服务器自动给客户发送数据。
DDE交换可以发生在单机或网络中不同计算机的应用程序之间。开发者还可以定义定制的DDE数据格式进行应用程序之间特别目的IPC,它们有更紧密耦合的通信要求。大多数基于Windows的应用程序都支持DDE。
2.8 对象连接与嵌入
应用程序利用对象连接与嵌入(OLE)技术管理复合文档(由多种数据格式组成的文档),OLE提供使某应用程序更容易调用其它应用程序进行数据编辑的服务。例如,OLE支持的字处理器可以嵌套电子表格,当用户要编辑电子表格时OLE库可自动启动电子表格编辑器。当用户退出电子表格编辑器时,该表格已在原始字处理器文档中得到更新。在这里电子表格编辑器变成了字处理器的扩展,而如果使用DDE,用户要显式地启动电子表格编辑器。
同DDE技术相同,大多数基于Windows的应用程序都支持OLE技术。
2.9 动态连接库
Win32动态连接库(DLL)中的全局数据可以被调用DLL的所有进程共享,这就又给进程间通信开辟了一条新的途径,当然访问时要注意同步问题。
虽然可以通过DLL进行进程间数据共享,但从数据安全的角度考虑,我们并不提倡这种方法,使用带有访问权限控制的共享内存的方法更好一些。
2.10 远程过程调用
Win32 API提供的远程过程调用(RPC)使应用程序可以使用远程调用函数,这使在网络上用RPC进行进程通信就像函数调用那样简单。RPC既可以在单机不同进程间使用也可以在网络中使用。
由于Win32 API提供的RPC服从OSF-DCE(Open Software Foundation Distributed Computing Environment)标准。所以通过Win32 API编写的RPC应用程序能与其它操作系统上支持DEC的RPC应用程序通信。使用RPC开发者可以建立高性能、紧密耦合的分布式应用程序。
2.11 NetBios函数
Win32 API提供NetBios函数用于处理低级网络控制,这主要是为IBM NetBios系统编写与Windows的接口。除非那些有特殊低级网络功能要求的应用程序,其它应用程序最好不要使用NetBios函数来进行进程间通信。
2.12

Sockets


Windows Sockets规范是以U.C.Berkeley大学BSD UNIX中流行的Socket接口为范例定义的一套Windows下的网络编程接口。除了Berkeley Socket原有的库函数以外,还扩展了一组针对Windows的函数,使程序员可以充分利用Windows的消息机制进行编程。
现在通过Sockets实现进程通信的网络应用越来越多,这主要的原因是Sockets的跨平台性要比其它IPC机制好得多,另外WinSock 2.0不仅支持TCP/IP协议,而且还支持其它协议(如IPX)。Sockets的唯一缺点是它支持的是底层通信操作,这使得在单机的进程间进行简单数据传递不太方便,这时使用下面将介绍的WM_COPYDATA消息将更合适些。
2.13 WM_COPYDATA消息
WM_COPYDATA是一种非常强大却鲜为人知的消息。当一个应用向另一个应用传送数据时,发送方只需使用调用SendMessage函数,参数是目的窗口的句柄、传递数据的起始地址、WM_COPYDATA消息。接收方只需像处理其它消息那样处理WM_COPY DATA消息,这样收发双方就实现了数据共享。
WM_COPYDATA是一种非常简单的方法,它在底层实际上是通过文件映射来实现的。它的缺点是灵活性不高,并且它只能用于Windows平台的单机环境下。

3 结束语

Win32 API为应用程序实现进程间通信提供了如此多种选择方案,那么开发者如何进行选择呢?通常在决定使用哪种IPC方法之前应考虑下一些问题,如应用程序是在网络环境下还是在单机环境下工作等。

posted @ 2009-10-23 09:01 深邃者 阅读(210) | 评论 (0)编辑 收藏

vs2005 调试

1,断点设置有技巧:

1)设置条件断点,比如i==10,变量改变时断点;

2)如何让断点在指定的命中次数或者大于某个次数时触发呢?方法是设定几个断点的HitCount,右键单击断点,在弹出菜单中选择Hit Count;

3)When Hit,这个选项可以让我们在命中断点后做一些事情,包括输出一些内容,或者调用宏,比如输出一个程序中变量的值;

4)利用断点的Filter功能,比如我希望断点只有被机器名为yizhu的机器访问才能触发;

具体参见:一篇介绍VS2005调试断点技巧的文章

2. 怎样判断加载的dll的正确性?

   调试时,打开Debug->Window->Modules,在窗口中显示的就是当前进程加载的所有dll及其详细信息,如果

断点无法击中,可以检查这里,看是否有匹配的pdb文件或者是加载了错误的dll

3. 已经开始调试的工程加入另外的进程并且调试

   如果你在调试客户端,但是服务器需要调试,那么使用菜单中的Tools-> Attach to process进行进程挂接,这种方法可以挂接所有windows下的程序,能否调试,就看其是否调试版和有调试用的PDB文件

4. 同时启动多进程进行调试

  在Solution的属性中的Common Properties->Startup Project。选择Multiple startup projects。这个选项是可以记忆的,下次打开可以直接调试,非常方便

5.调试Windows Service

 MSDN推荐的方法
1、调试windows服务的初始化、启动
  另写一个程序控制服务的初始化和启动
  注意:OnStart里写Log, OnStart里要在30秒返回. 不然启动就失败了! 所以OnStart里不要放太多代码! 可以用异步或线程.
2、调试windows服务的其他方面
1 ) 安装您的服务 : intallutils xx.exe
2) 可从“服务控制管理器”、“服务器资源管理器”或代码启动服务
3) vs: 设置相关断点,启动调试,再在工具栏中选择 调试->附加到进程..., 选择您的服务, 确定。
3、trace方法
1)添加调试方法

private static void DebugRun(string[] args)

2)改写程序入口为如下:

public static void Main(string[] args)
{
#if DEBUG
DebugRun(args);
#else
/*
初始化服务
*/
#endif
}

3)加入2种调试代码

EventLog.WriteEntry("...");
System.Diagnostics.Debug.WriteLine("...");

 

6,远程调试技术


    顾名思义,就是要调试的程序和调试器本身并不在一台机器上。由于虚拟机技术的盛行,在虚拟机里面运行待调试的程序,而在外面运行调试器,也是一种比较流行的做法。

1 为什么使用远程调试
   远程调试有如下好处:
    a. 能让产品运行在一个比较干净的环境。有的时候如果产品安装在一个装好集成环境的机器上,某些bug并不能显示出来。
    b. 易于部署调试环境。很多产品都非常复杂,比如很多都以service方式运行或者要load很复杂的resource,想在调试器里面直接按F5运行,越来越难。
    c. 对于游戏等全屏方式运行的程序,尤其有用。以前我对调试directx程序非常头疼。

2 怎样使用远程调试
   使用vs2005进行远程调试,详细的介绍参考:http://support.microsoft.com/kb/910448

   简单的来说,
   1)在被调试的机器上面运行Msvsmon.exe
   2)在调试机器上面运行vs2005,并attach到远程机器的某个进程

3 注意事项

   a. 设置好正确的权限
   被调试机器和调试机器需要互相信任的权限(two-way)。如果两台机器在同一个workgroup,让两台机器拥有一个相同的账号和密码,然后以这个账号运行。如果两台机器在一个域里面,比较简单,Msvsmon可以设置权限。如果一台机器在domain里面,另外一台不在,同样是让两台机器拥有一个相同的账号和密码,然后以这个账号运行。
   对于Windows XP要特别注意一下,设置匿名的访问权限才可以work , http://support.microsoft.com/kb/908099

   b.设置好symbol
   什么,你不知道什么是symbol?简单的来说,symbol file(*.pdb) is for source-level debugging. VS2005就是靠它来调试exe的。默认情况下debug版本生成的,而release版本不生成pdb.设置好 [Project proerties]-[C/C++]-[Debug Information Format]-Program Database就可以了。
   为了减少symbol方面的麻烦,最简单的做法是让被调试机器上的binary版本和本地compile出来保持一致。

posted @ 2009-10-23 08:59 深邃者 阅读(818) | 评论 (0)编辑 收藏

windbg调试器

WinDbg调试器

你可以从微软网站上下载到的调试器:

· KD-内核调试器。你可以用它来调试蓝屏一类的系统问题。如果是开发设备驱动程序是少不了它的。

· CDB-命令行调试器。这是一个命令行程序

· NTSD-NT调试器。这是一个用户模式调试器,可以用来调试用户模式应用程序。它实际上是一个CDB的windows UI增强。

· WinDbg-用一个漂亮的UI包装了KD和NTSD。WinDbg即可以调试内核模式,也可以调试用户模式程序。

·  VS, VS.net-使用同KD和NTSD相同的调试引擎,并且相比于同样用于调试目的的WinDbg,提供了功能更丰富的界面。

 

WinDbg实际上包装了NTSD和KD并且提供了一个更好用的用户界面。它也提供了命令行开关,比如最小化启动(-m),附加到一PID指定的进程(-p)以及自动打开崩溃文件(-z)。它支持三种类型的命令。

·  Regular commands(比如: k) 用来调试进程

·  Dot commands(比如:.sympath)用来控制调试器

·  Extension commands(比如: !handle)-这些命令属于可以用来添加到WinDbg的自定义命令;它们用扩展DLL的输出函数来实现。

 

PDB文件

     PDB文件, 是链接器生成程序数据库文件(Program database files)。私有的PDB文件包括私有以及公有符号,源代码行号,类型,局部以及全局变量。公有的PDB文件不包含类型,局部变量以及源代码行号信息。

配置WinDbg


      运行WinDbg->菜单->File->Symbol File Path->按照下面的方法设置_NT_SYMBOL_PATH变量:
在弹出的框中输入“C:\MyCodesSymbols; SRV*C:\MyLocalSymbols*http://msdl.microsoft.com/download/symbols”(按照这样设置,WinDbg将先从本地文件夹C:\MyCodesSymbols中查找Symbol,如果找不到,则自动从MS的Symbol Server上下载Symbols)。另一种做法是从这个Symbol下载地址中http://www.microsoft.com/whdc/devtools/debugging/symbolpkg.mspx,下载相应操作系统所需要的完整的Symbol安装包,并进行安装,例如我将其安装在D:\WINDOWS\Symbols,在该框中输入“D:\WINDOWS\Symbols”。(这里要注意下载的Symbols的版本一定要正确,在我的Win2003+Sp1上,我曾经以为安装Win2003+Sp2的Symbols可能会牛×点,但结果证明我错了,用WinDbg打开可执行文件时,提示“PDB symbol for mscorwks.dll not loaded;Defaulted to export symbols for ntdll.dll”的错误,我有重新装上Win2003+Sp1的Symbols, 现在一切运行正常^_^)

set _NT_SYMBOL_PATH=srv*C:\MySymbols*http://msdl.microsoft.com/download/symbols

 

WINDBG命令

调试前的必备工作

在开始调试前首先要做的工作是设置好符号(Symbols)路径。没有符号,你看到的调用堆栈基本上毫无意义。Microsoft的操作系统符号文件(PDB)是对外公开的。另外请注意在编译你自己的程序选择生成PDB文件的选项。如果设置好符号路径后,调用堆栈看起来还是不对。可以使用lm, !sym noisy, !reload 等命令来验证符号路径是否正确。

Windbg也支持源码级的调试。在开始源码调试前,你需要用.srcpath设置源代码路径。如果你是在生成所执行代码的机器上进行调试,符号文件中的源码路径会指向正确的位置,所以不需要设置源代码路径。如果所执行代码是在另一台机器上生成的,你可以将所用的源码拷贝(保持原有的目录结构)的一个可以访问的文件夹(可以是网络路径)并将源代码路径设为该文件夹的路径。注意如果是远程调试,你需要使用.lsrcpath来设置源码路径。

 

2)启动Debugger

Windbg可以用于如下三种调试:

(1)远程调试:你可以从机器A上调试在机器B上执行的程序。具体步骤如下:
  在机器B上启动一个调试窗口(Debug Session)。你可以直接在Windbg下运行一个程序或者将Windbg附加(Attach)到一个进程。
  在机器B的Windbg命令窗口上启动一个远程调试接口(remote):

    .server npipe:pipe=PIPE_NAME

     PIPE_NAME是该接口的名字。
在机器A上运行:

windbg –remote npipe:server=SERVER_NAME,pipe=PIPE_NAME

SERVER_NAME是机器B的名字。

Dump文件调试:如果在你的客户的机器上出现问题,你可能不能使用远程调试来解决问题。你可以要求你的用户将Windbg附加到出现问题的进程上,然后在命令窗口中输入:

.dump /ma File Name

创建一个Dump文件。在得到Dump文件后,使用如下的命令来打开它:

windbg –z DUMP_FILE_NAME

(2)本地进程调试:你可以在Windbg下直接运行一个程序:

Windbg “path to executable” arguments     

    也可以将Windbg附加到一个正在运行的程序:

    Windbg –p “process id”  

Windbg –pn “process name”

    注意有一种非侵入(Noninvasive)模式可以用来检查一个进程的状态并不进程的执行。当然在这种模式下无法控制被调试程序的执行。这种模式也可以用于查看一个已经在Debugger控制下运行的进程。具体命令如下:

    Windbg –pv –p “process id” 

Windbg –pv –pn “process name” 

(3)调试多个进程和线程

如果你想控制一个进程以及它的子进程的执行,在Windbg的命令行上加上-o选项。Windbg中还有一个新的命令.childdbg 可以用来控制子进程的调试。如果你同时调试几个进程,可以使用 | 命令来显示并切换到不同的进程。

在同一个进程中可能有多个线程。~命令可以用来显示和切换线程。

.hh keyword  如何得到帮助

静态命令:

显示调用堆栈:在连接到一个调试窗口后,首先要知道的就是程序当前的执行情况k* 命令显示当前线程的堆栈。~*kb会显示所有线程的调用堆栈。如果堆栈太长,Windbg只会显示堆栈的一部分。.kframes可以用来设置缺省显示框架数。

显示局部变量:接下来要做通常是用dv显示局部变量的信息。CTRL+ALT+V可以切换到更详细的显示模式。关于dv要注意的是在优化过的代码中dv的输出极有可能是不准确的。这时后你能做的就是阅读汇编代码来发现你感兴趣的值是否存储在寄存器中或堆栈上。有时后当前的框架(Frame)上可能找不到你想知道的数据。如果该数据是作为参数传到当前的方法中的,可以读一读上一个或几个框架的汇编代码,有可能该数据还在堆栈的某个地址上。静态变量是储存在固定地址中的,所以找出静态变量的值较为容易。.Frame(或者在调用堆栈窗口中双击)可以用来切换当前的框架。注意dv命令显示的是当前框架的内容。你也可在watch窗口中观察局部变量的值。

显示类和链表: dt可以显示数据结构。比如dt PEB 会显示操作系统进程结构。在后面跟上一个进程结构的地址会显示该结构的详细信息:dt PEB 7ffdf000。

Dl命令可以显示一些特定的链表结构。

显示当前线程的错误值:!gle会显示当前线程的上一个错误值和状态值。!error命令可以解码HRESULT。

搜索或修改内存:使用s 命令来搜索字节,字或双字,QWORD或字符串。使用e命令来修改内存。

计算表达式:?命令可以用来进行计算。关于表达式的格式请参照帮助文档。使用n命令来切换输入数字的进制。

显示当前线程,进程和模块信息:!teb显示当前线程的环境信息。最常见的用途是查看当前线程堆栈的起始地址,然后在堆栈中搜索值。!peb显示当前进程的环境信息,比如执行文件的路径等等。lm显示进程中加载的模块信息。

显示寄存器的值:r命令可以显示和修改寄存器的值。如果要在表达式中使用寄存器的值,在寄存器名前加@符号(比如@eax)。

显示最相近的符号:ln Address。如果你有一个C++对象的指针,可以用来ln来查看该对象类型。

查找符号:x命令可以用来查找全局变量的地址或过程的地址。x命令支持匹配符号。x kernel32!*显示Kernel32.dll中的所有可见变量,数据结构和过程。

查看lock:!locks显示各线程的锁资源使用情况。对调试死锁很有用。

查看handle:!handle显示句柄信息。如果一段代码导致句柄泄漏,你只需要在代码执行前后使用!handle命令并比较两次输出的区别。有一个命令!htrace对调试与句柄有关的Bug非常有用。在开始调试前输入:

!htrace –enable

然后在调试过程中使用!htrace handle_value 来显示所有与该句柄有关的调用堆栈。

显示汇编代码:u。

 

程序执行控制命令:

设置代码断点:bp/bu/bm 可以用来设置代码断点。你可以指定断点被跳过的次数。假设一段代码KERNEL32!SetLastError在运行很多次后会出错,你可以设置如下断点:

    bp KERNEL32!SetLastError 0x100.

在出错后使用bl 来显示断点信息(注意粗体显示的值):

0 e 77e7a3b0     004f (0100)  0:*** KERNEL32!SetLastError

重新启动调试(.restart命令)并设置如下的断点:

bp Kernel32!SetLastError 0x100-0x4f

Debugger会停在出错前最后一次调用该过程的地方。

你可以指定断点被激活时Debugger应当执行的命令串。在该命令串中使用J命令可以用来设置条件断点:

bp `mysource.cpp:143` "j (poi(MyVar)”0n20) ''; 'g' "

上面的断点只在MyVar的值大于32时被激活(g命令

条件断点的用途极为广泛。你可以指定一个断点只在特殊的情况下被激活,比如传入的参数满足一定的条件,调用者是某个特殊的过程,某个全局变量被设为特殊的值等等。

设置内存断点:ba可以用来设置内存断点。调试过程中一个常见的问题是跟踪某些数据的变化。如下的断点:

ba w4 0x40000000 "kb; g"

可以打印出所有修改0x40000000的调用堆栈。

控制程序执行:p, pa,t, ta等命令可以用来控制程序的执行。

控制异常和事件处理:Debugger的缺省设置是跳过首次异常(first chance expcetion),在二次异常(second chance exception)时中断程序的执行。sx命令显示Debugger的设置。sxe和sxd可以改变Debugger的设置。

    sxe clr

可以控制Debugger在托管异常发生时中断程序的执行。常用的Debugger事件有:

    av    访问异常 

    eh    C++异常

    clr   托管异常

    ld    模块加载

-c 选项可以用来指定在事件发生时执行的调试命令。

 

 

 

 

堆栈显示指令kb , kp, kP , kv
反汇编指令 u,uf
跟踪指令 T,TA,TB,TC
执行相关指令 P,PA,PC
跟踪查看指令 WT

----------------------------------------------------------------------------

堆栈显示指令 

k [b|p|P|v]

在内核调试的时候,k命令用来显示内核栈的内容

先说说内核栈用来干嘛的 看了些资料个人理解是这样的

比如我们的代码运行时,肯定会有函数函数然后还会调用函数 但是系统如何记录是哪个父函数调用了这个子函数,在子函数调用之前整个状态又是怎样的,其实系统是利用了堆栈记录的 栈这个东西好阿 先进后出 最近调用的函数记录在最顶层 函数执行完后就从栈内弹出之前记录的参数,如果调用函数 一样的把函数压进栈内就好了 这样一来 一旦子函数执行完,从栈内弹出的第一个函数肯定是该子函数的老爹 我们可以看上层堆栈的状态等等 功能大家慢慢去体会吧我也没用过 呵呵 不好说什么 下面说些细节的东西

b

显示传给函数的前三个参数

p

显示传给函数的全部参数

P( 大写)

跟上面那个一样 只不过是显示形式不同而已

V

外加显示一些额外的信息

----------------------------------------------------------------------------

u [f]

反汇编指令,嘿嘿 超级有用的指令哟虽然说内核很多东西很复杂 认识偶尔小小反下也是可以的

u

反汇编当前寄存器指向的代码

uf 函数名(比如nt!ZwCreateFile)

反汇编指定的函数

----------------------------------------------------------------------------

t [r]

单步跟踪

r 打开指显示寄存器的详细信息,状态的开关(下面指令一样有效,在用1次就会关闭哦~)

ta 地址

让程序执行到指定地址

tb

让程序运行到分支语句时停止

tc

让程序运行到下一个函数调用停止

----------------------------------------------------------------------------

p [r]

单步执行一跳指令

r 打开指显示寄存器的详细信息,状态的开关(下面指令一样有效,在用1次就会关闭哦~)

pa

让程序执行到指定地址

pc

让程序执行到函数调用就停止

----------------------------------------------------------------------------

wt

在想查看指定函数的信息而又不想单步通过该函数时很有用。可以到函数的起始地址并执行 wt 命令。(摘自翻译文档)

这个感觉用处不是很大.不细细研究了

----------------------------------------------------------------------------

Ps: 很多人不清楚到底p指令和t指令有什么区别 其实很简单 p指令执行到函数时把这个当做一个指令来执行也就是说不会进入函数执行,但是t指令会进入到函数里面执行 就这么简单~~呵呵

 

 

远程调试

使用WinDbg进行远程调试是很容易的,而且有很多种可行的方法。在下文中,’调试服务器’指的是运行在你所要调试的远程机器上的调试器。’调试客户端’指的是控制当前会话的调试器。

·      使用调试器:你需要CDB, NTSD或者WinDbg已经安装在远程机器上。WinDbg客户端可以连接到CDB, NTSD或者WinDbg中的任何一个作为服务器,反之亦然。在客户端和服务器直接可以选择TCP或者命名管道作为通讯协议。

o   在服务器端的启动过程:

§  WinDbg –server npipe:pipe=pipename(注:可以允许多个客户端连接)

§  从WinDbg内部: .server npipe:pipe=pipename(注,连接单个客户端)

你可以用多种协议开启不同的服务会话。并且可用密码来保护一个会话。

o   从客户端连接:

§  WinDbg -remote npipe:server=Server, pipe=PipeName[,password=Password]

§  从WinDbg内部: File->Connect to Remote Session: for connection string, enter npipe:server=Server, pipe=PipeName [,password=Password]

·     使用Remote.exe: Remote.exe使用命名管道作为通讯的方式。如果你使用的是一个命令行接口的程序,比如KD,CDB或者NTSD。你可以使用remote.exe来远程调试。注意:使用@q(不是q)来退出客户端,不用关掉服务端。

o   要启动一个服务端:

§  Remote.exe /s “cdp –p <pid>” test1

o   从客户端连接:

§  Remote.exe /c <machinename> test1

上面的test1是我们所选择的命名管道的名字。

服务端会显示那个客户端从那个服务器连接以及执行过的命令。你可以使用‘qq’命令来退出服务端;或者使用File->Exit来退出客户端。另外,如果要进行远程调试,你必须属于远程机器的”Debugger User”组并且服务器必须允许远程连接。
即时调试

在WinDbg的文档的”Enabling Postmorten Debugging”部分对此有很详细的讨论。简而言之,你可以把WinDbg设置成默认的即时调试器,命令就是:Windbg –I。这个命令实际上是把注册表中 HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug的键值设置成WinDbg。如果要把WinDbg设置成为默认的托管调试器,你需要显示设置如下的注册表键值:
HKLM\Software\Microsoft\.NETFramework\DbgJITDebugLaunchSetting 设置成 2
HKLM\Software\Microsoft\.NETFramework\DbgManagedDebugger 设置成Windbg.(注意其中的启动参数设置)

通过JIT的设置,当一个应用程序在不是调试的状态下抛出了未处理的异常之时,WinDbg就会被启动。
64位调试

所有这些调试器均支持在AMD64和IA64上的64位调试环境。


托管应用程序的调试

WinDbg 6.3以后的版本支持在Widbey(VS2005和.net 2.0的内部开发代号) .net CLR托管调试。在文档中针对托管调试有很好的讨论。需要注意的是,对于托管程序来说,没有刚才所说的PDB(译注:托管代码实际上也是有PDB的,但是这个PDB实际上记录了C#代码和IL代码的对应关系以及相关的一些信息)的概念,因为所有的程序都是编译成为ILASM。调试器通过CLR来查询所需的附加信息。

有几点需要注意:

你只能在托段函数的代码被执行过至少一次之后才能设置断点。只有这样它才能被编译成汇编代码。记住以下的几点:

·         关于函数的地址的复杂化以及对应的断点设置:

o   CLR有可能丢弃已经编译好的代码,所以函数的入口地址有可能改变。

o   同样的代码有可能被多次编译,如果多个应用程序域没有共享这段代码的话。如果你设置了一个断点,它就会被设置在当前线程(译注:CLR的逻辑线程)所在的应用程序域内。

o   泛型的特殊实例可能导致同一个函数有不同的地址。.

·         数据存储布局的复杂化以及对应的数据检查:
CLR可能会在运行的时候任意改变数据的存储布局,所以一个结构体成员的偏移量可能会被改变掉. (译注:实际上是在一个类型被加载的时候决定的数据布局,之后是不会改变的。)
一个类型的信息是在第一次使用的时候被加载,所以你可能不能够查看一个数据成员如果它还没有被使用过.

·         调试器命令的复杂化

o   当跟踪托管代码的时候,你会需要穿越大段的CLR自己的代码比如JIT编译器的代码,原因可能是你第一次进入一个函数,或者是你在托管和非托管代码之间进行切换。
调试Windows服务

使用WinDbg,你可以像调试其它应用程序那样调试Windows服务程序。即可以通过附加进程的方法启动Windows服务,也可以把WinDbg当作一个即时调试器,并且在代码中调用DbgBreakPoint 或者 DebugBreak,或者在x86机器上加入一条int 3汇编指令。
调试异常

一个调试器会得到两次的异常通知-第一次在应用程序有机会处理异常之前(‘first chance exception’);如果应用程序没有处理这个异常,这时候调试器就会有机会来处理异常(‘second-chance exception’)。如果调试器没有处理二次机会的异常,应用程序就会退出。

.lastevent或者,!analyze –v命令会给你显示异常的记录以及异常抛出所在函数的堆栈跟踪信息。

你也可以使用 .exr, .cxr以及 .ecxr命令来显示异常和上下文记录。同时需要注意的是,你也可以改变first-chance的处理选项。对应的命令就是: sxe, sxd, sxn和sxi。


虚拟机调试驱动

 

1)
将 WinDbg 发送一个快捷方式,并修改在快捷方式上右键=>"属性"
将"目标"中的 WinDbg 文件名后添加 "-k com:port=\\.\pipe\com_1,baud=115200,pipe" , 如下:
"C:\Program Files\Debugging Tools for Windows\windbg.exe" -k com:port=\\.\pipe\com_1,baud=115200,pipe

2)
打开虚拟机中的 c:\boot.ini 文件(之前去掉"只读"属性),复制一行
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" /fastdetect
即添加了一个启动选项,并修改为:
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise[Debug]" /fastdetect /debug /debugport=com1 /baudrate=115200
即添加了调试选项,调试端口以及串口的速率.
保存.

3)
关闭虚拟机里的目标windows系统(必须,否则在"Settings..."里的"Add..."将为灰色,不可选状态),
选择目标windows系统的"Settings..."选项,在"Hardware"选项中,点击下面的"Add..."按钮.
选择"Serial Port"点击"Next",再选择"Output to named pipe","Next",
这一向导中,前两项不修改,最后一项修改为"The other end is an application.",
如果这里存在"高级"选项,则在其中选择"Yield CPU on poll"[注:有些虚拟机在这里并没有"高级"选项,则在"Finish"后,选择"Serial Port",再勾选右下角的"Yield CPU on poll"],
"Finish".

"OK",完成"Virtual Maching Setting".

4)
打开虚拟机中的目标Windows系统,选择"Windows Server 2003, Enterprise[Debug]"后,立即打开之前创建的WinDbg快捷方式(若先打开WinDbg,则会报找不到'com:port=....'等信息).

5)
连接上虚拟机中的目标windows系统后,立即发送一个WinDbg中的"Debug"=>"Break"来中断它.

6)
设置"Symbol"路径,"Source"路径和"ImagePath"路径(若有多个,则用";"号隔开).
如:
"File"=>"Symbol" 里设置:"G:\虚拟机共享文件\Win2003Symbols;G:\虚拟机共享文件\GiveIO",勾选"Reload","OK". (第一个是Windows的符号[这里要注意,符号应该是虚拟机内Windows系统对应的],第二个是要调试的驱动的符号)

"File"=>"Source" 里设置:"G:\虚拟机共享文件\GiveIO", "OK". (要调试的驱动的源码我放在了此目录中)

"File"=>"Image File Path" 里设置: "G:\虚拟机共享文件\GiveIO", "Reload", "OK". (我把要调试的驱动程序放在了此目录中)

"File"=>"Open Source File", 打开驱动源文件.

7)
在要调试的驱动程序上下断点(这里我调试的驱动模块名为"GiveIO",后面是在"DriverEntry"入口点下断点[bu设置的是延迟断点]):
bu GiveIO!DriverEntry
按"F5"(或在命令行中用"g"命令),即可让虚拟机中的目标Windows顺利启动.

8)
在虚拟机中运行要调试的驱动程序,即可运行到断点处.

9)
一些说明:
在已运行的Windows目标系统中调试指定驱动:
首先应使WinDbg产生一个断点,使用WinDbg中的"Debug"=>"Break",此时目标Windows系统就会中断.
此时使用:
bu GiveIO!DriverEntry
命令,在指定驱动的模块名的入口点下断点(GiveIO为此处的驱动模块名).
运行驱动后,即会在驱动的DriverEntry处断下来,此时就可以进行跟踪调试了.

bl命令可以查看已下的断点
bc清除断点,如果后面跟"*",则清除所有断点

posted @ 2009-10-23 08:58 深邃者 阅读(2025) | 评论 (0)编辑 收藏

#pragma 预处理指令详解

#pragma 预处理指令详解

在所有的预处理指令中,#pragma 指令的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。

依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。 
    其格式一般为: #pragma  para 
    其中para为参数,下面来看一些常用的参数。 
 

(1)message 信息参数

    #pragma  message("消息文本") 
    当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 
判断自己有没有在源代码的什么地方定义了_X86这个宏,可以用下面的方法:
    #ifdef  _X86 
    #pragma  message("_X86  macro  activated!") 
    #endif  
    定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示"_86  macro  activated!"。  
      

(2)code_seg代码段参数

    #pragma  code_seg( ["section-name" [, "section-class"] ] ) 
    它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。 
 

(3)#pragma once 

    只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,
但是考虑到兼容性并没有太多的使用它。 

      
(4)#pragma  hdrstop

    表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,
但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。   
    有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。
你可以用#pragma  startup指定编译优先级,如果使用了#pragma  package(smart_init),
BCB就会根据优先级的大小先后编译。   

      
(5)#pragma  resource  "*.dfm"

    表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体  外观的定义。   

        
(6)#pragma  warning( disable: 4507 34; once: 4385; error: 164 )
 
    等价于: 
    #pragma  warning( disable: 4507 34 )    //  不显示4507和34号警告信息 
    #pragma  warning( once: 4385 )          //  4385号警告信息仅报告一次 
    #pragma  warning( error: 164 )          //  把164号警告信息作为一个错误。 

    同时这个pragma  warning  也支持如下格式: 
    #pragma  warning( push [, n ] ) 
    #pragma  warning( pop ) 
    这里n代表一个警告等级(1---4)。 
    #pragma  warning( push )保存所有警告信息的现有的警告状态。 
    #pragma  warning( push, n )保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n。   
    #pragma  warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的一切改动取消。例如: 
    #pragma  warning( push ) 
    #pragma  warning( disable: 4705 ) 
    #pragma  warning( disable: 4706 ) 
    #pragma  warning( disable: 4707 ) 
    //....... 
    #pragma  warning(  pop  )   
    在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。 


(7)#pragma  comment(...) 

    该指令将一个注释记录放入一个对象文件或可执行文件中。 
常用的lib关键字,可以帮我们连入一个库文件。如:
    #pragma  comment(lib, "comctl32.lib")
    #pragma  comment(lib, "vfw32.lib")
    #pragma  comment(lib, "wsock32.lib")
 
   
每个编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。

例如,对循环优化功能: 
#pragma  loop_opt(on)     //  激活 
#pragma  loop_opt(off)    //  终止 

有时,程序中会有些函数会使编译器发出你熟知而想忽略的警告,
如“Parameter  xxx  is  never  used  in  function  xxx”,可以这样: 
#pragma  warn  —100         //  Turn  off  the  warning  message  for  warning  #100 
int  insert_record(REC  *r) 
{  /*  function  body  */  } 
#pragma  warn  +100          //  Turn  the  warning  message  for  warning  #100  back  on 
函数会产生一条有唯一特征码100的警告信息,如此可暂时终止该警告。 

每个编译器对#pragma的实现不同,在一个编译器中有效在别的编译器中几乎无效。可从编译器的文档中查看。


补充 —— #pragma pack 与 内存对齐问题


    许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k
(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。

    Win32平台下的微软C编译器(cl.exe for 80x86)在默认情况下采用如下的对齐规则:
    任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。比如对于double类型(8字节),
就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。

    Linux下的GCC奉行的是另外一套规则(在资料中查得,并未验证,如错误请指正):
    任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型
(比如long,double)都以4为对齐模数。

    ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。
填充区就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。那么结构体本身有什么对齐要求吗?
有的,ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,可以更严格。


如何使用c/c++中的对齐选项

    vc6中的编译选项有 /Zp[1|2|4|8|16] ,/Zp1表示以1字节边界对齐,相应的,/Zpn表示以n字节边界对齐。
n字节边界对齐的意思是说,一个成员的地址必须安排在成员的尺寸的整数倍地址上或者是n的整数倍地址上,取它们中的最小值。
也就是:
    min ( sizeof ( member ),  n)

    实际上,1字节边界对齐也就表示了结构成员之间没有空洞。
    /Zpn选项是应用于整个工程的,影响所有的参与编译的结构。
    要使用这个选项,可以在vc6中打开工程属性页,c/c++页,选择Code Generation分类,在Struct member alignment可以选择。

    要专门针对某些结构定义使用对齐选项,可以使用#pragma pack编译指令:


(1) #pragma  pack( [ n ] )

    该指令指定结构和联合成员的紧凑对齐。而一个完整的转换单元的结构和联合的紧凑对齐由/Zp 选项设置。
紧凑对齐用pack编译指示在数据说明层设置。该编译指示在其出现后的第一个结构或联合说明处生效。
该编译指示对定义无效。
    当你使用#pragma  pack ( n ) 时, 这里n 为1、2、4、8 或16。
    第一个结构成员之后的每个结构成员都被存储在更小的成员类型或n 字节界限内。
如果你使用无参量的#pragma  pack, 结构成员被紧凑为以/Zp 指定的值。该缺省/Zp 紧凑值为/Zp8 。


(2) 编译器也支持以下增强型语法:
    #pragma  pack( [ [ { push | pop } , ] [ identifier, ] ] [ n] )

    若不同的组件使用pack编译指示指定不同的紧凑对齐, 这个语法允许你把程序组件组合为一个单独的转换单元。
带push参量的pack编译指示的每次出现将当前的紧凑对齐存储到一个内部编译器堆栈中。
    编译指示的参量表从左到右读取。如果你使用push, 则当前紧凑值被存储起来;
如果你给出一个n 的值, 该值将成为新的紧凑值。若你指定一个标识符, 即你选定一个名称,
则该标识符将和这个新的的紧凑值联系起来。

    带一个pop参量的pack编译指示的每次出现都会检索内部编译器堆栈顶的值,并且使该值为新的紧凑对齐值。
如果你使用pop参量且内部编译器堆栈是空的,则紧凑值为命令行给定的值, 并且将产生一个警告信息。
若你使用pop且指定一个n的值, 该值将成为新的紧凑值。若你使用p o p 且指定一个标识符,
所有存储在堆栈中的值将从栈中删除, 直到找到一个匹配的标识符, 这个与标识符相关的紧凑值也从栈中移出,
并且这个仅在标识符入栈之前存在的紧凑值成为新的紧凑值。如果未找到匹配的标识符,
将使用命令行设置的紧凑值, 并且将产生一个一级警告。缺省紧凑对齐为8 。

   pack编译指示的新的增强功能让你编写头文件, 确保在遇到该头文件的前后的
紧凑值是一样的。


(3) 栈内存对齐

    在vc6中栈的对齐方式不受结构成员对齐选项的影响。它总是保持对齐,而且对齐在4字节边界上。

posted @ 2009-10-23 08:56 深邃者 阅读(95) | 评论 (0)编辑 收藏

c++内存布局

0712月,我写了一篇《C++虚函数表解析》的文章,引起了大家的兴趣。有很多朋友对我的文章留了言,有鼓励我的,有批评我的,还有很多问问题的。我在这里一并对大家的留言表示感谢。这也是我为什么再写一篇续言的原因。因为,在上一篇文章中,我用了的示例都是非常简单的,主要是为了说明一些机理上的问题,也是为了图一些表达上方便和简单。不想,这篇文章成为了打开C++对象模型内存布局的一个引子,引发了大家对C++对象的更深层次的讨论。当然,我之前的文章还有很多方面没有涉及,从我个人感觉下来,在谈论虚函数表里,至少有以下这些内容没有涉及:

1)有成员变量的情况。

2)有重复继承的情况。

3)有虚拟继承的情况。

4)有钻石型虚拟继承的情况。

 

这些都是我本篇文章需要向大家说明的东西。所以,这篇文章将会是《C++虚函数表解析》的一个续篇,也是一篇高级进阶的文章。我希望大家在读这篇文章之前对C++有一定的基础和了解,并能先读我的上一篇文章。因为这篇文章的深度可能会比较深,而且会比较杂乱,我希望你在读本篇文章时不会有大脑思维紊乱导致大脑死机的情况。;-)

 

对象的影响因素

 

简而言之,我们一个类可能会有如下的影响因素:

 

1)成员变量

2)虚函数(产生虚函数表)

3)单一继承(只继承于一个类)

4)多重继承(继承多个类)

5)重复继承(继承的多个父类中其父类有相同的超类)

6)虚拟继承(使用virtual方式继承,为了保证继承后父类的内存布局只会存在一份)

上述的东西通常是C++这门语言在语义方面对对象内部的影响因素,当然,还会有编译器的影响(比如优化),还有字节对齐的影响。在这里我们都不讨论,我们只讨论C++语言上的影响。

 

本篇文章着重讨论下述几个情况下的C++对象的内存布局情况。

 

1)单一的一般继承(带成员变量、虚函数、虚函数覆盖)

2)单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)

3)多重继承(带成员变量、虚函数、虚函数覆盖)

4)重复多重继承(带成员变量、虚函数、虚函数覆盖)

5)钻石型的虚拟多重继承(带成员变量、虚函数、虚函数覆盖)

 

我们的目标就是,让事情越来越复杂。

 

知识复习

 

我们简单地复习一下,我们可以通过对象的地址来取得虚函数表的地址,如:

 

          typedef void(*Fun)(void);

 

            Base b;

 

            Fun pFun = NULL;

 

            cout << "虚函数表地址:" << (int*)(&b) << endl;

            cout << "虚函数表第一个函数地址:" << (int*)*(int*)(&b) << endl;

 

            // Invoke the first virtual function 

            pFun = (Fun)*((int*)*(int*)(&b));

            pFun();

 

我们同样可以用这种方式来取得整个对象实例的内存布局。因为这些东西在内存中都是连续分布的,我们只需要使用适当的地址偏移量,我们就可以获得整个内存对象的布局。

 

本篇文章中的例程或内存布局主要使用如下编译器和系统:

1)Windows XP VC++ 2003

2)Cygwin G++ 3.4.4

 

单一的一般继承

 

下面,我们假设有如下所示的一个继承关系:

 

 

请注意,在这个继承关系中,父类,子类,孙子类都有自己的一个成员变量。而了类覆盖了父类的f()方法,孙子类覆盖了子类的g_child()及其超类的f()

 

我们的源程序如下所示:

 

class Parent {

public:

    int iparent;

    Parent ():iparent (10) {}

    virtual void f() { cout << " Parent::f()" << endl; }

    virtual void g() { cout << " Parent::g()" << endl; }

    virtual void h() { cout << " Parent::h()" << endl; }

 

};

 

class Child : public Parent {

public:

    int ichild;

    Child():ichild(100) {}

    virtual void f() { cout << "Child::f()" << endl; }

    virtual void g_child() { cout << "Child::g_child()" << endl; }

    virtual void h_child() { cout << "Child::h_child()" << endl; }

};

 

class GrandChild : public Child{

public:

    int igrandchild;

    GrandChild():igrandchild(1000) {}

    virtual void f() { cout << "GrandChild::f()" << endl; }

    virtual void g_child() { cout << "GrandChild::g_child()" << endl; }

    virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }

};

我们使用以下程序作为测试程序:(下面程序中,我使用了一个int** pVtab 来作为遍历对象内存布局的指针,这样,我就可以方便地像使用数组一样来遍历所有的成员包括其虚函数表了,在后面的程序中,我也是用这样的方法的,请不必感到奇怪,)

 

    typedef void(*Fun)(void);

    GrandChild gc;

   

 

    int** pVtab = (int**)&gc;

 

    cout << "[0] GrandChild::_vptr->" << endl;

    for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){

                pFun = (Fun)pVtab[0][i];

                cout << "    ["<<i<<"] ";

                pFun();

    }

    cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;

    cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;

    cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;

 

其运行结果如下所示:(在VC++ 2003G++ 3.4.4下)

 

[0] GrandChild::_vptr->

    [0] GrandChild::f()

    [1] Parent::g()

    [2] Parent::h()

    [3] GrandChild::g_child()

    [4] Child::h1()

    [5] GrandChild::h_grandchild()

[1] Parent.iparent = 10

[2] Child.ichild = 100

[3] GrandChild.igrandchild = 1000

 

使用图片表示如下:

 

 

 

可见以下几个方面:

1)虚函数表在最前面的位置。

2)成员变量根据其继承和声明顺序依次放在后面。

3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。

 

 

 

 

 

多重继承

 

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:

 

 

 

我们的类继承的源代码如下所示:父类的成员初始为102030,子类的为100

 

class Base1 {

public:

    int ibase1;

    Base1():ibase1(10) {}

    virtual void f() { cout << "Base1::f()" << endl; }

    virtual void g() { cout << "Base1::g()" << endl; }

    virtual void h() { cout << "Base1::h()" << endl; }

 

};

 

class Base2 {

public:

    int ibase2;

    Base2():ibase2(20) {}

    virtual void f() { cout << "Base2::f()" << endl; }

    virtual void g() { cout << "Base2::g()" << endl; }

    virtual void h() { cout << "Base2::h()" << endl; }

};

 

class Base3 {

public:

    int ibase3;

    Base3():ibase3(30) {}

    virtual void f() { cout << "Base3::f()" << endl; }

    virtual void g() { cout << "Base3::g()" << endl; }

    virtual void h() { cout << "Base3::h()" << endl; }

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

    int iderive;

    Derive():iderive(100) {}

    virtual void f() { cout << "Derive::f()" << endl; }

    virtual void g1() { cout << "Derive::g1()" << endl; }

};

 

我们通过下面的程序来查看子类实例的内存布局:下面程序中,注意我使用了一个s变量,其中用到了sizof(Base)来找下一个类的偏移量。(因为我声明的是int成员,所以是4个字节,所以没有对齐问题。关于内存的对齐问题,大家可以自行试验,我在这里就不多说了)

 

             typedef void(*Fun)(void);

               Derive d;

 

                int** pVtab = (int**)&d;

 

                cout << "[0] Base1::_vptr->" << endl;

                pFun = (Fun)pVtab[0][0];

                cout << "     [0] ";

                pFun();

 

                pFun = (Fun)pVtab[0][1];

                cout << "     [1] ";pFun();

 

                pFun = (Fun)pVtab[0][2];

                cout << "     [2] ";pFun();

 

                pFun = (Fun)pVtab[0][3];

                cout << "     [3] "; pFun();

 

                pFun = (Fun)pVtab[0][4];

                cout << "     [4] "; cout<<pFun<<endl;

 

                cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;

 

 

                int s = sizeof(Base1)/4;

 

                cout << "[" << s << "] Base2::_vptr->"<<endl;

                pFun = (Fun)pVtab[s][0];

                cout << "     [0] "; pFun();

 

                Fun = (Fun)pVtab[s][1];

                cout << "     [1] "; pFun();

 

                pFun = (Fun)pVtab[s][2];

                cout << "     [2] "; pFun();

 

                pFun = (Fun)pVtab[s][3];

                out << "     [3] ";

                cout<<pFun<<endl;

 

                cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl;

 

                s = s + sizeof(Base2)/4;

                cout << "[" << s << "] Base3::_vptr->"<<endl;

                pFun = (Fun)pVtab[s][0];

                cout << "     [0] "; pFun();

 

                pFun = (Fun)pVtab[s][1];

                cout << "     [1] "; pFun();

 

                pFun = (Fun)pVtab[s][2];

                cout << "     [2] "; pFun();

 

                pFun = (Fun)pVtab[s][3];

                 cout << "     [3] ";

                cout<<pFun<<endl;

 

                s++;

                cout << "["<< s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl;

                s++;

                cout << "["<< s <<"] Derive.iderive = " << (int)pVtab[s] << endl;

 

其运行结果如下所示:(在VC++ 2003G++ 3.4.4下)

[0] Base1::_vptr->

     [0] Derive::f()

     [1] Base1::g()

     [2] Base1::h()

     [3] Driver::g1()

     [4] 00000000      ç 注意:在GCC下,这里是1

[1] Base1.ibase1 = 10

[2] Base2::_vptr->

     [0] Derive::f()

     [1] Base2::g()

     [2] Base2::h()

     [3] 00000000      ç 注意:在GCC下,这里是1

[3] Base2.ibase2 = 20

[4] Base3::_vptr->

     [0] Derive::f()

     [1] Base3::g()

     [2] Base3::h()

     [3] 00000000

[5] Base3.ibase3 = 30

[6] Derive.iderive = 100


使用图片表示是下面这个样子:

 

 

我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。

3) 内存布局中,其父类布局依次按声明顺序排列。

4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

posted @ 2008-10-18 17:31 深邃者 阅读(169) | 评论 (0)编辑 收藏

c++虚函数表解析

前言

 

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

 

 

关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。

 

当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。

 

言归正传,让我们一起进入虚函数的世界。

 

 

虚函数表

 

C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

 

这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

 

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。

 

假设我们有这样的一个类:

 

class Base {

     public:

            virtual void f() { cout << "Base::f" << endl; }

            virtual void g() { cout << "Base::g" << endl; }

            virtual void h() { cout << "Base::h" << endl; }

 

};

 

按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

 

          typedef void(*Fun)(void);

 

            Base b;

 

            Fun pFun = NULL;

 

            cout << "虚函数表地址:" << (int*)(&b) << endl;

            cout << "虚函数表第一个函数地址:" << (int*)*(int*)(&b) << endl;

 

            // Invoke the first virtual function 

            pFun = (Fun)*((int*)*(int*)(&b));

            pFun();

 

实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

 

虚函数表地址:0012FED4

虚函数表第一个函数地址:0044F148

Base::f

 

 

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()Base::h(),其代码如下:

 

            (Fun)*((int*)*(int*)(&b)+0); // Base::f()

            (Fun)*((int*)*(int*)(&b)+1); // Base::g()

            (Fun)*((int*)*(int*)(&b)+2); // Base::h()

 

这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

 

 

下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

 

一般继承(无虚函数覆盖)

 

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

 

 

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

 

对于实例:Derive d; 的虚函数表如下:

 

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

 

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

 

 

 

一般继承(有虚函数覆盖)

 

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

 

 

 

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

 

 

我们从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

 

这样,我们就可以看到对于下面这样的程序,

 

            Base *b = new Derive();

 

            b->f();

 

b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

 

 

 

多重继承(无虚函数覆盖)

 

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

 

 

 

对于子类实例中的虚函数表,是下面这个样子:

 

我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

 

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

 

 

 

 

多重继承(有虚函数覆盖)

 

下面我们再来看看,如果发生虚函数覆盖的情况。

 

下图中,我们在子类中覆盖了父类的f()函数。

 

 

 

下面是对于子类实例中的虚函数表的图:

 

 

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

 

            Derive d;

            Base1 *b1 = &d;

            Base2 *b2 = &d;

            Base3 *b3 = &d;

            b1->f(); //Derive::f()

            b2->f(); //Derive::f()

            b3->f(); //Derive::f()

 

            b1->g(); //Base1::g()

            b2->g(); //Base2::g()

            b3->g(); //Base3::g()

 

 

安全性

 

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

 

一、通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

 

          Base1 *b1 = new Derive();

            b1->f1(); //编译出错

 

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

 

 

二、访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

 

如:

 

class Base {

    private:

            virtual void f() { cout << "Base::f" << endl; }

 

};

 

class Derive : public Base{

 

};

 

typedef void(*Fun)(void);

 

void main() {

    Derive d;

    Fun pFun = (Fun)*((int*)*(int*)(&d)+0);

    pFun();

}

 

 

结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

 

在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了,目前是软件开发技术主管,技术方面,主攻Unix/C/C++,比较喜欢网络上的技术,比如分布式计算,网格计算,P2PAjax等一切和互联网相关的东西。管理方面比较擅长于团队建设,技术趋势分析,项目管理。欢迎大家和我交流,我的MSNEmail是:haoel@hotmail.com 

 

附录一:VC中查看虚函数表

 

我们可以在VCIDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的)

附录 二:例程

下面是一个关于多重继承的虚函数表访问的例程:

 

#include <iostream>

using namespace std;

 

class Base1 {

public:

            virtual void f() { cout << "Base1::f" << endl; }

            virtual void g() { cout << "Base1::g" << endl; }

            virtual void h() { cout << "Base1::h" << endl; }

 

};

 

class Base2 {

public:

            virtual void f() { cout << "Base2::f" << endl; }

            virtual void g() { cout << "Base2::g" << endl; }

            virtual void h() { cout << "Base2::h" << endl; }

};

 

class Base3 {

public:

            virtual void f() { cout << "Base3::f" << endl; }

            virtual void g() { cout << "Base3::g" << endl; }

            virtual void h() { cout << "Base3::h" << endl; }

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

            virtual void f() { cout << "Derive::f" << endl; }

            virtual void g1() { cout << "Derive::g1" << endl; }

};

 

 

typedef void(*Fun)(void);

 

int main()

{

            Fun pFun = NULL;

 

            Derive d;

            int** pVtab = (int**)&d;

 

            //Base1's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

            pFun = (Fun)pVtab[0][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

            pFun = (Fun)pVtab[0][1];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

            pFun = (Fun)pVtab[0][2];

            pFun();

 

            //Derive's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

            pFun = (Fun)pVtab[0][3];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[0][4];

            cout<<pFun<<endl;

 

 

            //Base2's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[1][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[1][1];

            pFun();

 

            pFun = (Fun)pVtab[1][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[1][3];

            cout<<pFun<<endl;

 

 

 

            //Base3's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[2][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[2][1];

            pFun();

 

            pFun = (Fun)pVtab[2][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[2][3];

            cout<<pFun<<endl;

 

            return 0;

}

posted @ 2008-10-18 17:29 深邃者 阅读(99) | 评论 (0)编辑 收藏

如何用 Win32 APIs 枚举应用程序窗口和进程

如何用 Win32 APIs 枚举应用程序窗口和进程

编译:NorthTibet

下载源代码

摘要

  我们在编写程序时,常常遇到的一件事情就是要准确列出系统中所有正在运行的程序或者进程。Windows 任务管理器就是这样的一个程序。它既能列出运行的桌面应用程序,又能列出系统中所有运行的进程。那么,我们在程序中如何实现这样的任务呢?本文下面将详细讨论这个问题。


枚举顶层(top-level)窗口

  枚举桌面顶层窗口相对于枚举进程来说可能要容易一些。枚举桌面顶层窗口的方法是用 EnumWindows() 函数。不要用 GetWindow()来创建窗口列表,因为窗口之间复杂的父子及同胞关系(Z-Order)容易造成混乱而使得枚举结果不准确。
  EnumWindows()有两个参数,一个是指向回调函数的指针,一个是用户定义的 LPARAM 值, 针对每个桌面窗口(或者顶层窗口)它调用回调函数一次。然后回调函数用该窗口句柄做一些处理,比如将它添加到列表中。这个方法保证枚举结果不会被窗口复杂的层次关系搞乱,因此,一旦有了窗口句柄,我们就可以通过 GetWindowText() 得到窗口标题。


枚举进程

  建立系统进程列表比枚举窗口稍微复杂一些。这主要是因为所用的 API 函数对于不同的 Win32 操作系统有依赖性。在 Windows 9x、Windows Me、Windows 2000 Professional 以及 Windows XP 中,我们可以用 ToolHelp32 库中的 APIs 函数。但是在 Windows NT 里,我们必须用 PSAPI 库中的 APIs 函数, PSAPI 库是 SDK 的一部分。本文我们将讨论上述所有平台中的实现。附带的例子程序将对上述库中的 APIs 进行包装,以便包装后的函数能支持所有 Win32 操作系统。


使用 ToolHelp32 库枚举进程

  ToolHelp32 库函数在 KERNEL32.dll 中,它们都是标准的 API 函数。但是 Windows NT 4.0 不提供这些函。
  ToolHelp32 库中有各种各样的函数可以用来枚举系统中的进程、线程以及获取内存和模块信息。其中枚举进程 只需用如下三个的函数:CreateToolhelp32Snapshot()、Process32First()和 Process32Next()。
  使用 ToolHelp32 函数的第一步是用 CreateToolhelp32Snapshot() 函数创建系统信息“快照”。这个函数可以让你选择存储在快照中的信息类型。如果你只是对进程信息感兴趣,那么只要包含 TH32CS_SNAPPROCESS 标志即可。 CreateToolhelp32Snapshot() 函数返回一个 HANDLE,完成调用之后,必须将此 HANDLE 传给 CloseHandle()。
  接下来是调用一次 Process32First 函数,从快照中获取进程列表,然后重复调用 Process32Next,直到函数返回 FALSE 为止。这样将遍历快照中进程列表。这两个函数都带两个参数,它们分别是快照句柄和一个   PROCESSENTRY32 结构。
  调用完 Process32First 或 Process32Next 之后,PROCESSENTRY32 中将包含系统中某个进程的关键信息。其中进程 ID 就存储在此结构的 th32ProcessID。此 ID 可以被传给 OpenProcess() API 以获得该进程的句柄。对应的可执行文件名及其存放路径存放在 szExeFile  结构成员中。在该结构中还可以找到其它一些有用的信息。
  注意:在调用 Process32First() 之前,一定要记住将 PROCESSENTRY32  结构的 dwSize 成员设置成 sizeof(PROCESSENTRY32)。


使用 PSAPI 库枚举进程

  在 Windows NT 中,创建进程列表使用 PSAPI 函数,这些函数在 PSAPI.DLL 中。这个文件是随 Platform SDK 一起分发的,最新版本的 Platform SDK 可以从这里下载

使用这个库所需的 PSAPI.h 和 PSAPI.lib 文件也在该 Platform SDK 中。
  为了使用 PSAPI 库中的函数,需将 PSAPI.lib 添加到代码项目中,同时在所有调用 PSAPI API 的模块中包含 PSAPI.h 文件。记住一定要随可执行文件一起分发 PSAPI.DLL,因为它不随 Windows NT 一起分发。你可以点击这里单独下载 PSAPI.DLL 的可分发版本(不用完全下载 Platform SDK)。
  与 ToolHelp32 一样,PSAPI 库也包含各种各样有用的函数。由于篇幅所限,本文只讨论与枚举进程有关函数:EnumProcesses()、 EnumProcessModules()、GetModuleFileNameEx()和 GetModuleBaseName()。
  创建进程列表的第一步是调用 EnumProcesses()。该函数的声明如下:

BOOL EnumProcesses( DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded );
  EnumProcesses()带三个参数,DWORD 类型的数组指针 lpidProcess;该数组的大小尺寸 cb;以及一个指向 DWORD 的指针 cbNeeded,它接收返回数据的长度。DWORD 数组用于保存当前运行的进程 IDs。cbNeeded 返回数组所用的内存大小。下面算式可以得出返回了多少进程:nReturned = cbNeeded / sizeof(DWORD)。
  注意:虽然文档将返回的 DWORD 命名为“cbNeeded”,实际上是没有办法知道到底要传多大的数组的。EnumProcesses()根本不会在 cbNeeded 中返回一个大于 cb 参数传递的数组值。结果,唯一确保 EnumProcesses()函数成功的方法是分配一个 DWORD 数组,并且,如果返回的 cbNeeded 等于 cb,分配一个较大的数组,并不停地尝试直到 cbNeeded 小于 cb
  现在,你获得了一个数组,其元素保存着系统中每个进程的ID。如果你要想获取进程名,那么你必须首先获取一个句柄。要想从进程 ID 得到句柄,就得调用 OpenProcess()。
  一旦有了句柄,则需要得到该进程的第一个模块。为此调用 EnumProcessModules() API:
EnumProcessModules( hProcess, &hModule, sizeof(hModule), &cbReturned );
  调用之后,hModule 变量中保存的将是进程中的第一个模块。记住进程其实没有名字,但进程的第一个模块既是该进程的可执行模块。现在你可以用 hModule 中返回的模块句柄调用 GetModuleFileNameEx() 或 GetModuleBaseName() API 函数获取全路径名,或者仅仅是进程可执行模块名。两个函数均带四个参数:进程句柄,模块句柄,返回名字的缓冲指针以及缓冲大小尺寸。
  用 EnumProcesses() API 返回的每一个进程 ID 重复这个调用过程,你便可以创建 Windows NT 的进程列表。


16位进程的处理方法

  在 Windows 95,Windows 98 和 Windows ME 中,ToolHelp32 对待16位程序一视同仁,它们与 Win32 程序一样有自己的进程IDs。但是在 Windows NT,Windows 2000 或 Windows XP 中情况并不是这样。在这些操作系统中,16位程序运行在所谓的 VDM 当中(也就是DOS机)。
  为了在 Windows NT,Windows 2000 和 Windows XP 中枚举16位程序,你必须使用一个名为 VDMEnumTaskWOWEx()的函数。在源代码模块中必须包含 VDMDBG.h,并且 VDMDBG.lib 文件必须与项目链接。这两个文件都在 Platform SDK 中。该函数的声明如下:
INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEX fp,LPARAM lparam );

  此处 dwProcessId 是 NTVDM 中拟枚举的16位任务进程标示符。参数 fp 是回调枚举函数的指针。参数 lparam 是用户定义的值,它被传递到枚举函数。枚举函数应该被定义成如下这样:

BOOL WINAPI Enum16( DWORD dwThreadId, 
WORD hMod16,
WORD hTask16,
PSZ pszModName,
PSZ pszFileName,
LPARAM lpUserDefined );
  该函数针对每个运行在 NTVDM 进程中的16位任务调用一次,NTVDM 进程ID将被传入 VDMEnumTaskWOWEx()。如果想继续枚举则返回 FALSE,终止枚举则返回 TRUE。注意这是与 EnumWindows()相对的。


关于代码

  本文附带的代码例子将 PSAPI 和 ToolHelp32 封装到一个名为 EnumProcs() 的函数中。该函数的工作原理类似 EnumWindows(),有一个指向回调函数的指针,并要对该函数进行重复调用,针对系统中的每个进程调用一次。另一个参数是用户定义的 lParam。下面是该函数的声明:
BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );

使用该函数时,要象下面这样声明回调函数:

BOOL CALLBACK Proc( DWORD dw, WORD w16, LPCSTR lpstr, LPARAM lParam );
  参数 dw 包含 ID,“w16”是16位任务的任务号,如果为32位进程则为0(在 Windows 95 中总是0),参数lpstr 指向文件名,lParam 是用户定义的,要被传入 EnumProcs()。
  EnumProcs() 函数通过显示链接使用 ToolHelp32 和 PSAPI,而非通常所用的隐式链接。之所以要这样做,主要是为了让代码能够在二进制一级兼容,从可以在所有 Win32 操作系统平台上运行。

posted @ 2008-09-22 18:34 深邃者 阅读(190) | 评论 (0)编辑 收藏

如何“干净地”终止 Win32 中的应用程序

如何“干净地”终止 Win32 中的应用程序

编译:Northtibet

  • 摘要
  • 32 位进程(和 Windows 95 下的 16 位进程)
  • 16 位问题(在 Windows NT 下)
  • 示例代码
  • 摘要

      在理想环境中,某一进程可能会通过某种形式的进程间通信要求另一进程关闭。不过,如果你对希望其关闭的应用程序没有源代码级控制权,可能就没有办法做这样的选择。尽管没有哪种方法能保证“干净地”关闭 Win32 中的应用程序,但你可以采取一些步骤来确保应用程序使用最佳方法清除资源。

    32 位进程(和 Windows 95 下的 16 位进程)

      在 Win32 下,操作系统可保证在进程关闭时清除进程所拥有的资源。但是,这并不意味着进程本身将有机会对磁盘执行任何最后的信息刷新或通过远程连接执行任何最后的通信,也不意味着进程的 DLL 将有机会执行其 PROCESS_DETACH 代码。这就是通常最好避免在 Windows 95 和 Windows NT 下终止应用程序的原因。

    如果你必须关闭进程,请按照下列步骤操作:

    1. 向你打算关闭的进程所拥有的所有顶级窗口发送一条 WM_CLOSE 消息。许多 Windows 应用程序会通过关闭它自身来响应此消息。

      注意:控制台应用程序对 WM_CLOSE 的响应取决于它是否安装了控制处理程序。

      使用 EnumWindows() 找到目标窗口的句柄。在回调函数中,检查该窗口的进程 ID 是否与要关闭的进程相匹配。你可以通过调用 GetWindowThreadProcessId() 来执行此操作。确定匹配项后,使用 PostMessage() 或 SendMessageTimeout() 向该窗口发送 WM_CLOSE 消息。
    2. 使用 WaitForSingleObject() 等待进程的句柄。确保你使用超时值等待,因为在很多情况下 WM_CLOSE 不会关闭应用程序。记住,应使超时值足够长(通过 WaitForSingleObject() 或 SendMessageTimeout()),以便用户可以响应为了 处理 WM_CLOSE 消息而创建的任何对话框。
    3. 如果返回值为 WAIT_OBJECT_0,则应用程序已干净地将其自身关闭。如果返回值为 WAIT_TIMEOUT,则必须使用 TerminateProcess() 关闭应用程序。

      注意:如果从 WaitForSingleObject() 得到的返回值不是 WAIT_OBJECT_0 或 WAIT_TIMEOUT,则应使用 GetLastError() 找出原因。
    通过执行上述这些步骤,你便完全有可能干净地关闭应用程序(无需 IPC 或用户干预)。

    16 位问题(在 Windows NT 下)

      上述步骤适用于 Windows 95 下的 16 位应用程序,而 Windows NT 下的 16 位应用程序与 Windows 95 下的 16 位应用程序的工作方式差别非常大。
      在 Windows NT 下,所有 16 位应用程序都在虚拟 DOS 机 (VDM) 中运行。此 VDM 是作为 Windows NT 下的一个 Win32 进程 (NTVDM) 运行的。NTVDM 进程具有进程 ID。你可以通过 OpenProcess() 获取该进程的句柄,就像处理其它任何 Win32 进程一样。不过,在 VDM 中运行的 16 位应用程序都没有进程 ID,因此你无法从 OpenProcess() 获取进程句柄。VDM 中的每个 16 位应用程序都有一个 16 位任务句柄和一个 32 位执行线程。可通过调用函数 VDMEnumTaskWOWEx() 找到该任务句柄和线程 ID。有关这方面的其它信息,请参见:“如何用 Win32 APIs 枚举应用程序窗口和进程”。
      关闭 Windows NT 下的 16 位应用程序的首选和最直接的方法是关闭整个 NTVDM 进程。你可以通过执行前面所描述的步骤来完成此操作。你只需知道 NTVDM 的进程 ID 即可,参考“如何用 Win32 APIs 枚举应用程序窗口和进程”所讲的方法来查找 NTVDM 的进程 ID。此方法的缺点是它会关闭在该 VDM 中运行的所有 16 位应用程序。如果这不是你想要的结果,则需要采取其它方法。
      如果你希望关闭 NTVDM 进程中的单个 16 位应用程序,需要按照下列步骤操作:
    1. 向该进程所拥有的以及与你要关闭的 16 位任务具有相同线程 ID 的所有顶级窗口发送一条 WM_CLOSE 消息。执行此操作最有效的方法是使用 EnumWindows()。在回调函数中,检查窗口的进程 ID 和线程 ID 是否与要关闭的 16 位任务相匹配。请记住,该进程 ID 将成为在其中运行 16 位应用程序的 NTVDM 的进程 ID。
    2. 尽管你有线程 ID,但无法等待 16 位进程的终止。因此,你必须等待任意时间长度(以允许干净关闭),然后尝试关闭应用程序。如果应用程序已关闭,则此操作无效。如果应用程序尚未关闭,则它将终止应用程序。
    3. 使用称为 VDMTerminateTaskWOW() 的函数终止应用程序,该函数可在 Vdmdbg.dll 中找到。它采用 VDM 的进程 ID 和 16 位任务的任务编号。

      此方法允许你关闭 Windows NT 下 VDM 中的单个 16 位应用程序。不过,16 位 Windows 以及 VDM 中运行的 WOWExec 都不能有效地清除已终止任务的资源。如果你要寻找最有可能干净地终止 Windows NT 下的 16 位应用程序的方法,应考虑终止整个 VDM 进程。注意:如果你要启动以后可能会终止的 16 位应用程序,请将 CREATE_SEPARATE_WOW_VDM 与 CreateProcess() 结合使用。

    示例代码

      下面的示例代码使用以下两个函数实现上述用于 16 位和 32 位应用程序的方法:TerminateApp() 和 Terminate16App()。TerminateApp() 采用一个 32 位进程 ID 和一个超时值(以毫秒为单位)。Terminate16App()。这两个函数都使用 DLL 函数的显式链接,以便它们的二进制文件与 Windows NT 和 Windows 95 都兼容。

       //******************

    // 头文件 TermApp.h

    //******************

    #include <windows.h>

    #define TA_FAILED 0

    #define TA_SUCCESS_CLEAN 1

    #define TA_SUCCESS_KILL 2

    #define TA_SUCCESS_16 3


    DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout ) ;

    DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,

    WORD w16Task, DWORD dwTimeout );



    //*********************

    // 实现代码 TermApp.cpp

    //*********************

    #include "TermApp.h"

    #include <vdmdbg.h>

    typedef struct

    {

    DWORD dwID ;

    DWORD dwThread ;

    } TERMINFO ;

    // 声明回调枚举函数.

    BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam ) ;

    BOOL CALLBACK Terminate16AppEnum( HWND hwnd, LPARAM lParam ) ;

    /*----------------------------------------------------------------

    DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )

    功能:

    关闭 32-位进程(或 Windows 95 下的 16-位进程)

    参数:

    dwPID

    要关闭之进程的进程 ID.

    dwTimeout

    进程关闭前等待的毫秒时间.

    返回值:

    TA_FAILED —— 如果关闭失败.

    TA_SUCCESS_CLEAN —— 如果使用 WM_CLOSE 关闭了进程.

    TA_SUCCESS_KILL —— 如果使用 TerminateProcess() 关闭了进程.

    返回值的定义参见头文件.

    ----------------------------------------------------------------*/

    DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )

    {

    HANDLE hProc ;

    DWORD dwRet ;

    // 如果无法用 PROCESS_TERMINATE 权限打开进程,那么立即放弃。

    hProc = OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE,dwPID);

    if(hProc == NULL)

    {
    return TA_FAILED ;
    }


    // TerminateAppEnum() 将 WM_CLOSE 消息发到所有其进程ID 与你所提供的进程ID 匹配的窗口.

    EnumWindows((WNDENUMPROC)TerminateAppEnum, (LPARAM) dwPID) ;


    // 等待处理,如果成功,OK。如果超时,则干掉它.

    if(WaitForSingleObject(hProc, dwTimeout)!=WAIT_OBJECT_0)

    dwRet=(TerminateProcess(hProc,0)?TA_SUCCESS_KILL:TA_FAILED);

    else

    dwRet = TA_SUCCESS_CLEAN ;

    CloseHandle(hProc) ;

    return dwRet ;

    }


    /*----------------------------------------------------------------

    DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,

    WORD w16Task, DWORD dwTimeout )

    功能:

    关闭 Win16 应用程序.

    参数:

    dwPID

    16-位程序运行其中的 NTVDM 进程 ID.

    dwThread

    16-位程序中执行线程的线程 ID.

    w16Task

    应用程序的 16-位任务句柄.

    dwTimeout

    任务关闭前等待的毫秒时间.



    返回值:

    如果成功, 返回 TA_SUCCESS_16

    如果不成功, 返回 TA_FAILED.

    返回值的定义参见该函数的头文件.

    注意:

    你可以通过 VDMEnumTaskWOW() 或 VDMEnumTaskWOWEx() 函数获得 Win16 和线程 ID.

    ----------------------------------------------------------------*/

    DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,

    WORD w16Task, DWORD dwTimeout )

    {

    HINSTANCE hInstLib ;

    TERMINFO info ;

    // 你必须通过外部链接调用函数,以便代码在所有 Win32 平台上都兼容。

    BOOL (WINAPI *lpfVDMTerminateTaskWOW)(DWORD dwProcessId,WORD htask) ;

    hInstLib = LoadLibraryA( "VDMDBG.DLL" ) ;

    if( hInstLib == NULL )
    return TA_FAILED ;

    // 获得函数过程地址.

    lpfVDMTerminateTaskWOW = (BOOL (WINAPI *)(DWORD, WORD ))

    GetProcAddress( hInstLib, "VDMTerminateTaskWOW" ) ;

    if( lpfVDMTerminateTaskWOW == NULL )

    {

    FreeLibrary( hInstLib ) ;

    return TA_FAILED ;

    }

    // 向所有匹配进程 ID 和线程的窗口发送 WM_CLOSE 消息.

    info.dwID = dwPID ;

    info.dwThread = dwThread ;

    EnumWindows((WNDENUMPROC)Terminate16AppEnum, (LPARAM) &info) ;

    // 等待.

    Sleep( dwTimeout ) ;

    // 然后终止.

    lpfVDMTerminateTaskWOW(dwPID, w16Task) ;

    FreeLibrary( hInstLib ) ;

    return TA_SUCCESS_16 ;

    }



    BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam )

    {

    DWORD dwID ;

    GetWindowThreadProcessId(hwnd, &dwID) ;

    if(dwID == (DWORD)lParam)

    {

    PostMessage(hwnd, WM_CLOSE, 0, 0) ;

    }

    return TRUE ;
    }



    BOOL CALLBACK Terminate16AppEnum( HWND hwnd, LPARAM lParam )

    {

    DWORD dwID ;

    DWORD dwThread ;

    TERMINFO *termInfo ;

    termInfo = (TERMINFO *)lParam ;

    dwThread = GetWindowThreadProcessId(hwnd, &dwID) ;

    if(dwID == termInfo->dwID && termInfo->dwThread == dwThread )

    {

    PostMessage(hwnd, WM_CLOSE, 0, 0) ;

    }

    return TRUE ;

    }

    posted @ 2008-09-22 18:33 深邃者 阅读(203) | 评论 (0)编辑 收藏

    SQL

         摘要: SQL操作全集 SQL操作全集 下列语句部分是Mssql语句,不可以在access中使用。 SQL分类:  DDL—数据定义语言(CREATE,ALTER,DROP,DECLARE)  DML—数据操纵语言(SELECT,DELETE,UPDATE,INSERT)  DCL—数据控制语言(GRANT,REVOKE,COMMIT,ROLLBACK) 首先,简要介绍基...  阅读全文

    posted @ 2008-09-17 09:45 深邃者 阅读(98) | 评论 (0)编辑 收藏

    LIST控件

    CListCtrl使用技巧

    以下未经说明,listctrl默认view 风格为report


    1. CListCtrl 风格

          LVS_ICON: 为每个item显示大图标
          LVS_SMALLICON: 为每个item显示小图标
          LVS_LIST: 显示一列带有小图标的item
          LVS_REPORT: 显示item详细资料

          直观的理解:windows资源管理器,“查看”标签下的“大图标,小图标,列表,详细资料”



    2. 设置listctrl 风格及扩展风格

          LONG lStyle;
          lStyle = GetWindowLong(m_list.m_hWnd, GWL_STYLE);//获取当前窗口style
          lStyle &= ~LVS_TYPEMASK; //清除显示方式位
          lStyle |= LVS_REPORT; //设置style
          SetWindowLong(m_list.m_hWnd, GWL_STYLE, lStyle);//设置style
     
          DWORD dwStyle = m_list.GetExtendedStyle();
          dwStyle |= LVS_EX_FULLROWSELECT;//选中某行使整行高亮(只适用与report风格的listctrl)
          dwStyle |= LVS_EX_GRIDLINES;//网格线(只适用与report风格的listctrl)
          dwStyle |= LVS_EX_CHECKBOXES;//item前生成checkbox控件
          m_list.SetExtendedStyle(dwStyle); //设置扩展风格
     
          注:listview的style请查阅msdn
          http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceshellui5/html/wce50lrflistviewstyles.asp

     


    3. 插入数据

          m_list.InsertColumn( 0, "ID", LVCFMT_LEFT, 40 );//插入列
          m_list.InsertColumn( 1, "NAME", LVCFMT_LEFT, 50 );
          int nRow = m_list.InsertItem(0, “11”);//插入行
          m_list.SetItemText(nRow, 1, “jacky”);//设置数据

     


    4. 一直选中item

        选中style中的Show selection always,或者在上面第2点中设置LVS_SHOWSELALWAYS



    5. 选中和取消选中一行

        int nIndex = 0;
        //选中
        m_list.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
        //取消选中
        m_list.SetItemState(nIndex, 0, LVIS_SELECTED|LVIS_FOCUSED);
     


    6. 得到listctrl中所有行的checkbox的状态

          m_list.SetExtendedStyle(LVS_EX_CHECKBOXES);
          CString str;
          for(int i=0; i<m_list.GetItemCount(); i++)
          {
               if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED || m_list.GetCheck(i))
               {
                    str.Format(_T("第%d行的checkbox为选中状态"), i);
                    AfxMessageBox(str);
               }
          }



    7. 得到listctrl中所有选中行的序号


          方法一:
          CString str;
          for(int i=0; i<m_list.GetItemCount(); i++)
          {
               if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED )
               {
                    str.Format(_T("选中了第%d行"), i);
                    AfxMessageBox(str);
               }
          }

          方法二:
          POSITION pos = m_list.GetFirstSelectedItemPosition();
          if (pos == NULL)
               TRACE0("No items were selected!\n");
          else
          {
               while (pos)
               {
                    int nItem = m_list.GetNextSelectedItem(pos);
                    TRACE1("Item %d was selected!\n", nItem);
                    // you could do your own processing on nItem here
               }
          }



    8. 得到item的信息

          TCHAR szBuf[1024];
          LVITEM lvi;
          lvi.iItem = nItemIndex;
          lvi.iSubItem = 0;
          lvi.mask = LVIF_TEXT;
          lvi.pszText = szBuf;
          lvi.cchTextMax = 1024;
          m_list.GetItem(&lvi);

          关于得到设置item的状态,还可以参考msdn文章
          Q173242: Use Masks to Set/Get Item States in CListCtrl
                   http://support.microsoft.com/kb/173242/en-us



    9. 得到listctrl的所有列的header字符串内容

          LVCOLUMN lvcol;
          char  str[256];
          int   nColNum;
          CString  strColumnName[4];//假如有4列

          nColNum = 0;
          lvcol.mask = LVCF_TEXT;
          lvcol.pszText = str;
          lvcol.cchTextMax = 256;
          while(m_list.GetColumn(nColNum, &lvcol))
          {
               strColumnName[nColNum] = lvcol.pszText;
               nColNum++;
          }



    10. 使listctrl中一项可见,即滚动滚动条

        m_list.EnsureVisible(i, FALSE);


    11. 得到listctrl列数

        int nHeadNum = m_list.GetHeaderCtrl()->GetItemCount();


    12. 删除所有列

          方法一:
             while ( m_list.DeleteColumn (0))
           因为你删除了第一列后,后面的列会依次向上移动。

          方法二:
          int nColumns = 4;
          for (int i=nColumns-1; i>=0; i--)
              m_list.DeleteColumn (i);



    13. 得到单击的listctrl的行列号

          添加listctrl控件的NM_CLICK消息相应函数
          void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
          {
               // 方法一:
               /*
               DWORD dwPos = GetMessagePos();
               CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
      
               m_list.ScreenToClient(&point);
      
               LVHITTESTINFO lvinfo;
               lvinfo.pt = point;
               lvinfo.flags = LVHT_ABOVE;
        
               int nItem = m_list.SubItemHitTest(&lvinfo);
               if(nItem != -1)
               {
                    CString strtemp;
                    strtemp.Format("单击的是第%d行第%d列", lvinfo.iItem, lvinfo.iSubItem);
                    AfxMessageBox(strtemp);
               }
              */
      
              // 方法二:
              /*
               NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
               if(pNMListView->iItem != -1)
               {
                    CString strtemp;
                    strtemp.Format("单击的是第%d行第%d列",
                                    pNMListView->iItem, pNMListView->iSubItem);
                    AfxMessageBox(strtemp);
               }
              */
               *pResult = 0;
          }

     


    14. 判断是否点击在listctrl的checkbox上

          添加listctrl控件的NM_CLICK消息相应函数
          void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
          {
               DWORD dwPos = GetMessagePos();
               CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
      
               m_list.ScreenToClient(&point);
      
               LVHITTESTINFO lvinfo;
               lvinfo.pt = point;
               lvinfo.flags = LVHT_ABOVE;
        
               UINT nFlag;
               int nItem = m_list.HitTest(point, &nFlag);
               //判断是否点在checkbox上
               if(nFlag == LVHT_ONITEMSTATEICON)
               {
                    AfxMessageBox("点在listctrl的checkbox上");
               }
               *pResult = 0;
          }



    15. 右键点击listctrl的item弹出菜单

          添加listctrl控件的NM_RCLICK消息相应函数
          void CTest6Dlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult)
          {
               NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
               if(pNMListView->iItem != -1)
               {
                    DWORD dwPos = GetMessagePos();
                    CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
       
                    CMenu menu;
                    VERIFY( menu.LoadMenu( IDR_MENU1 ) );
                    CMenu* popup = menu.GetSubMenu(0);
                    ASSERT( popup != NULL );
                    popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this );
               }
               *pResult = 0;
      }


     


    16. item切换焦点时(包括用键盘和鼠标切换item时),状态的一些变化顺序

          添加listctrl控件的LVN_ITEMCHANGED消息相应函数
          void CTest6Dlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
          {
               NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
               // TODO: Add your control notification handler code here
       
               CString sTemp;
     
               if((pNMListView->uOldState & LVIS_FOCUSED) == LVIS_FOCUSED &&
                (pNMListView->uNewState & LVIS_FOCUSED) == 0)
               {
                    sTemp.Format("%d losted focus",pNMListView->iItem);
               }
               else if((pNMListView->uOldState & LVIS_FOCUSED) == 0 &&
                   (pNMListView->uNewState & LVIS_FOCUSED) == LVIS_FOCUSED)
               {
                    sTemp.Format("%d got focus",pNMListView->iItem);
               }
     
               if((pNMListView->uOldState & LVIS_SELECTED) == LVIS_SELECTED &&
                (pNMListView->uNewState & LVIS_SELECTED) == 0)
               {
                    sTemp.Format("%d losted selected",pNMListView->iItem);
               }
               else if((pNMListView->uOldState & LVIS_SELECTED) == 0 &&
                (pNMListView->uNewState & LVIS_SELECTED) == LVIS_SELECTED)
               {
                    sTemp.Format("%d got selected",pNMListView->iItem);
               }
       
               *pResult = 0;
          }




    17. 得到另一个进程里的listctrl控件的item内容

    http://www.codeproject.com/threads/int64_memsteal.asp



    18. 选中listview中的item

    Q131284: How To Select a Listview Item Programmatically
    http://support.microsoft.com/kb/131284/en-us



    19. 如何在CListView中使用CListCtrl的派生类

    http://www.codeguru.com/cpp/controls/listview/introduction/article.php/c919/



    20. listctrl的subitem添加图标

          m_list.SetExtendedStyle(LVS_EX_SUBITEMIMAGES);
          m_list.SetItem(..); //具体参数请参考msdn

     


    21. 在CListCtrl显示文件,并根据文件类型来显示图标

          网上找到的代码,share
          BOOL CTest6Dlg::OnInitDialog()
          {
               CDialog::OnInitDialog();
      
               HIMAGELIST himlSmall;
               HIMAGELIST himlLarge;
               SHFILEINFO sfi;
               char  cSysDir[MAX_PATH];
               CString  strBuf;
     
               memset(cSysDir, 0, MAX_PATH);
      
               GetWindowsDirectory(cSysDir, MAX_PATH);
               strBuf = cSysDir;
               sprintf(cSysDir, "%s", strBuf.Left(strBuf.Find("\\")+1));
     
               himlSmall = (HIMAGELIST)SHGetFileInfo ((LPCSTR)cSysDir, 
                          0, 
                          &sfi,
                          sizeof(SHFILEINFO), 
                          SHGFI_SYSICONINDEX | SHGFI_SMALLICON );
      
               himlLarge = (HIMAGELIST)SHGetFileInfo((LPCSTR)cSysDir, 
                          0, 
                          &sfi, 
                          sizeof(SHFILEINFO), 
                          SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
      
               if (himlSmall && himlLarge)
               {
                    ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                                 (WPARAM)LVSIL_SMALL, (LPARAM)himlSmall);
                    ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                                 (WPARAM)LVSIL_NORMAL, (LPARAM)himlLarge);
               }
               return TRUE;  // return TRUE  unless you set the focus to a control
          }
     
          void CTest6Dlg::AddFiles(LPCTSTR lpszFileName, BOOL bAddToDocument)
          {
               int nIcon = GetIconIndex(lpszFileName, FALSE, FALSE);
               CString strSize;
               CFileFind filefind;
     
               //  get file size
               if (filefind.FindFile(lpszFileName))
               {
                    filefind.FindNextFile();
                    strSize.Format("%d", filefind.GetLength());
               }
               else
                    strSize = "0";
      
               // split path and filename
               CString strFileName = lpszFileName;
               CString strPath;
     
               int nPos = strFileName.ReverseFind('\\');
               if (nPos != -1)
               {
                    strPath = strFileName.Left(nPos);
                    strFileName = strFileName.Mid(nPos + 1);
               }
      
               // insert to list
               int nItem = m_list.GetItemCount();
               m_list.InsertItem(nItem, strFileName, nIcon);
               m_list.SetItemText(nItem, 1, strSize);
               m_list.SetItemText(nItem, 2, strFileName.Right(3));
               m_list.SetItemText(nItem, 3, strPath);
          }
     
          int CTest6Dlg::GetIconIndex(LPCTSTR lpszPath, BOOL bIsDir, BOOL bSelected)
          {
               SHFILEINFO sfi;
               memset(&sfi, 0, sizeof(sfi));
      
               if (bIsDir)
               {
                SHGetFileInfo(lpszPath, 
                             FILE_ATTRIBUTE_DIRECTORY, 
                             &sfi, 
                             sizeof(sfi), 
                             SHGFI_SMALLICON | SHGFI_SYSICONINDEX |
                             SHGFI_USEFILEATTRIBUTES |(bSelected ? SHGFI_OPENICON : 0)); 
                return  sfi.iIcon;
               }
               else
               {
                SHGetFileInfo (lpszPath, 
                             FILE_ATTRIBUTE_NORMAL, 
                             &sfi, 
                             sizeof(sfi), 
                             SHGFI_SMALLICON | SHGFI_SYSICONINDEX | 
                             SHGFI_USEFILEATTRIBUTES | (bSelected ? SHGFI_OPENICON : 0));
                return   sfi.iIcon;
               }
               return  -1;
          }



    22. listctrl内容进行大数据量更新时,避免闪烁

          m_list.SetRedraw(FALSE);
          //更新内容
          m_list.SetRedraw(TRUE);
          m_list.Invalidate();
          m_list.UpdateWindow();
     
    或者参考

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_mfc_cwnd.3a3a.setredraw.asp



    23. listctrl排序

    Q250614:How To Sort Items in a CListCtrl in Report View
    http://support.microsoft.com/kb/250614/en-us



    24. 在listctrl中选中某个item时动态改变其icon或bitmap

    Q141834: How to change the icon or the bitmap of a CListCtrl item in Visual C++
    http://support.microsoft.com/kb/141834/en-us



    25. 在添加item后,再InsertColumn()后导致整列数据移动的问题

    Q151897: CListCtrl::InsertColumn() Causes Column Data to Shift
    http://support.microsoft.com/kb/151897/en-us



    26. 关于listctrl第一列始终居左的问题

    解决办法:把第一列当一个虚列,从第二列开始插入列及数据,最后删除第一列。
         
    具体解释参阅   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/listview/structures/lvcolumn.asp

     


    27. 锁定column header的拖动

    http://msdn.microsoft.com/msdnmag/issues/03/06/CQA/



    28. 如何隐藏clistctrl的列

        把需隐藏的列的宽度设为0,然后检测当该列为隐藏列时,用上面第27点的锁定column 的拖动来实现


    29. listctrl进行大数据量操作时,使用virtual list   

    http://www.codeguru.com/cpp/controls/listview/advanced/article.php/c4151/
    http://www.codeproject.com/listctrl/virtuallist.asp



    30. 关于item只能显示259个字符的问题

    解决办法:需要在item上放一个edit。



    31. 响应在listctrl的column header上的鼠标右键单击

    Q125694: How To Find Out Which Listview Column Was Right-Clicked
    http://support.microsoft.com/kb/125694/en-us



    32. 类似于windows资源管理器的listview

    Q234310: How to implement a ListView control that is similar to Windows Explorer by using DirLV.exe
    http://support.microsoft.com/kb/234310/en-us

     


    33. 在ListCtrl中OnTimer只响应两次的问题

    Q200054:
    PRB: OnTimer() Is Not Called Repeatedly for a List Control
    http://support.microsoft.com/kb/200054/en-us


    34. 以下为一些为实现各种自定义功能的listctrl派生类

              (1)    拖放       
                       http://www.codeproject.com/listctrl/dragtest.asp

                       在CListCtrl和CTreeCtrl间拖放
                       http://support.microsoft.com/kb/148738/en-us
     
              (2)    多功能listctrl
                       支持subitem可编辑,图标,radiobutton,checkbox,字符串改变颜色的类
                       http://www.codeproject.com/listctrl/quicklist.asp
     
                       支持排序,subitem可编辑,subitem图标,subitem改变颜色的类
                       http://www.codeproject.com/listctrl/ReportControl.asp

              (3)    subitem中显示超链接
                       http://www.codeproject.com/listctrl/CListCtrlLink.asp

              (4)    subitem的tooltip提示
                       http://www.codeproject.com/listctrl/ctooltiplistctrl.asp

              (5)    subitem中显示进度条   
                       http://www.codeproject.com/listctrl/ProgressListControl.asp
                       http://www.codeproject.com/listctrl/napster.asp
                       http://www.codeguru.com/Cpp/controls/listview/article.php/c4187/

              (6)    动态改变subitem的颜色和背景色
                        http://www.codeproject.com/listctrl/highlightlistctrl.asp
                        http://www.codeguru.com/Cpp/controls/listbox/colorlistboxes/article.php/c4757/
     
              (7)    类vb属性对话框
                        http://www.codeproject.com/listctrl/propertylistctrl.asp
                        http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c995/
                        http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c1041/
     
              (8)    选中subitem(只高亮选中的item)
                        http://www.codeproject.com/listctrl/SubItemSel.asp
                        http://www.codeproject.com/listctrl/ListSubItSel.asp
     
              (9)    改变行高
                        http://www.codeproject.com/listctrl/changerowheight.asp
     
              (10)   改变行颜色
                        http://www.codeproject.com/listctrl/coloredlistctrl.asp
     
              (11)   可编辑subitem的listctrl
                        http://www.codeproject.com/listctrl/nirs2000.asp
                        http://www.codeproject.com/listctrl/editing_subitems_in_listcontrol.asp
     
              (12)   subitem可编辑,插入combobox,改变行颜色,subitem的tooltip提示
                        http://www.codeproject.com/listctrl/reusablelistcontrol.asp
     
              (13)   header 中允许多行字符串
                        http://www.codeproject.com/listctrl/headerctrlex.asp
     
              (14)   插入combobox
                        http://www.codeguru.com/Cpp/controls/listview/editingitemsandsubitem/article.php/c979/
     
              (15)   添加背景图片
                        http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c4173/
                        http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c983/
                        http://www.vchelp.net/vchelp/archive.asp?type_id=9&class_id=1&cata_id=1&article_id=1088&search_term=
       
              (16)  自适应宽度的listctrl
                        http://www.codeproject.com/useritems/AutosizeListCtrl.asp

              (17)  改变ListCtrl高亮时的颜色(默认为蓝色)
                       处理 NM_CUSTOMDRAW
               http://www.codeproject.com/listctrl/lvcustomdraw.asp

    posted @ 2008-09-16 16:24 深邃者 阅读(385) | 评论 (0)编辑 收藏

    仅列出标题
    共5页: 1 2 3 4 5