Profil de Libin绿色家园PhotosBlogListes Outils Aide

Blog


29 mai

Install VTK Libraries on Windows

If you don't have VTK download it here

    Unzip VTK-4.2-LastestRelease.zip in $LibDir, this will create a new directory $LibDir /VTK otherwise rename it to VTK

    Run CMake and select the $LibDir/VTK directory as 'Where is the source code'
    Put the same path + "-VC++" (like Visual C++) as 'Where to build the binaries'
    Be sure that 'Visual Studio 6' is selected.
    Check 'Show Advanced Values'.

   Click on Configure, CMake will ask you to create the VTK-VC++ folder


    Put setup parameters exactly like that:
- BUILD_TESTING = OFF
- VTK_USE_ANSI_STDLIB = ON

- VTK_USE_HYBRID = ON
- VTK_USE_PATENDED = ON

    Click twice again on 'Configure' and then click on 'OK'. This will generate a Visual C++ project (*.dsw) in $LibDir/VTK-VC++.

    Launch $LibDir/VTK-VC++/VTK.dsw and compile in RelWithDebInfo mode.
To do that, right click on the menu and check 'Build
', you can also uncheck 'Build MiniBar'.

    Then select 'RelWithDebInfo'.

    Start Compilation with ALL_BUILD

How do I build the vtk VC6.0 projects on WinXP?

Author: Jing Li

Updated time: 30-06-2003

 

 

How do I build the vtk VC6.0 projects on WinXP?

 

I successfully built the VTK Cone.cpp example using MSVC6.0 on WinXP platform. I would like to share my experience of building the VTK VC6.0 project with you.

 

Step 1 .  Download vtk42-LatestRelease.exe under the Wintel Pre-Compiled Binaries (Windows 9x/NT) section from http://public.kitware.com/VTK/get-software.php to your harddisk, and install them under "c:\program files\vtk42" directory. In this directory, there will be several folders: bin (holds vtk*. dll files),  include (holds vtk*.h header files ) and lib (holds vtk*.lib files)

 

Step 2. Copy vtk*.dll files to c:\windows\system32

 

Step 3. Start MSVC6.0, go to File, New, Project, and create a Win32 Console Application

 

Step 4. Add the C++ source files Cone.cpp to the project. (It is better to copy the source file to the current project directory first, then add it to the current project)

 

Step 5. Tell the MSVC6.0 the directories that hold the vtk include header files and vtk library files. Go to Tools, Options. Select the tab Directories. Under the list for Include files add: C:\PROGRAM FILES\VTK42\INCLUDE\VTK; Under the list for Library files add: C:\PROGRAM FILES\VTK42\LIB\VTK.

 

 

Step 6. Add vtk*.libs to the project setting. Go to Project, Settings, and select the Link tab. In the General category, add vtk*.libs to the Object/Library Modules, which are vtkCommon.lib vtkFiltering.lib vtkGraphics.lib vtkHybrid.lib vtkImaging.lib vtkIO.lib vtkpng.lib vtkjpeg.lib vtkParallel.lib vtkRendering.lib vtkzlib.lib (please note there is a space between each library name)

 

 

After setting up all of the steps, you can build the vtkVC6.0 project, and run it.

 

 

You can convert the vtk VC6.0 project to a vtk VC .Net project straightway.  

28 mai

VC补遗之Debug篇

作者: 晨光(Morning) 关键字: VC  Debug 调试技巧  来源: 原作
【声明】如需复制、传播,请附上本声明,谢谢。原文出处:http://morningspace.51.net/ moyingzz@etang.com

引子

  前阵子因为工作的需要,翻阅了《Visual C++ 6宝典》一书。虽然自己接触VC也有些年头了,可却发现里面也有不少内容是我鲜有了解的,以下是我摘录并整理的部分内容,希望会对经常使用VC却有着和我一样情况的朋友有所帮助,本文取名"补遗"也正是出于此意。当然,这里所选的条目,或许稍有偏向,因为毕竟是从自己的角度出发,摘录了自认为容易忽略的内容。如果,你需要了解此处未有提及的其他内容,请查阅相关书籍。

调试应用程序前所要做的必要设置

   Project Settings->C/C++选项卡->General Category

  • Debug info选择Program Database for Edit and Continue
  • Optimizations选择Disable(Debug)
  • 若不选择Generate browse info,可以节省编译时间

   Project Settings->C/C++选项卡->Code Generation Category

  • Use run-time library选择Debug库(比如:Debug Multithreaded DLL)

   Project Settings->Link选项卡->General Category

  • 选择Generate debug info

  如果调试DLL,则需要:Project Settings->Debug选项卡->General Category

  • Executable for debug session填入加载和访问DLL的程序的名称(比如:为了调试ActiveX控件,你可以使用VC的实用程序tstcon32.exe)

Variables窗口

   Variables窗口包含三个选项卡。Auto选项卡显示运行程序当前行以及上一行中所使用的变量;Locals选项卡显示在当前执行的函数内具有局部定义的所有变量(包括局部变量和传入函数的参数);this选项卡显示this指针当前所指向的对象内部的成员变量

  你可以通过Variable窗口修改简单类型变量的当前值(比如:int、double),只要在窗口中双击该值就可以修改了。

Watch窗口

  可以在Watch窗口中手工将表达式输入到Name域中,或者拖拽表达式,或者从剪贴板粘贴。Watch窗口也可以修改简单类型的变量的值。

Memory窗口

  可以查开所有调试进程的地址空间中的内存内容。可以按字节格式、字格式和双字格式显示内存内容。为显示具体位置处的存储内容,要在Memory窗口的Address栏内键入表达式。Memory窗口可能显示指定地值之前的内存内容,不过它会将光标定位在表达式对应的地址处。

Disassembly窗口

   Set Next Statement功能可以更改处理器的指令指针,以为处理器选择需要执行的下一条指令。但是,如果你设置的语句在另一个函数中或者你不能正确维护堆栈,则后果是不可预测的,通常正在调试的程序会崩溃。

调试多线程

  调试多线程应用程序时,可以通过Debug菜单(调试运行时才可见)下的Threads功能,打开Threads对话框,从而将焦点设置给程序内的一个具体线程。还可以挂起、恢复某个线程

调试异常

  通过Debug菜单(调试运行时才可见)下的Exceptions功能,打开Exceptions对话框,指定调试运行时,程序对异常的响应情况(包括:Stop always和Stop if not handled两种)。

简单调试技巧

   - 使用AfxMessageBox

  如果调试程序窗口的出现干扰了应用程序的执行,或者错误出现在release版程序中,则可以使用AfxMessageBox输出你所感兴趣的信息。这类似于早先用printf输出调试信息的方法。

   - 使用TRACE宏:

   MFC提供了宏TRACE、TRACE0、TRACE1、TRACE2、TRACE3,用以生成调时输出。它们将调试信息输出到afxDump对象,该变量是MFC的CDumpContext类的一个实例,它将信息发送到Output窗口。尽量使用TRACE0~TRACE3,因为它们需要的存储空间小于TRACE。TRACE0输出一个字符串,TRACE1输出字符串并可附带一个变量,TRACE2可以附带两个变量,TRACE3可以附带三个。在创建Unicode应用程序时,TRACE必须使用_T宏格式化字符串,而TRACEn不需要。release版应用程序将忽略所有的TRACE和TRACEn宏。

   - 使用断言:

  除了ASSERT外,ASSERT_VALID宏可以验证一个指向CObject派生类对象的指针的有效性。release版应用程序将忽略所有的ASSERT和ASSERT_VALID宏。

   - 使用Dump

   CObject类中包含Dump()成员函数,它将内容通过afxDump对象输出到Output窗口。你可以在派生类中重载它。

   - 内存泄漏:

  可以使用CMemeoryState检测内存泄漏。方法:
    - 在你感兴趣的那段代码之前,创建CMemeoryState对象
    - 调用Checkpoint函数
    - 在你感兴趣的那段代码之后,调用DumpAllObjectsSince函数

  如果不需要完全dump在两次调用Checkpoint之间被分配的所有对象,则:
    - 在你感兴趣的那段代码之前,创建CMemeoryState对象msBeforeCall, msAfterCall, msDiffBA
    - 调用msBeforeCall的Checkpoint函数
    - 在你感兴趣的那段代码之后,调用msAfterCall的Checkpoint函数
    - 调用msDiffBA的Difference函数对msBeforeCall和msAfterCall进行比较
    - 若两者存在差异,则调用msDiffBA的DumpStatistics

  无法使用CMemoryState对malloc/free,GlobalAlloc/GlobalFree,LocalAlloc/LocalFree进行检测。

   - 使用MFC Tracer

   MFC Tracer(文件名:tracer.exe),可以启用或禁用部分或全部MFC跟踪调试信息(TRACE和TRACEn宏)。

远程调试

  远程调试允许你调试运行在与你机器相连的其他机器上的程序。远程机器必须运行Visual C++ Debug Monitor(Msvcmon.exe,和MSDEV.exe在同一个目录下),为了运行Msvcmon.exe,远程机器上必须有: Msvcrt.dll、Tln0t.dll、Dm.dll 、Msvcp60.dll、Msdis100.dll 。步骤:

  • 在远程机器上运行Msvcmon.exe,选择Network(TCP/IP),选择Settings设置目标机器的名称,再选择Connect
  • 在远程机器上运行要调试的程序
  • 在本地机器上运行Visual Studio,选择Build->Debugger Remote Connection打开Remote Connection,选择Network(TCP/IP),选择Settings设置目标机器的名称
  • 按F5开始调试程序

  远程调试的主要优点是应用程序运行在一个不会因为出现调试程序而受影响的机器上,适合于调试涉及显示、键盘、音频驱动等程序。同时,也适合于调试debug版能正常运行而release版不能正常运行的程序。

VC补遗之Profile篇

作者: 晨光(Morning) 关键字: VC  Profile 性能优化  来源: 原作
【声明】如需复制、传播,请附上本声明,谢谢。原文出处:http://morningspace.51.net/ moyingzz@etang.com

   (续篇)

Profile的作用

  帮助你分析并发现程序运行的瓶颈,找到耗时所在,同时也能帮助你发现不会被执行的代码。从而最终实现程序的优化。

Profile的组成

   Profile包括3个命令行工具:PREP,PROFILE,PLIST。可以以命令行方式运行Profile,其过程是:PREP读取应用程序的可执行文件并生成一个.PBI文件和一个.PBT文件;PROFILE根据.PBI文件,实际运行并分析程序,生成.PBO输出文件;PREP再根据.PBO文件和.PBT文件,生成新的.PBT文件;PLIST根据.PBT文件生成可阅读的输出。

Profile的具体功能

   - Function timing:对程序花费在执行特定函数上的时间进行评估。可以通过Profile对话框激活该功能。分析结果中,Func Time一栏以秒为单位记录了函数运行所花时间,下一栏显示了该函数时间占总运行时间的百分比;Func+Child Time栏记录了函数及其所调用的子函数运行所花的总时间,下一栏显示了前述时间占总运行时间的百分比;Hit Count栏记录函数被调用的次数;Function栏显示函数的名称。

   - Function coverage:记录特定函数是否被调用,可以用来确定代码中的未执行部分。可以通过Profile对话框激活该功能。分析结果列出所有被分析的函数,并使用*号标记执行过的函数。

   - Function counting:记录程序调用特定函数的次数。在Profile对话框中选择Custom,并在Custome Settings中指定fcount.bat(位于VC98\bin目录下)。需要注意的是,在指定fcount.bat所在目录时,最好不要用长文件名的方式,这样有可能出错,比如要将c:\Program Files写成c:\Progra~1。

   - Line counting:记录程序所执行的代码中特定行的次数。在Profile对话框中选择Custom,并在Custome Settings中指定lcount.bat(位于VC98\bin目录下)。该功能使用.EXE中的调试信息启动Profile,因此不需要.MAP文件。分析结果中,Line栏标示源代码的行号,Hit Count栏记录该行执行次数,下一栏显示了该行执行次数占所有代码行执行次数的百分比,Source Line显示了对应的源代码。

   - Line coverage:记录代码中的特定行是否被执行,可以用来确定代码中的未执行部分。可以通过Profile对话框激活该功能。分析结果列出所有被分析的代码行,并使用*号标记执行过的行。由于Line coverage只记录代码行是否被执行过,所以其执行开销要比Line counting小。

  此外,Profile对话框还提供了Merge功能,用以把多次运行Profile之后的统计结果组合起来。如果你正在使用Function coverage功能,则会看到是否测试了所有函数;如果你正在使用Function timing功能,则会看到以往分析与本次分析所有合并运行的累计时间。

IDE环境下Profile的使用

   - 对于涉及函数分析的功能

  • 选择Project->Settings->Link,选择Enable profiling复选框
  • 重建项目
  • 选择Build->Profile,弹出Profile对话框
  • 做必要设置后,选择OK,开始运行程序

   - 对于涉及行分析的功能

  • 选择Project->Settings->Link,选择Enable profiling复选框和Generate debug info复选框
  • 选择Project->Settings->C/C++,选择Line Numbers Only
  • 重建项目
  • 选择Build->Profile,弹出Profile对话框
  • 做必要设置后,选择OK,开始运行程序

配置Profile的三种方式

   - 修改profiler.ini文件

   profiler.ini位于VC98\bin目录下,在其[profiler]段中,你可以指定不参与分析的LIB文件或OBJ文件。比如:

[profiler]
exclude:user32.lib
exclude:gdi32.lib

   - 在Profile对话框中指定选项

  若你选择了Funciton timing、Function coverage或Line coverage选项,则你可以在Advanced settings中指定进一步的范围,比如:你希望Profile只分析SampleApp.cpp文件中特定范围内的代码,可以在Advanced settings中填入, /EXCALL /INC SampleApp.cpp(30-67) 。又如:你希望file1.obj和file2.obj不参与分析,则可以在Advanced settings中填入, /EXC file1.obj /EXC file2.obj 。再如:你希望只描述指定函数,则可以在Advanced settings中填入, /SF ?SampleFunc@@YAXPAH@@ ,紧跟SF参数的是特定函数的修饰符名,获取该名称的最简单的方式是在创建项目时生成的MAP文件中查找。

   SF,EXCALL,EXC,INC都是PREP的命令行参数,有关其他参数的详细说明可以通过在命令行提示符输入PREP /H得到。

   - 编写批命令文件

  可以参考fcount.bat、fcover.bat、ftime.bat、lcount.bat以及lcover.bat

从Profile中输出数据

   PLIST /T命令允许PLIST将.PBT文件内容以制表格式输出到文本文件中,该格式适合输入到电子表格或数据库中。比如:PLIST /T MYPROG > MYPROG.TXT,生成的MYPROG.TXT可以利用profiler.xlm(位于VC98\bin目录下)导入到Microsoft Excel电子表格中。

注意

   - 通常,分析整个程序的意义不大,因为大多数Windows应用程序,主要时间花费在消息等待上,因此精确定位要分析的代码,可以加快Profile的执行速度,提高其分析准确度。在Profile执行期间尽量关闭其他不相干的应用程序。

   - 若启用了远程调试,则不能够从Build菜单中调用Profile功能。

   - 对于inline函数,编译器以实际代码替换函数调用,因此inline函数不生成.MAP文件或CALL指令,所以当执行这样的函数时,Profile将无法得知,花费时间、运行次数等数据都归属于调用该函数的函数。Profile可以提供有关inline函数的行一级的运行次数和覆盖信息。

   - 对于多线程应用程序,Profile的行为取决于你所选择的分析方式,对于Line counting和Line coverage,Profile并未区分线程之间有何不同,它将包含当前运行的所有线程。对于Function timing、Function coverage和Function counting,分析结果取决于线程,你可以用以下方式分析一个独立线程:

  • 将线程的主函数声明为初始函数(用PREP /SF选项)
  • 包含程序中的所有函数(不要使用PREP /EXC选项)

  否则,分析结果很难解释。

C 的效率浅析

转自:http://www.itluntan.cn/ShowPost.asp?ThreadID=246
自从七十年代C语言诞生以来,一直以其灵活性、高效率和可移植性为软件开发人员所钟爱,成为系统软件开发的首选工具。而C 作为C语言的继承和发展,不仅保留了C语言的高度灵活、高效率和易于理解等诸多优点,还包含了几乎所有面向对象的特征,成为新一代软件系统构建的利器。

   相对来说,C语言是一种简洁的语言,所涉及的概念和元素比较少,主要是:宏(macro)、指针(pointer)、结构(struct)、函数(function)和数组(array),比较容易掌握和理解。而C 不仅包含了上面所提到的元素,还提供了私有成员(private members)、公有成员(public members)、函数重载(function overloading)、缺省参数(default parameters)、构造函数、析构函数、对象的引用(references)、操作符重载(operator overloading)、友元(friends)、模板(templates)、异常处理(exceptions)等诸多的要素,给程序员提供了更大的设计空间,同时也增加了软件设计的难度。

   C语言之所以能被广泛的应用,其高效率是一个不可忽略的原因,C语言的效率能达到汇编语言的80%以上,对于一种高级语言来说,C语言的高效率就不言而喻了。那么,C 相对于C来说,其效率如何呢?实际上,C 的设计者stroustrup要求C 效率必须至少维持在与C相差5%以内,所以,经过精心设计和实现的C 同样有很高的效率,但并非所有C 程序具有当然的高效率,由于C 的特殊性,一些不好的设计和实现习惯依然会对系统的效率造成较大的影响。同时,也由于有一部分程序员对C 的一些底层实现机制不够了解,就不能从原理上理解如何提高软件系统的效率。

   本文主要讨论两个方面的问题:第一,对比C 的函数调用和C函数调用,解析C 的函数调用机制;第二,例举一些C 程序员不太注意的技术细节,解释如何提高C 的效率。为方便起见,本文的讨论以下面所描述的单一继承为例(多重继承有其特殊性,另作讨论)。

class X { public: virtual ~X(); //析构函数 virtual void VirtualFunc(); //虚函数 inline int InlineFunc() { return m_iMember}; //内联函数 void NormalFunc(); //普通成员函数 static void StaticFunc(); //静态函数 private: int m_iMember; }; class XX: public X { public: XX(); virtual ~XX(); virtual void VirtualFunc(); private: String m_strName; int m_iMember2; };

C 的的函数分为四种:内联函数(inline member function)、静态成员函数(static member function)、虚函数(virtual member function)和普通成员函数。

   内联函数类似于C语言中的宏定义函数调用,C 编译器将内联函数的函数体扩展在函数调用的位置,使内联函数看起来象函数,却不需要承受函数调用的开销,对于一些函数体比较简单的内联函数来说,可以大大提高内联函数的调用效率。但内联函数并非没有代价,如果内联函数体比较大,内联函数的扩展将大大增加目标文件和可运行文件的大小;另外,inline关键字对编译器只是一种提示,并非一个强制指令,也就是说,编译器可能会忽略某些inline关键字,如果被忽略,内联函数将被当作普通的函数调用,编译器一般会忽略一些复杂的内联函数,如函数体中有复杂语句,包括循环语句、递归调用等。所以,内联函数的函数体定义要简单,否则在效率上会得不偿失。

静态函数的调用,如下面的几种方式:
X obj; X* ptr = &obj; obj.StaticFunc(); ptr->StaticFunc(); X::StaticFunc();

将被编译器转化为一般的C函数调用形式,如同这样:

mangled_name_of_X_StaticFunc();    //obj.StaticFunc(); mangled_name_of_X_StaticFunc();    // ptr->StaticFunc(); mangled_name_of_X_StaticFunc();   // X::StaticFunc();

mangled_name_of_X_StaticFunc()是指编译器将X::StaticFunc()函数经过变形(mangled)后的内部名称(C 编译器保证每个函数将被mangled为独一无二的名称,不同的编译器有不同的算法,C 标准并没有规定统一的算法,所以mangled之后的名称也可能不同)。可以看出,静态函数的调用同普通的C函数调用有完全相同的效率,并没有额外的开销。

普通成员函数的调用,如下列方式:
X obj; X* ptr = &obj; obj.NormalFunc(); ptr->NormalFunc();

将被被编译器转化为如下的C函数调用形式,如同这样。

mangled_name_of_X_NormalFunc(&obj);    //obj.NormalFunc(); mangled_name_of_X_NormalFunc(ptr); // ptr->NormalFunc();

可以看出普通成员函数的调用同普通的C调用没有大的区别,效率与静态函数也相同。编译器将重新改写函数的定义,增加一个const X* this参数将调用对象的地址传送进函数。

   虚函数的调用稍微复杂一些,为了支持多态性,实现运行时刻绑定,编译器需要在每个对象上增加一个字段也就是vptr以指向类的虚函数表vtbl,如类X的对象模型如下图所示(本文中对此不多做解释,若想进一步了解,可以参考其它材料)。
虚函数的多态性只能通过对象指针或对象的引用调用来实现,如下的调用:

X obj; X* ptr = &obj; X& ref = obj; ptr->VirtualFunc(); ref.VirtualFunc();

将被C 编译器转换为如下的形式。

<TABLE cellSpacing=0 cellPadding=2><TBODY><TR><TD class=code bgColor=#e6e6e6>( *ptr->vptr[2] )(ptr); ( *ptr->vptr[2] )(&ref);</TD></TR></TBODY></TABLE>

其中的2表示VirtualFunc在类虚函数表的第2个槽位。可以看出,虚函数的调用相当于一个C的函数指针调用,其效率也并未降低。

   由以上的四个例子可以看出,C 的函数调用效率依然很高。但C 还是有其特殊性,为了保证面向对象语义的正确性,C 编译器会在程序员所编写的程序基础上,做大量的扩展,如果程序员不了解编译器背后所做的这些工作,就可能写出效率不高的程序。对于一些继承层次很深的派生类或在成员变量中包含了很多其它类对象(如XX中的m_strName变量)的类来说,对象的创建和销毁的开销是相当大的,比如XX类的缺省构造函数,即使程序员没有定义任何语句,编译器依然会给其构造函数扩充以下代码来保证对象语义的正确性:

XX::XX() { // 编译器扩充代码所要做的工作 1、 调用父类X的缺省构造函数 2、 设定vptr指向XX类虚函数表 3、 调用String类的缺省构造函数构造m_strName };

所以为了提高效率,减少不必要的临时对象的产生、拖延暂时不必要的对象定义、用初始化代替赋值、使用构造函数初始化列表代替在构造函数中赋值等方法都能有效提高程序的运行效率。以下举例说明:

   1、 减少临时对象的生成。如以传送对象引用的方式代替传值方式来定义函数的参数,如下例所示,传值方式将导致一个XX临时对象的产生

效率不高的做法     高效率做法 void Function( XX xx ) void Function( const XX& xx ) { { //函数体 //函数体 } }

2、 拖延暂时不必要的对象定义。在C中要将所有的局部变量定义在函数体头部,考虑到C 中对象创建的开销,这不是一个好习惯。如下例,如果大部分情况下bCache为"真",则拖延xx的定义可以大大提高函数的效率。

效率不高的做法 高效率做法 void Function( bool bCache ) void Function( bool bCache ) { { //函数体 //函数体 XX xx; if( bCache ) if( bCache ) {// do something without xx { return; // do something without xx } return; } //对xx进行操作 XX xx; //对xx进行操作 … return; return; } }

3、 可能情况下,以初始化代替先定义后赋值。如下例,高效率的做法会比效率不高的做法省去了cache变量的缺省构造函数调用开销。

效率不高的做法 高效率做法 void Function( const XX& xx ) void Function( const XX& xx ) { { XX cache; XX cache = xx; cache = xx ; } }

4、 在构造函数中使用成员变量的初始化列表代替在构造函数中赋值。如下例,在效率不高的做法中,XX的构造函数会首先调用m_strName的缺省构造函数,再产生一个临时的String object,用空串""初始化临时对象,再以临时对象赋值(assign)给m_strName,然后销毁临时对象。而高效的做法只需要调用一次m_strName的构造函数。

效率不高的做法 高效率做法 XX::XX() XX::XX() : m_strName( "" ) { { m_strName = ""; … … } }

类似的例子还很多,如何写出高效的C 程序需要实践和积累,但理解C 的底层运行机制是一个不可缺少的步骤,只要平时多学习和思考,编写高效的C 程序是完全可行的。

24 mai

CThread - a Worker Thread wrapper class 1

Contents

Preface

CThread Specifics

Preface

CThread class written in Microsoft Visual C++ is a wrapper class that constitutes the base for the comfortable Windows worker thread handling in the MFC environment. CThread itself is an abstract class from which user thread-specific classes have to be derived. CThread class offers the opportunities how to define, implement and handle thread objects. Most functionality is done in this base class, a developer is just responsible to implement a thread-specific task and handle incoming notifications fired from the owner of the thread. CThread class is fully compliant to the Object-Oriented Paradigm.

CThread Class Conception

Thread-Task Paradigms

CThread abstract class defines conception describing the main requirements regarding the thread handling. There are two main paradigms concerning thread task implementation:

Trivial Threads

Thread task is a simple sequence of commands that are to be done. After starting the thread, the thread terminates after completing the whole task. Owner thread (typically the main application thread) may communicate with a CThread thread by some intermediate object visible for both sides. This communication, however, does not provide effective parent-child-thread notifications. Maintaining all risky situations originating in such communication requires an additional developer's effort. Using of this conception is recommended for linear or straightforward heavyweight tasks, more or less independent from the owner thread that do not require sophisticated or intensive communication with the owner thread. CThread thread supporting this paradigm is called Trivial Thread.

Notificable Threads

In opposite to Trivial Threads, the thread task may be application-sensitive and listens to the owner thread commands. In this case the thread task is a loop in which thread waits for incoming notifications (commands). After receiving a command the thread executes this command. Incoming commands are handled sequentially in the thread task procedure. Simultaneously, the thread may set the current thread activity status to inform the owner thread.

Notificable Threads act as 'schedulers' or 'services'. That means, these threads execute their task on the owner thread demand and wait for another command. Usually the task executed in such thread should not be too long to allow an owner thread to make an effective controlling over the thread. This thread is called Notificable Thread.

CThread class supports both paradigms but emphasizes developers to use Notificable Threads.

Thread Synchronization

Thread-Handler-Oriented Synchronization

CThread derived classes may utilize the special synchronization feature that is implemented in the basic CThread class. The mentioned Thread-Handler-Oriented Synchronization is a powerful feature provided by CThread class. Developers do not have to deal too much with synchronization among thread objects using the same thread-task handler (the ThreadHandler() method). They just use Lock() or Unlock() CThread methods to lock the critical code that is to be executed exclusively in the ThreadHandler() method. Developers may, however, omit this synchronization feature and define the CThread derived class, which does not support this kind of synchronization. It is up to the developer’s responsibility to implement non-critical thread task or instantiate just one thread object in such case.

Each CThread derived class requiring its own Thread-Handler-Oriented Synchronization must declare this feature explicitly (this can be automatically established while working with Worker Thread Class Generator Wizard).

This kind of synchronization is so-called Thread-Handler-Oriented. Suppose a developer utilizes two CThread derived classes CThreadDerived1 and CThreadDerived2 and each class implements its own ThreadHandler() method. Let Thread11 and Thread12 are the instantiated objects of the class CThreadDerived1 and Thread21 and Thread22 are the objects of the class CThreadDerived2. Thread11 is synchronized with Thread12 but not with Thread21 or Thread22 and vice versa. All thread objects of the CThreadDerived1 class are synchronized among each other (as well as all objects of the CThreadDerived2 class) but the synchronization of CThreadDerived1 objects is fully independent from the synchronization of CThreadDerived2 objects.

On the other hand, if the CThreadDerived3 class is derived from the CThreadDerived2 but do not implement its own ThreadHandler() method (uses the handler implemented in the CThreadDerived2 class), the CThreadDerived3 objects are automatically synchronized with the CThreadDerived2 objects and vice versa.

In a CThread object-oriented hierarchy developers may design several CThread -derived classes for different purposes. Some of them will implement the specific thread-task handlers the others will inherit them. Thread-Handler-Oriented Synchronization allows developers to split CThread objects into the groups that contain CThread -derived objects operating on the same thread-task handler. Thus, each group defines its own thread-task handler and logically provides the synchronization for all objects belonging to this group. On the other hand, another group operates on a different thread-task handler, executing a completely different task independent from another group, so the synchronization has also to be independent from another group. Thread-Handler-Oriented Synchronization establishes and maintains the independent synchronization for each group automatically. All the developer has to do is to write the SUPPORT_THREAD_SYNCHRONIZATION(ClassName) macro in the constructor in his CThread-Derived ClassName class where the thread-task handler (the virtual ThreadHandler() method) is actually implemented. Thread-Handler-Oriented Synchronization is supported for both Trivial and Notificable CThread Threads.

Single Thread Object Synchronization

CThread class itself implements an internal synchronization. This is the special synchronization feature that is not visible from within the owner thread. Internal synchronization does not impact Lock() or Unlock() mechanism mentioned in the above paragraph under any circumstances. The benefit of the internal synchronization is a synchronized access to critical CThread methods while accessing the one instance of CThread object asynchronously.

This kind of synchronization is sometimes called Single Thread Object Synchronization. If, for example, the childThread is an instance of CThread-Derived class, which is shared by two other (arbitrary) threads parentThread1 and parentThread2, both these parent threads operate on the childThread object asynchronously. The internal synchronization implemented in CThread class guarantees that all critical childThread methods will be executed properly regardless the parent thread owning the current childThread focus. The only care that must be taken is that none of the parent threads deletes the childThread's CThread object while another is still operating on it.

Despite the given guaranty there is one situation where the concurrent operating on the same object may be confusing. It concerns mainly Notificable Threads and sending commands from the parent threads to the childThread. If both parent threads send several commands asynchronously to the childThread there is no any guaranty about which command will be actually handled in the childThread at a moment. The similar situation may occur also in Trivial Threads while setting up some important controlling object property (or variable) used in the childThread by both parent threads concurrently. For this reason, a concurrency-handling-of-one-object design has to be carefully prepared.

This kind of synchronization is established automatically while registering CThread class in an application process for both Trivial and Notificable Threads.

Process Synchronization

CThread-Derived class offers also a global locking mechanism, which is exclusive for the whole process. The user may use ProcessLock() or ProcessUnlock() CThread static methods (that were previously opened by OpenProcessLocking() method) wherever in a code. These methods are static so there is no necessary to instantiate any CThread object. User may use this synchronization mechanism to accomplish an exclusive access to the global-critical resources (opening the file, common communication object, singleton etc.). Process Synchronization may be used in an arbitrary part of the code not necessarily in CThread tasks only.

The mentioned synchronization does not support an inter-process synchronization.

Thread Notification

Notificable Threads react to the owner-thread incoming commands and allow the owner to obtain the current thread activity status. Owner thread usually uses the PostCommand() method which fires an appropriate command to the thread. Thread immediately reacts to the incoming command (inside ThreadHandler() virtual method) and is responsible to handle this command. Simultaneously, the thread should set the meaningful current activity status by using SetActivityStatus() method. Owner thread uses GetActivityStatus() method to obtain the current status.

To establish a Notificable thread a developer has to add the SUPPORT_THREAD_NOTIFICATION macro in his CThread-Derived class constructor where the thread-task handler (the virtual ThreadHandler() method) is actually implemented. He also has to implement the thread-task handler following the specific rules discussed later. The whole stuff may be arranged automatically by using the Worker Thread Class Generator Wizard that is discussed in the chapter 2.

Commands

The user should always communicate with CThread notificable threads via commands. Start(), Pause(), Continue(), Reset(), Stop() or the general PostCommand() CThread methods are intended for such use. Although, this is not the only way how to communicate with CThread threads (we may, for example, use methods of a specific object operating in the thread handler directly), it is highly recommended to use the mentioned command-communication. The main benefit is a synchronized and logical access to the thread-task code. Incoming commands fired from everywhere fill up an internal CThread command queue and are handled sequentially in the same order they were fired - first-in-first-out (cyclic stack mechanism). For example, the calling sequence: Start(), Pause(), Continue(), Stop() fired in one step will be handled exactly in the same order in which they were called.

CThread class introduces four helper methods wrapping the PostCommand() method: Run(), Pause(), Continue() and Reset(). All they do is just firing the corresponding command to a CThread thread. These methods just remind the similar functionality schema provided by schedulers or services. Developers may decide how to interpret these methods or they may decide not to utilize them at all. What is important is to handle an appropriate command in the ThreadHandler() method when a developer decides to use these helpers.

Racing Conditions

To avoid racing discrepancies between threads, developers should follow several guidelines while building an application using CThread threads. An owner thread is responsible for a CThread thread lifetime. From the child CThread thread point of view, thus, the owner thread exists during the whole thread lifetime. The only responsibility for such thread is not to destroy the owner thread explicitly.

On the other hand, from the owner-thread point of view, CThread thread may terminate unpredictably. CThread object, however, guarantees that each CThread method call will be semantically properly executed regardless the attached Windows thread is alive or not. In other words, none CThread method will hang after the thread will have been prematurely terminated.

The owner thread (or any thread) may utilize some CThread methods to find out the existence status of the thread (IsAlive(), GetExitCode(), GetActivityStatus() etc.). Furthermore, the owner thread may be notified by some user specific callback that is initiated from within the CThread thread to be invoked immediately when some important situation occurs. Similarly, this thread may set up a useful variable (or an object property) which is shared by the owner thread as well (in Trivial Threads).

For CThread threads cooperating among each other it is a developer's responsibility how to maintain racing conditions. This usually strongly depends on the concrete architecture of an application. In general, avoiding racing problems requires a proper thread-communication design and an additional developing effort. Developers may also consult some design-patterns discussing this theme.

Synchronous versus Asynchronous Methods; Deadlocks

There is one big dilemma in a thread theory - "use or not to use" synchronous methods for controlling of threads. An advantage of such schema is a guaranty that each method sending a command to a child thread invoked from the owner thread waits until the child thread actually completes the command. If, for example, the thread is forced to be paused by some, for example, PauseThread() method called from the owner thread, PauseThread() will wait until the thread is actually paused. This works fine for many worker threads that are more or less independent from their owner threads. Usually, SetEvent-WaitForSingleObject programming model is used in this schema.

Unfortunately, there are many cases when this schema is inapplicable. In GUI applications requiring bi-directional communication between an application and a thread this model is a priori inappropriate. Following the previous example, if a GUI application calls the PauseThread() method it may wait a long time for the thread completion which leads to a message queue blocking. In this situation the application hangs until the method is completed. Moreover, if for any reason the PauseThread() method hangs or fails the application may hang forever.

Even worse situation occurs when the thread tries to cooperate with the main GUI application using the PostMessage() and SendMessage() functions. If PauseThread() is invoked in the application and the thread sends consequently some message back to the application waiting for a response, this message cannot be handled in the application. The message is namely to be handled by the application thread but the application thread is blocked by the PauseThread() method. This is a serious situation leading to an excellent deadlock. The only safe using of a synchronous thread-controlling method in a GUI application is the case when the thread does not require any task to be executed by the application thread while completing the appropriate command.

CThread class uses synchronous methods as few as possible. There are just four methods that works synchronously: Start() and Kill() methods that are, fortunately, not dangerous because of their principal independence from the owner thread, and Stop() and WaitForActivityStatus() methods for which a special care has to be taken. For more information concerning these methods refer to CThread Reference Manual (CThread.hlp or CThread.htm).

Instead of distinguishing among many possible scenarios how to manage synchronous calls, CThread class provides only one general method for such purposes: WaitForActivityStatus(). This method is called from the owner thread and just waits until the thread sets up a desired thread activity status. The method, however, requires a proper synchronous-call design varying from case to case (see CThread Reference Manual).

Stopping CThread threads is discussed in details in the next paragraph.

CThread and GUI

CThread object wraps a Windows worker thread. Nevertheless, the thread may be used in GUI context as well. As a worker thread CThread object does not contain its own message queue and therefore shares the same queue as the main application thread. Thread itself communicates with this queue (or a Windows window procedure directly) usually via the PostMessage() and SendMessage() functions or methods utilizing these functions implicitly. Using thePostMessage() function is generally considered as more safe.

Sending messages from the CThread thread to the main application message queue is preferred by using the Windows function PostMessage(hWnd, nMsg, lParam, wParam) requiring the original Windows HWND handle of the main application window. Generally, there is no guaranty about lifetime of C++ objects originating in a different thread. These C++ objects have to be handled carefully in the CThread context.

Normally, there is not big problem while communicating with the message queue belonging to the main application window (if no synchronous thread-controlling methods are called from the main thread - see the previous paragraph). CThread thread posts messages to the main window in the same way as other objects do in the main application. Posted message is added at the end of the queue and sequentially handled. However, there is one important exception - stopping the thread. Stopping the CThread thread is accomplished by the CThread's Stop() method called from the main application thread. Stop() method is primarily a synchronous method and should wait until the CThread thread actually finishes. If the CThread thread is forced to stop from within the application but is still communicating with the application thread or the message queue (and eventually waiting for message completion - some methods like the List Control's DeleteAllItems() use implicit SendMessage() notification), that thread may not be properly terminated. The thread waits for the message completion that is to be done in the main application thread but the application thread itself is blocked by the Stop() method in some message handler. This leads to a deadlock and the application freezes.

To get out of such difficulties we have to use one 'tricky' method. Suppose we are using the CThread thread communication with the application message queue (or, generally, with the application thread, e.g. through SendMessage()). First of all, a developer has to define a special activity status, for example, THREAD_PREPARED_TO_TERMINE in his CThread-Derived class. This activity status describes the situation that the CThread thread having set up this activity status will not utilize neither the application message queue nor the application thread any more, thus, from this moment the thread will neither post nor send any Windows message to the application. The fragment of code setting up this activity status in the CThread's ThreadHandler() method is as follows:

...

case CThread::CMD_RUN:
   SetActivityStatus(CThread::THREAD_RUNNING);
   // communicate with the main application message 
   // queue/application thread
   PostOrSendMessagesToTheApplication();
   break;

   ...

case CThread::CMD_STOP:
   // stop communication with the application
   CompleteCommunicationFinally();
   // and set up the necessary status informing the application
   // that this thread will not utilize the message queue/thread any more
   SetActivityStatus(CThreadDerived::THREAD_PREPARED_TO_TERMINE);
   bContinue = FALSE; 
   break;

....

The next step is to stop the CThread thread from within the main application (owner thread). The best way is to use the main frame's OnClose() method in main-frame applications (in dialog-based applications the situation is described as well). The main idea is to suppress closing the main frame window until the CThread thread's activity status is equal to THREAD_PREPARED_TO_TERMINE. This is accomplished by periodical firing the WM_CLOSE message from within the OnClose() method to prevent an application-thread-blocking and allowing the CThread thread to complete its eventual message handling. The whole mentioned stuff is accomplished in the following code in the main application:

For main-frame-based applications (SDI, MDI):

Collapse
CMainFrame::OnClose() 
{ 
   DWORD dwExitCode;
   // force the thread to stop but leave immediately 
   // (the second parameter = 0) 
   m_myThread.Stop(dwExitCode, 0);
   if (m_myThread.GetActivityStatus() != 
      CThreadDerived::THREAD_PREPARED_TO_TERMINE) 
   { 
      // invoke this handler again (keeping the message 
      // loop enabled for the final
      // CThread thread message handling as well as 
      // preventing the application thread 
      // from blocking)
      PostMessage(WM_CLOSE); 
   } 
   else 
   { 
      // here the <CODE>CThread thread doesn't use 
      // the message queue/application thread
      // anymore, so we can stop the thread synchronously 
      // to ensure that the thread
      // is actually terminated
      m_myThread.Stop(dwExitCode);
      // ... and close the main frame properly 
      CFrameWnd::OnClose(); 
   }; 
}

Analogically for dialog-based applications (we will use OnCancel() virtual method here - this method is not a message handler!):

CMyDialog::OnCancel() 
{ 
   DWORD dwExitCode;
   m_myThread.Stop(dwExitCode, 0);
   if (m_myThread.GetActivityStatus() != CThreadDerived::THREAD_MSG_SESSION_CLOSED) 
   { 
      // call this handler again
      // IDCANCEL is the dialog's 'Cancel' button resource ID
      PostMessage(WM_COMMAND, MAKEWPARAM((WORD)IDCANCEL, (WORD)IDCANCEL));
   } 
   else 
   { 
      m_myThread.Stop(dwExitCode);
      // ... and close the dialog properly 
      CDialog::OnCancel();
   }; 
}

In some specific cases more complex handling is needed. Developers may want to use the special flag indicating the end of message handling initiated in the CThread thread. They may also want to stop the thread in some other non-finalize method. But the basic idea not to block the application thread and periodically call the stop-thread-handler by the PostMessage() method sketched in the above code remains the same.

Generating CThread-Derived Class Source Code

CThread-Derived classes can be generated automatically by using the Worker Thread Class Generator Wizard enclosed in this delivery. A user may choose the base CThread derived class from which he wants to derive his own Trivial or Notificable CThread-Derived class and implements the thread specific task by writing down the code in the ThreadHandler() method skeleton. He may also establish some kind of synchronization or share the synchronization already established in the selected base class. The Worker Thread Class Generator workaround does not require any special explanation and it's intuitive at first glance.

Implementing CThread Task Handler

After generating the CThread-Derived class source the developer has to implement the thread handler at least in one such class (from the class-object-hierarchy point of view). The thread handler is declared and implemented in the *.h and *.cpp files as a virtual method:CThreadDerived::ThreadHandler().

According to the article 1 the user may implement one of the two possible paradigms.

1. Trivial Thread: Simple sequence of commands. ThreadHandler() in this case may look as follows:

DWORD CThreadDerived::ThreadHandler()
{
   BOOL bEverythingOK;
   <command_1>;
   <command_2>;
   <command_3>;
   ...
   <command_n>;
   ...
   if (bEverythingOK)
      return CThread::DW_OK ; // return 0 if finished successfully
   else
      return CThread::DW_ERROR; // return –1 otherwise
}

2. Notificable Thread: ThreadHandler() implements the task which is sensitive to the owner thread incoming commands. The owner thread may, in turn, obtain the current thread activity status in an arbitrary phase of running thread. Thread Handler() in such case should look somehow like this:

Collapse
// CThread derived class supporting thread object synchronization
DWORD CThreadDerived::ThreadHandler()
{
   BOOL bContinue = TRUE;
   int nIncomingCommand;
   do
   {
      WaitForNotification(nIncomingCommand, CThreadDerived::DEFAULT_TIMEOUT);
      /////////////////////////////////////////////////////////////////////
      // Main Incoming Command Handling:
      /////////////////////////////////////////////////////////////////////
      switch (nIncomingCommand)
      {
      case CThread::CMD_TIMEOUT_ELAPSED:
         if (GetActivityStatus() != CThread::THREAD_PAUSED)
         {
            UserSpecificTimeoutElapsedHandler();
            // fire CThread::CMD_RUN command immediately...
            // ... from within this thread use always the 
            // following method when you
            // want to fire an additional command. 
            // This method bypasses all incoming
            // commands and forces the passed command 
            // to be handled immediately
            HandleCommandImmediately(CMD_RUN); 
         }
         break;

      case CThread::CMD_INITIALIZE: // initialize Thread Task
         // this command should be handled; it’s fired when the Thread starts
         UserSpecificOnInitializeHandler();
         // execute CThread::CMD_RUN command immediately
         HandleCommandImmediately(CMD_RUN);
         break;

      case CThread::CMD_RUN: // handle 'OnRun' command
         if (GetActivityStatus() != CThread::THREAD_PAUSED)
         {
            SetActivityStatus(CThread::THREAD_RUNNING);
            UserSpecificOnRunHandler();
            // the critical code which requires an exclusive handling
            // for all threads operating on this handler
            // (Thread-Handler-Oriented Synchronization)
            Lock(); // <CODE>CThread method
            UserSpecificCriticalCode();
            Unlock(); // CThread method
         }
         break;

      case CThread::CMD_PAUSE: // handle 'OnPause' command
         if (GetActivityStatus() != CThread::THREAD_PAUSED)
         {
            UserSpecificOnPauseHandler();
            SetActivityStatus(CThread::THREAD_PAUSED);
         }
         break;

      case CThread::CMD_CONTINUE: // handle 'OnContinue' command
         if (GetActivityStatus() == CThread::THREAD_PAUSED)
         {
            SetActivityStatus(CThread::THREAD_CONTINUING);
            UserSpecificOnContinueHandler();
            // execute CThread::CMD_RUN command if necessary
            HandleCommandImmediately(CMD_RUN); 
         }
         break;

      case CThreadDerived::CMD_USER_SPECIFIC: 
        // handle the user-specific command
         UserSpecificOnUserCommandHandler();
         break;

      case CThread::CMD_STOP: // handle 'OnStop' command
         UserSpecificOnStopHandler();
         bContinue = FALSE; // ... and leave the thread function finally
         break;

      default: // handle unknown commands...
         break;
      };

   } while (bContinue);

   return (DWORD)CThread::DW_OK; //... if thread task completion OK
}

Establishing (and starting) thread objects of the CThreadDerived class in the owner thread as well as handling these threads may look as in the following example:

CMainProgram::HandleThreads();
{
   CThreadDerived thread1, thread2;
   // start threads
   try
   {
      thread1.Start();
      thread2.Start();

      ...

      thread1.Pause();

      ...

      thread2.Continue();

      // ... or send the user-specific command...
      // (must be recognizable in the ThreadHandler())
      thread2.PostCommand(CThreadDerived::CMD_USER_SPECIFIC);
      ...

      // stop threads
      thread1.Stop(); // (synchronous) waits until the thread actually finishes
      thread2.Stop(); // (synchronous) waits until the thread actually finishes
   }
   catch (CThreadException* pe)
   {
      pe->ReportError();
      pe->Delete();
   };
}

Note 1:

The communication from the owner thread to the Notificable Thread should always be established by sending the commands that are recognizable in the ThreadHandler() method.

Note 2:

Phrases using italic font in the mentioned source code list mean CThreadDerived specific methods or data members. All others are Windows System functions or CThread provided methods and data members.

Important Notes

  • A user may utilize CThread -constructor parameters while constructing a CThread object. The first parameter is a void pointer and the second is LPARAM value. Void pointer may point to an arbitrary useful object (an owner of this thread e.g.) or to be a HWND Window handle with which the CThread thread cooperates. During thread task operation the thread may notify the owner object if needed. For example, the running thread may set the task progress position in the progress bar implemented in the owner object. Thread must, of course, be familiar with the architecture of the owner object.
  • While using CThread -threads users should not use those Windows thread-specific functions that are responsible for creation or termination of Windows threads in the CThread context. Keep in mind that CThread threads are maintained by the specific CThread architecture. Creating threads in the terms of CThread class means various internal allocations, settings and synchronization-registering (in the Thread-Handler-Oriented Synchronization model) that are correctly removed only by using the specific CThread operations (leaving the thread controlling function, CThread class destructor, Stop() and Kill() methods). Termination of a thread by e.g. ::TerminateThread() Windows function, bypasses the unregistration model provided by the CThread class which may result to several deadlocks or, even, to a crash. The following list contains Windows thread-specific functions that should not be used in the CThread context:

    • CreateThread()
    • CreateRemoteThread()
    • ExitThread()
    • TerminateThread()
    • ThreadProc()
    • CloseHandle() (this especially may lead to a catastrophic failure)

    Users may, of course, utilize the mentioned functions but under their own thread strategy omitting CThread features.

  • Users should also prefer the CThread::GetExitCode() method instead of GetThreadExitCode() Windows function. This method offers the valid thread-exit-code regardless the Windows thread is alive, destroyed or closed. The method works fine even if the handle belonging to the Windows thread is not more valid.
  • Although CThread class provides the GetHandle() method returning a Windows handle of a running thread, this method must be used very carefully - if ever. CThread architecture considers this value as an internal variable. The handle is automatically closed (and zeroed) after the thread leaves the thread controlling function. In general, the state of the handle is, therefore, unpredictable. Developers, therefore, have to take care about using the Windows thread handle regarding the thread lifetime in a specific application.
  • Opened CThread session must be properly "closed". That means, all CThread -specific internal allocations and registering are to be closed at the end of the CThread session. CThread class itself accomplishes this stuff automatically when the corresponding Windows thread naturally finishes (leaves the thread controlling function mapped to the CThread's ThreadHandler() method). Otherwise CThread object provides three possibilities how to stop the running thread and make the necessary clean up automatically. Users should always use only the following strategies while stopping the CThread thread explicitly:
    1. CThread's Stop() method forces the thread to the regular stopping and the clean-up is accomplished automatically after leaving the ThreadHandler() method. The best way.
    2. CThread's Kill() method destroys the running Windows thread and cleans up the whole contents on its own. Possible, but should be used in emergency cases only.
    3. CThread object going out of scope invokes a destructor that kills the Windows thread by calling the Kill() method (see the previous point). Ugly not recommended way.

Additional Documentation

More detailed information concerning CThread class can be found in CThread Reference Manual (CThread.hlp, CThread.htm and CThread.doc files) in '/Doc' subdirectory of the main installation directory.

Author: Dominik Filipp, © 1999, Bratislava, Slovakia, Europe
E-mail address: Dominik.Filipp@work4.sk

CThread Specifics

To display a list of topics by category, click any of the contents entries below. To display an alphabetical list of topics, choose the Index button.

C/C++ Elements

Classes and Class Members
Structures and Enums

Other

Overviews
Modules

Help file built: 08/30/99

About Autoduck


About Autoduck

The sources for this Help file were generated by Autoduck, the source code documentation tool that generates Print or Help files from tagged comments in C, C++, Assembly, and Basic source files.

For more information, contact Eric Artzt (erica@microsoft.com).

19 mai

VC中基于 Windows 的精确定时

中国科学院光电技术研究所 游志宇

示例工程下载

  在工业生产控制系统中,有许多需要定时完成的操作,如定时显示当前时间,定时刷新屏幕上的进度条,上位 机定时向下位机发送命令和传送数据等。特别是在对控制性能要求较高的实时控制系统和数据采集系统中,就更需要精确定时操作。
  众所周知,Windows 是基于消息机制的系统,任何事件的执行都是通过发送和接收消息来完成的。 这样就带来了一些问题,如一旦计算机的CPU被某个进程占用,或系统资源紧张时,发送到消息队列 中的消息就暂时被挂起,得不到实时处理。因此,不能简单地通过Windows消息引发一个对定时要求 严格的事件。另外,由于在Windows中已经封装了计算机底层硬件的访问,所以,要想通过直接利用 访问硬件来完成精确定时,也比较困难。所以在实际应用时,应针对具体定时精度的要求,采取相适 应的定时方法。
  VC中提供了很多关于时间操作的函数,利用它们控制程序能够精确地完成定时和计时操作。本文详细介绍了 VC中基于Windows的精确定时的七种方式,如下图所示:


图一 图像描述

  方式一:VC中的WM_TIMER消息映射能进行简单的时间控制。首先调用函数SetTimer()设置定时 间隔,如SetTimer(0,200,NULL)即为设置200ms的时间间隔。然后在应用程序中增加定时响应函数 OnTimer(),并在该函数中添加响应的处理语句,用来完成到达定时时间的操作。这种定时方法非常 简单,可以实现一定的定时功能,但其定时功能如同Sleep()函数的延时功能一样,精度非常低,最小 计时精度仅为30ms,CPU占用低,且定时器消息在多任务操作系统中的优先级很低,不能得到及时响 应,往往不能满足实时控制环境下的应用。只可以用来实现诸如位图的动态显示等对定时精度要求不高的情况。如示例工程中的Timer1。
  方式二:VC中使用sleep()函数实现延时,它的单位是ms,如延时2秒,用sleep(2000)。精度非常 低,最小计时精度仅为30ms,用sleep函数的不利处在于延时期间不能处理其他的消息,如果时间太 长,就好象死机一样,CPU占用率非常高,只能用于要求不高的延时程序中。如示例工程中的Timer2。
  方式三:利用COleDateTime类和COleDateTimeSpan类结合WINDOWS的消息处理过程来实现秒级延时。如示例工程中的Timer3和Timer3_1。以下是实现2秒的延时代码:

      COleDateTime      start_time = COleDateTime::GetCurrentTime();
      COleDateTimeSpan  end_time= COleDateTime::GetCurrentTime()-start_time;
      while(end_time.GetTotalSeconds()< 2) //实现延时2秒
     { 
              MSG   msg;
              GetMessage(&msg,NULL,0,0);
              TranslateMessage(&msg); 
              DispatchMessage(&msg);
              
             //以上四行是实现在延时或定时期间能处理其他的消息,
       //虽然这样可以降低CPU的占有率,
             //但降低了延时或定时精度,实际应用中可以去掉。
             end_time = COleDateTime::GetCurrentTime()-start_time;
      }//这样在延时的时候我们也能够处理其他的消息。      
  方式四:在精度要求较高的情况下,VC中可以利用GetTickCount()函数,该函数的返回值是  DWORD型,表示以ms为单位的计算机启动后经历的时间间隔。精度比WM_TIMER消息映射高,在较 短的定时中其计时误差为15ms,在较长的定时中其计时误差较低,如果定时时间太长,就好象死机一样,CPU占用率非常高,只能用于要求不高的延时程序中。如示例工程中的Timer4和Timer4_1。下列代码可以实现50ms的精确定时:
       DWORD dwStart = GetTickCount();
       DWORD dwEnd   = dwStart;
       do
       {
          dwEnd = GetTickCount()-dwStart;
       }while(dwEnd <50);
为使GetTickCount()函数在延时或定时期间能处理其他的消息,可以把代码改为:
       DWORD dwStart = GetTickCount();
       DWORD dwEnd   = dwStart;
       do
       {
              MSG   msg;
              GetMessage(&msg,NULL,0,0);
              TranslateMessage(&msg); 
              DispatchMessage(&msg);
              dwEnd = GetTickCount()-dwStart;
       }while(dwEnd <50);
虽然这样可以降低CPU的占有率,并在延时或定时期间也能处理其他的消息,但降低了延时或定时精度。
  方式五:与GetTickCount()函数类似的多媒体定时器函数DWORD timeGetTime(void),该函数定时精 度为ms级,返回从Windows启动开始经过的毫秒数。微软公司在其多媒体Windows中提供了精确定时器的底 层API持,利用多媒体定时器可以很精确地读出系统的当前时间,并且能在非常精确的时间间隔内完成一 个事件、函数或过程的调用。不同之处在于调用DWORD timeGetTime(void) 函数之前必须将 Winmm.lib  和 Mmsystem.h 添加到工程中,否则在编译时提示DWORD timeGetTime(void)函数未定义。由于使用该 函数是通过查询的方式进行定时控制的,所以,应该建立定时循环来进行定时事件的控制。如示例工程中的Timer5和Timer5_1。
  方式六:使用多媒体定时器timeSetEvent()函数,该函数定时精度为ms级。利用该函数可以实现周期性的函数调用。如示例工程中的Timer6和Timer6_1。函数的原型如下:
       MMRESULT timeSetEvent( UINT uDelay, 
                               UINT uResolution, 
                               LPTIMECALLBACK lpTimeProc, 
                               WORD dwUser, 
                               UINT fuEvent )
  该函数设置一个定时回调事件,此事件可以是一个一次性事件或周期性事件。事件一旦被激活,便调用指定的回调函数, 成功后返回事件的标识符代码,否则返回NULL。函数的参数说明如下:
       uDelay:以毫秒指定事件的周期。
       Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
       LpTimeProc:指向一个回调函数。
       DwUser:存放用户提供的回调数据。
       FuEvent:指定定时器事件类型:
       TIME_ONESHOT:uDelay毫秒后只产生一次事件
       TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。      
  具体应用时,可以通过调用timeSetEvent()函数,将需要周期性执行的任务定义在LpTimeProc回调函数 中(如:定时采样、控制等),从而完成所需处理的事件。需要注意的是,任务处理的时间不能大于周期间隔时间。另外,在定时器使用完毕后, 应及时调用timeKillEvent()将之释放。
  方式七:对于精确度要求更高的定时操作,则应该使用QueryPerformanceFrequency()和 QueryPerformanceCounter()函数。这两个函数是VC提供的仅供Windows 95及其后续版本使用的精确时间函数,并要求计算机从硬件上支持精确定时器。如示例工程中的Timer7、Timer7_1、Timer7_2、Timer7_3。
QueryPerformanceFrequency()函数和QueryPerformanceCounter()函数的原型如下:
       BOOL  QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
       BOOL  QueryPerformanceCounter(LARGE_INTEGER *lpCount);
  数据类型ARGE_INTEGER既可以是一个8字节长的整型数,也可以是两个4字节长的整型数的联合结构, 其具体用法根据编译器是否支持64位而定。该类型的定义如下:
       typedef union _LARGE_INTEGER
       {
           struct
           {
              DWORD LowPart ;// 4字节整型数
              LONG  HighPart;// 4字节整型数
           };
           LONGLONG QuadPart ;// 8字节整型数
           
        }LARGE_INTEGER ;
  在进行定时之前,先调用QueryPerformanceFrequency()函数获得机器内部定时器的时钟频率, 然后在需要严格定时的事件发生之前和发生之后分别调用QueryPerformanceCounter()函数,利用两次获得的计数之差及时钟频率,计算出事件经 历的精确时间。下列代码实现1ms的精确定时:
       LARGE_INTEGER litmp; 
       LONGLONG QPart1,QPart2;
       double dfMinus, dfFreq, dfTim; 
       QueryPerformanceFrequency(&litmp);
       dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
       QueryPerformanceCounter(&litmp);
       QPart1 = litmp.QuadPart;// 获得初始值
       do
       {
          QueryPerformanceCounter(&litmp);
          QPart2 = litmp.QuadPart;//获得中止值
          dfMinus = (double)(QPart2-QPart1);
          dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒
       }while(dfTim<0.001);
  其定时误差不超过1微秒,精度与CPU等机器配置有关。 下面的程序用来测试函数Sleep(100)的精确持续时间:
       LARGE_INTEGER litmp; 
       LONGLONG QPart1,QPart2;
       double dfMinus, dfFreq, dfTim; 
       QueryPerformanceFrequency(&litmp);
       dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
       QueryPerformanceCounter(&litmp);
       QPart1 = litmp.QuadPart;// 获得初始值
       Sleep(100);
       QueryPerformanceCounter(&litmp);
       QPart2 = litmp.QuadPart;//获得中止值
       dfMinus = (double)(QPart2-QPart1);
       dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒     
  由于Sleep()函数自身的误差,上述程序每次执行的结果都会有微小误差。下列代码实现1微秒的精确定时:
       LARGE_INTEGER litmp; 
       LONGLONG QPart1,QPart2;
       double dfMinus, dfFreq, dfTim; 
       QueryPerformanceFrequency(&litmp);
       dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
       QueryPerformanceCounter(&litmp);
       QPart1 = litmp.QuadPart;// 获得初始值
       do
       {
          QueryPerformanceCounter(&litmp);
          QPart2 = litmp.QuadPart;//获得中止值
          dfMinus = (double)(QPart2-QPart1);
          dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒
       }while(dfTim<0.000001);
其定时误差一般不超过0.5微秒,精度与CPU等机器配置有关。(完)
18 mai

主流的数码相机品牌的型号

富士
    富士的数码单反以S+一位数+pro为名,至今已出三代,全部采用富士的SR型superCCD,特别是当进化到S3 pro以后,其宽容度有了相当大的提高,总体性能也有一定加强。
  富士的DC以Finepix为名,有以下系列
  S系列,是外观专业的实用机和旗舰机,命名方式是S+数值,一般一个时期会一口气出三款,数值最大的,是旗舰机,功能全面,性能强大,现役旗舰为S9500。上一代的旗舰有两款:S7000和S20 pro,分别使用富士第四代的HR和SR型的CCD,分别具有高像素和高宽容度的特点。数值位居其次的,是长焦实用机,手动功能齐全,变焦比大,很受广大爱好者的喜爱,具有S5600、S5500、S5000三款,数值越大的,各方面越好。数值最小的,是长焦入门机,外观比较时尚,有手动功能,有S3000、S3500两个型号。
  F系列,外观时尚的实用机,命名方式是F+三位数或者F+三位数,目前分小巧时尚F4××和实用时尚的F8××以及最新F××三个分支,每个分支中数值越大越就是越是新款的或者性能越强机器。而F××目前有F10和F11两款型号——其中F11是F10的升级和改进具有外观时尚,成像质量好,具有非常高的ISO值,更适合在弱光条件下进行拍摄。
E系列是富士的实用的机型。该系列的命名方式是E+三位数,数值越大的机型,各方面性能越好,目前最新的型号是E900。
  A 系列入门机,属于对准就按快门的类型,功能简单实用。命名方式是A+三位数,数值越大的相机越好。目前最新的型号为A350。
Z系列,是紧跟时代潮流,采用了潜望镜的内伸缩镜头,2.5"LCD,如卡片一般轻巧的时尚卡片系列。现有Z1、Z2、Z3,Z3为最新型号。
佳能

佳能的数码单反采用EOS+数值+D的命名方式。目前分为三个档次:顶级相机一律以EOS1D为名,根据推出的版本,后边缀以MARK 2/3/4,而分化出来的高像素的1DS和高速度的1D两个版本分别针对了不同的用户。高档相机同样以EOS+一位数+D命名,但数值一定是大于1的,刚刚面世的EOS5D便是此档次相机的第一款相机。中级相机以EOS+两位数+D命名,数值越大的,是越新的产品,各方面的性能也越强大,目前最新的EOS20D是准专业数码单反中的翘楚。中级相机中有两个例外:D30和D60。入门级的单反相机,命名方式为EOS+三位数+D,数值越大的,是越新的产品,各方面的性能也越强大,功能也越齐全。目前最新型号为EOS350D
  佳能的DC代号是Power shot和digital IXUS两个大系列,定位为摄影爱好者和时尚用户。
  Power shot系列相机,是佳能公司为摄影爱好者量身定做的数码相机,具有功能齐全,操作简便,实用性强的特点。分别包含了以下几个系列的数码相机。
  PRO是旗舰级便携式数码相机。现有三个型号:PRO 1、PRO 90IS、PRO50。其中PRO90 IS是老款式的防抖长焦王,PPRO1是高达800万像素的旗舰相机,而PRO50由于年代久远,已不可考。
  G系列,是佳能过去的旗舰级相机,在PRO 1推出后的现在,成为了佳能第二级别的高档相机。命名方式是G+一位数。其特点是功能齐全,画面质量优异,操作感好。数值越大,像素就越高,对应的性能越强。目前最高型号为G6。
  S系列,外观时尚的商务用机,命名方式是S+两位数。功能非常强大,升级到S60以后,还拥有了相当于35mm相机的28mm大广角。型号中的数值越大,表明机器越新,各方面越优秀。S系列还可以使用象征专业的RAW格式拍摄拉开了和更低端相机的档次。S系列最近新发展了一个分支,命名方式是S+一位数+IS,该分支是佳能新一带长焦王,具有全面的手动功能,和非常实用的防抖功能。最新的S2 IS更是把变焦比提高到了12倍,并大大提高了微距拍摄的能力。
  A系列,是佳能的入门级实用型相机,该系列存在两个分支:一个分支是A+两位数,具有丰富手动功能的实用相机。该分支除了A80比A85强以外,都遵循数值大的优于数值小的这一规律。另一分支是A+三位数,该分支一直以来是佳能垫底的数码相机,一直到A400为止,都是纯傻瓜相机。但从最近一轮新品发布开始,A系列呈现这样的格局。A4X0,A5X0,A6X0三足并立。A4X0为廉价实用机,属于对准即拍的类型。A5X0取代了过去的A75、A85的位置,使用1/2.5英寸的CCD,具有全面的手动功能,使用两节AA电池以及日益流行的SD卡,成为目前最具性价比的入门型机器。而A6X0则成为A80和A95的继承者,使用1/1.8英寸的CCD,全面的手动功能和方便的旋转液晶屏。购买老型号A系列相机的时候,需要注意的是A80和A95两个型号使用的是1/1.8英寸的CCD,从而具有成像质量更好,噪点更低等特点。而其他型号的产品,则使用的是1/2.7英寸的CCD。
  Digital IXUS系列时尚型相机,是佳能广受欢迎的相机系列,外观时尚华丽,操控简便,成像质量优秀。
  该系列有两个分支,分别是在IXUS+两位数、IXUS+三位数。前者是使用1/2.5英寸的CCD,具有超薄的身材,纤细的外表。第一个数值表明了该相机像素数:30是 300万像素,40是400万像素。后者使用的是1/1.8英寸的CCD,虽然体型大一些,但优秀的成像,时尚的外表,仍然让人爱不释手。同样,其数值第一位数字表明了其像素的多少。同样,每个分支都是数值越大,各方面的表现就越好。该系列还有一个另类——体积超小的IXUS i和IXUS i5 IXUS i ZOOM组成的IXUS i系列,是佳能最小巧最时尚的数码相机。
索尼

索尼使用的的产品代号是syber shot,拥有以下几个系列的产品。
  F系列,包含了两个系列的产品,一个是索尼目前的旗舰级相机,命名方式是F+三位数, 数值越大表明其产品越新,技术越先进,档次越高。
  另一个F系列名气要小的多,是时尚的扭头机器,命名方式是F+两位数。其数值越大,表明越优秀。目前最新最高型号为F88
  V系列,是索尼的准专业机,是F系列旗舰相机的简化版。命名方式是V+一位数,现有V1和V3两个型号,V3在像素和性能上优于V1。
  W 系列,是索尼的实用入门机,是V系列的的简化机器,在性能和用料方面有所降低。命名规律是W+一位数,数值越大,一般越好。目前有W1、W5、W7三个型号(还曾经有过W1的小改进版W12)。
  P 系列,索尼极为成功的时尚机,命名方式为P+数值。数值越大的型号相机越好。目前分4X、7X、9X、P1X、1XX、2XX几个小系列(X表示数值)。P4X使用1/2.5吋CCD,定焦镜头。P7X是1/2.5吋CCD,三倍光学变焦镜头。P9X是1/1.8吋500万像素CCD,三倍光学变焦镜头。P1X和P1XX是1/1.8吋500~700万像素CCD,三倍光学变焦镜头。目前有P100、P120、P150三个型号,已经逐渐被最新的700万像素的P200代替。
  S系列,索尼最新推出的新系列,S90、S60几乎完全一样的外型和技术参数,让人难以理解其定位,也无法言明孰优孰劣。S40则比上述两款差一些,这个系列的特点是比较省电。适合家庭外出拍摄使用。
  T 系列,是使用潜望镜镜头的超薄卡片机,命名方式是T+数值,数值越大越好。过去曾经流行过的T1、T11、T3、T33其实在性能上均不相上下,只是外观有所不同。
M系列,观酷似手机的M系列数码相机,摄像功能强劲,外观时尚,目前有M1、M2两个型号。
  另外,索尼还有一些相机还没有成为一个系列。例如L1,精巧的外表,实用的功能,比起P系列,更多了一份高贵。又如最新推出的长焦防抖相机H1,具有500万像素、优异的画面表现。N1是款最新推出的3英寸超大屏幕的数码相机。R1则是当红旗舰F828的终结者,使用APS-C尺寸的大型1100万像素CMOS,在画面质量上将超越目前市场上所有的家用旗舰DC,成为新的DC之王。
尼康

尼康的数码单反编号比较简单,为D+数值,而衍生出来的型号会在后边再添加一些字母作为区别。顶级的数码单反是D+一位数。过去有D1,后来分化为高速的H分支和高像素的X分支,到了第二代的D2H、D2X、D2Hs,这种分化更加明显。中档数码单反,目前仅有D100一种型号,近日已经停产。而D200也发售在即。尼康的低端数码单反,命名方式为D+两位数。数值大的,性能和功能就越强大。目前有D70及其小升级版D70s、以及D50三个型号。
  尼康的DC全部以coolpix为系列名称,老款的相机都是三位数为编号,有9××高端系列和885、775低端系列。其中9××系列的地位相当于佳能的G系列,也是高性能和高档次的象征。扭头机外观设计。后继的相机,全部升为4位数,而且数值日益混乱,让人不知道到底什么才是更好的。但仍有规律可循:第一位数,永远是表示像素的,而数值最大的,就一定是最高级别的DC。如果是同一时期推出的机型,同一像素的情况下,第二位数越大的,各方面就越出色。不同时期的相机对比,则容易混乱。需要点明的是,目前尼康的顶级DC,是以8开头的四位数命名。而且尼康也许也注意到了命名混乱的问题,所以在新一轮的新品发布上,我们看到了尼康重新划定各个产品系列的情况。
  首先是尼康的S系列时尚相机,S系列的鼻祖coolpix SQ是轻薄型扭头机,后来推出的coolpix S1是时尚超薄口袋机。不久前推出的coolpix S2,是在S1的基础上加入了生活防水的能力。而最近的发布的超薄口袋机S3,继承了S1的轻薄,身怀600万像素,S4则继承了SQ的扭头型设计,并具有高达10倍的光学变焦和600万像素。
  尼康最新发布的P系列,具有无线传输功能的P系列,从外观上看,应该是过去的7900、7600、5900、5600、4600、5200、4200、885、775这些机器的升级。外观比较时尚,并且具有P档和A档两种还算实用的手动功能。其定位是非超薄的时尚实用机器。
  同样是首次发布的L系列的第一款相机L1,外观还算时尚,使用1/2.5英寸的CCD,没有太多的功能,定位属于对准就拍的傻瓜机型。是过去的5100、4100、3200、2200、3100.2100的替代者。
奥林巴斯

奥林巴斯E系列,是奥林巴斯的数码单反系列,现在有E-1和E300、E500三个型号,都具有超声波除尘系统,可以保持CCD洁净如新。E-1是高端的数码单反,E-300、E500是入门级的单反相机。
  C系列,是奥林巴斯最大的一个系列,共有五个分支,第一个分支是C+X0X0,是奥林巴斯的高端系列,其中数值最大的,就是一个时期的旗舰级别的相机,而数值略小的,则是是准专业高端机,数值越高越好,表明机器越新,性能越强大。目前最高型号为C8080和C7070第二个分支是C+X000,第一个数值是同CX0X0第一个数值相同的,往往是该型号的简化版。第三个分支是C+三位数,是普通的时尚数码相机,在C系列中是比较便宜,也比较易用的。数值越大的机器,就越好。第四个分支,是C+两位数,是小型是时尚机器,外观很炫,性能也很不错。是很适合使用的口袋机。第五个分支是C7XX系列,是奥林巴斯的长焦相机系列,功能齐全,画面优秀,也是喜欢长焦摄影的人的一个很好的选择。
  u系列命名方式是u+三位数值,数值越大的相机越好,目前最高型号为u800和u600,该系列的命名特征是,第一个数值表明了相机的像素。该系列的异类是u mini和u mini Digital S,这是非常漂亮的两款数码相机,不遵循数值的规律,只是像素上有所差别。
AZ系列,是使用潜望镜结构的卡片机。该系列命名方式为AZ+一位数,数值越大的相机越好。目前有AZ1和AZ2两个型号。
  最近推出的SP系列,由于推出机型较少,只有功能全面的SP300UZ、SP350UZ和新长焦王SP500UZ三个型号。除了焦距不同,像素不同外,都有功能都非常全面,外观比较专业的特点。
  除了上述系列以外,还推出了FE系列和i : robe IR两个系列的时尚数码相机这些相机,没有太多可以多说的,只要喜欢它的外观,那啥也别说,拿下就是了。
柯达

柯达单反以DSC为名,命名比较乱,并且柯达已经宣布停产,所以这里也就不予深入讨论了。
DC方面,柯达以EasyShare为名,并经历了两代的命名方式。
第一代命名方式中,柯达分为三个系列。
  LS 系列,时尚机型,命名方式为LS+三位数,第一个数值是代表是第几代,第二个数字表示像素,第三个数值表示几倍光学变焦,一般使用和诺基亚手机兼容的电池。数值越大的,往往越好。
  DX 系列,面对摄影爱好者的实用机型,命名方式为DX+四位数,前三位和LS系列的三位数的表示意义相同,只有十倍光学变焦的时候会在第三位上用9,第四位永远是0,一般使用和诺基亚手机兼容的电池,具有强大的手动功能,受到很多摄影爱好者的喜爱,这个系列中,数值越大的,相机越好。DX系列还有一个分支,算是另类吧,是入门型的相机,缺乏手动功能,只有一些场景模式。命名方式是DX+四位数,无规律,使用用AA电池。
  CX系列,是入门型相机,功能不多,对准就拍是这个系列的特点。命名方式是CX+四位数,数值的表示意义和DX系列表示意义相同。
  第二代的命名方式的产品系列通过不同的用户群加以区分,分别代表:
Pro (P)系列,是高性能系列的代表,适合于喜欢使用相机创意控制拍摄,计算机后期处理照片的人。也适合外出旅游携带。目前已经推出的产品是高像素广角王P880和专业防抖长焦王P850两个型号
  Z 系列是高倍变焦的实用性系列,适合于喜欢使用相机创意控制拍摄,计算机后期处理照片,在零售店打印的人。目前已经推出了Z7590、Z740、Z700、Z760四个型号,但由于产品尚少,还没有形成很明显的规律。
  V 系列,时尚系列,外型比较时尚,适合于需要简单易用、并带有一定数码创意功能的人。目前最新的是V550和V530。
  C 系列,对准即拍的入门系列,适合于需要留存美好回忆,不需要较多数码功能,以传统方式打印输出的用户。目前已有C300、C330、C360三个型号,数值越高的型号,相机越好。