Win32程序设计初步之线程


线程是Win32 API中最为令人激动和有用的特性之一。线程可让你将一个程序分解成多个
线程来执行。在这篇文章中你将学习到在Win32程序中创建线程的基本概念。

  可能用到线程的地方

  在你的程序中,有不少地方都可能要用到线程:

  .如果你创建一个MDI(Multiple Document Interface,多文档界面)的应用。对于
每个窗口都分配一个独立的线程是很有用的。例如,一个让你通过多个modem同时连接到
多个主机的MDI通信程序,对于连接一个主机的每个窗口,如果都拥有自己的线程,就能
让设计得到很大的简化。

  .在一个程序中,如果包含有复杂的图形(例如一个CAD/CAM程序,在一个复杂的画
图中,可能需要画10,000线来刷新显示),它将要使用一个很长的时间来刷新显示,通
过在后台中创建一个独立的线程来处理重绘,将会是很有用的,而用户界面也拥有自己
的线程,由于通过后台的线程重绘,前台的线程可很好地响应用户的操作。

  .在一个复杂的模拟程序中,例如是一个模拟生物在某个环境活动的程序,如果每个
实体都拥有自己的线程,通常可简化程序的设计。由于每个实体都是与其它的实体无关
,因此可以独立响应各自的模拟事件;

  .如果你程序中的某部分需要很快地响应某些高优先权的事件,这个问题可通过使用
线程优先权的办法来解决。代码中高优先权的部分放在它自己的线程中,并且该线程拥
有一个比同一机器中的其它线程更高的优先权。高优先权的线程将等待必要的事件发生
。当它察觉到该事件发生,它将会被唤醒并且使用可以得到的几乎全部的CPU周期来进行
处理,直到该任务全部完成。然后它可以恢复到休眠的状态,并等待下一事件的来临。

  .如果你使用一个多处理器的机器,并且想利用所有CPU的处理性能,你可以将该应
用分解成多个线程。NT划分CPU的单元是线程,因此如果你的应用只拥有一个线程,在默
认的状态下,只会用到一个CPU来处理。如果将程序分解为多个线程,NT将可以把线程分
配到不同的CPU上运行。

  .任何需要“在后台”处理,而前台需要响应用户操作的任务,使用线程来设计就更
简单。例如,你可以将繁重的计算、页面格式化操作、文件读写等放在独立的线程中,
并放在后台处理,这样就可以减少对前台用户操作的影响。

  介绍

  如果你使用过UNIX、VMS或者大型机系统,那么多线程可能对你来说已经是一个熟悉
的概念。如果你只用过MS-DOS的话,多线程对你来说或许是一个新事物。我们首先从一
个操作系统的角度来讨论一下多进程和多线程。

  MS-DOS是一个单进程的操作系统。它每次只能运行一个程序。你装入一个程序--工
作--退出,然后运行另一个。在某些情况下,TSR或许有点多进程的影子,不过TSR通常
导致的问题表明多进程只是MS-DOS的一个幻想。

  微软的Windows 3.1以及苹果的Macintosh系统都属于协作型的多任务操作系统。两
者都可以在同一时间运行多个程序(进程)。例如,你可以在一个窗口中运行一个字处
理程序,在另一个窗口中运行一个电子制表程序,然后在第三个出口运行一个从BBS上下
载文件的程序。称为协作型的原因,是由于每个程序都要负责在适当的时间正确地放弃
控制,以令所有的进程看起来都在同时工作。不过,一个长时间的磁盘访问或者有一个
程序在进行不可中断的任务处理,都将会独占系统一段时间,从而令协作暂停。这些情
况令协作型的多任务系统在很多情况下都是不可行的。只要有一个程序挂起,整个系统
都会因此而崩溃。这是由于该程序挂起时,它将不可以放弃控制,这样所有的处理都会
停下来。

  UNIX是一个抢先型的多任务系统,它可以为全部在运行的进程都分配一定的CPU时间
,并且尽量做到最恰当。UNIX给一个进程一段的CPU时间--可能是20毫秒左右--在这段时
间到期后,操作系统将会收回处理器的控制,并且将下一段的CPU时间分配给另一个进程
。因此一个UNIX机器即使同时运行几百个进程,也可以令用户感到很流畅。如果一个进
程挂起的话,对其它进程也不会有影响,因为CPU时间的控制仍然掌握在操作系统上。

  Windows NT和Windows95是一个抢先型多任务、多线程操作系统。因为它使用抢先型
的多任务,所以它拥有与UNIX同样平滑的处理和进程独立。多线程就更进一步。一个独
立的程序默认是使用一个线程,不过它可以将自己分解为几个独立的线程来执行,例如
,其中的一个线程可以发送一个文件到打印机,而另一个可以响应用户的输入。这个简
单的程序设计修改可以明显减少用户等待的时间,让用户无需担心长时间的计算、重绘
屏幕、文件读写等带来的不便。

  多线程还可以让你从许多高端的多处理器NT机器中得到好处。例如,你购买了一个
高级的RISC机器,可以使用多达10个CPU芯片,但在开始的时候你只购买了一个CPU。你
写了一个简单的Mandelbrot set程序,你发现需要15秒的时间来重新绘制Mandelbrot
set的画面。

  现在你在机器中多加9个CPU。当你再次运行这个Mandelbrot程序的时候,你会发现
它仍然需要15秒的时间来执行。NT可以做到在不同的CPU上运行不同的线程,不过它在一
个单线程的程序中,只能为它分配一个CPU,NT不可以将一个单线程的处理分配到多个CP
U上。由于NT自身的多线程,Mandelbrot程序还是会快一点的,因为它不会与NT系统的线
程竞争CPU时间。因此,在一个10CPU的机器上,除非程序是多线程的,否则任何一个程
序都不会占用超过十分之一的CPU处理能力。

  如果你将Mandelbrot程序多线程化,NT就可以在不同的CPU上运行独立的线程,并且
让程序得到全部CPU处理性能的好处。例如,如果该Mandelbrot程序将自己分解为10个线
程,然后每个线程都在一个CPU上运行,该程序的运行速度将会快10倍。在一个10CPU的
机器上,使用超过10个的线程是没有意义的,因为每个线程都会带来一点系统开销,因
此使用超过10个线程是有点浪费的。不过,你喜欢的话,或者更多的线程可以令设计思
路更简单,你可以将程序分解为100个线程,或者画每条扫描线都使用一个线程。事实上
,确实有不少的设计通过将一个应用分解为多个的线程,从而令整个的程序更易懂,而
且线程是很容易创建的。

  在NT中,进程可没有线程那样吸引人。一个程序可创建一个独立的进程,不过新的
进程是完全和以前的程序无关的。在UNIX中,一个新的进程可获得原有进程变量空间的
一个完全拷贝,而NT与UNIX不同,一个新的进程至多可以继承特别指示处理的拷贝。你
通常会使用进程的情况是,你想在某个程序中启动另一个独立的程序。例如,如果你要
写自己的程序管理器或者文件管理器,你将使用进程来在这些程序中启动其它的应用。

  NT启动进程时,默认只使用一个线程执行。例如,当你在命令行打“notepad”或者
在程序管理器中双击notepad(记事本)的图标,NT创建一个进程,并且该进程拥有一个
线程来“运行”notepad的代码。该进程事实上就是该应用拥有的全局变量、环境字符串
和堆栈的一个容器,而线程就是用来真正执行代码的。

  在一个进程中的所有线程都分享他们父进程的变量空间。每个线程都拥有它们自己
的堆栈。当你在一个进程中创建一个新的线程时,它可以访问到父进程所有的全局变量
和堆栈。由于现在多个线程都可以独立修改同一个全局变量,因此程序中在处理全局变
量时要比较小心,否则很容易带来问题。为了解决这种情况可能带来的问题,Win32
API内置有同步的技术,可以确保你独占访问全局的值。
例子1

  对于许多人来说,要从一个单一的进程中创建多个线程的话,在观念上就要做一些
改变。我们首先来看几个非常简单的例子,了解一下Win32 API中线程的基本运作是怎样
的。

  以下的代码是一个非常简单的单线程程序。在这个程序中,用户每按下回车键一次
,就会输出全局变量count的值。由于在程序中并没有改变count的值,因此该程序一直
输出0。这里并没有使用什么技巧:

 

#include
#include

UINT count;

void main(void)
{
 CHAR retStr[100];

 count=0;
 while(1)
 {
  cout << "Press to display the count... ";
  cin.getline(retStr, 100);
  cout << "The count is: " << count << endl
  << endl;
 }
}

 

  现在要为这些代码增加一个线程。NT中的线程其实就是后台中执行的一个函数。代
码中的CountThread函数增加全局变量count的值,然后休眠100毫秒。在你运行程序的时
候,你将会发现每次按下回车键,count的值就会增加。后台中的线程也会增加count的
值,同时以前的线程也会响应用户的输入。这个程序会同时做两件事情。

 

#include
#include

volatile UINT count;

void CountThread()
{
 while(1)
 {
  count++;
  Sleep(100);
 }
}

void main(void)
{
 HANDLE countHandle;
 DWORD threadID;
 CHAR retStr[100];

 count=0;

 // create a thread which
 // executes the "CountThread" function
 countHandle=CreateThread(0, 0,(LPTHREAD_START_ROUTINE) CountThread,0, 0,
&threadID);
 if (countHandle==0)
  cout << "Cannot create thread: "
  << GetLastError() << endl;

  while(1)
  {
   cout<< "Press to display the count... ";
   cin.getline(retStr, 100);
   cout << "The count is: " << count << endl
   << endl;
  }
}

 

主线程、后台线程和全局变量之间的关系可用下图表示:

 

****************图一********************

以上通过使用CreateThread函数来创建线程(该函数的具体描述可参考VC++ 2.0版本的W
in32在线帮助或者NT的SDK文档)。CreateThread接受线程函数的名字,该函数会在新的
线程中执行。这里的线程函数就是CountThread。线程函数可以选择接受一个4字节的参
数。在上面的代码中,线程函数并没有使用任何的参数,不过如果有的话这些参数将会
被传送至CreateThread的参数列表中,并且CreateThread会把它回送给线程函数。Creat
eThread将会返回一个线程ID和句柄给线程。线程ID是用来在系统中唯一确定线程的,并
且可接受一些函数,例如AttachThreadInput等。

  你可以通过堆栈参数来控制线程堆栈的初始化大小。将堆栈参数设置为0将会令线程
堆栈初始化为与它的父线程一样大。堆栈可以根据需要变大,不过增大堆栈会带来系统
开销,因此你最好在开始就分配一个适合的大小,这可以通过它使用的局部变量的大小
决定。你还可以唤醒一个挂起的线程。在挂起时,该线程将不会占用任何的CPU时间,直
到其它线程通过ResumeThread唤醒它。

  对于延迟一个线程来说,上面代码中的休眠函数是一个有效的方法。在以前,你可
能需要使用循环或者类似的技术来做到一个延迟,不过你也应该知道循环是不可靠的,
并且会造成资源的浪费,因为循环也会使用CPU周期,这是不必要的。休眠函数可以延迟
一个线程,而无需要花费任何的CPU周期。在休眠中,一个线程是完全挂起的。

  你要留意到全局变量count是使用volatile修饰符的。如果你注释掉线程中的Sleep
函数,并且除去volatile修饰符,在你按在回车键时,count的值会保持为0。这是由于
编译器的优化而造成的一个奇怪现象。例如,对于编译器来说,将可能使用一个寄存器
来保存主线程中count变量的值,而忽略第二个线程对该值的修改。volatile修饰符的作
用是告诉编译器无需对该变量作任何的优化,即无需要将它放到一个寄存器中,并且该
值可在赋值期间被外部改变。你将会发现对于多线程引用的全局变量来说,volatile是
一个非常重要的修饰符。

第二个简单例子

以下的代码是另一个这样例子,在后台中运行一个独立于主线程的线程。这个后台线程
将会发出嘟嘟声,而主线程将会等待它完成。这段代码展示了如果传送一个整型的参数
到一个线程中,以及如何等待一个线程完成处理。

// thread1.cpp

#include
#include
#include

// The function to run in a thread
void HonkThread(DWORD iter)
{
 DWORD i;

 for (i=0; i < iter; i++)
 {
  Beep(200, 50);
  Sleep(1000);
 }
}

void main(void)
{
 HANDLE honkHandle;
 DWORD threadID;
 DWORD iterations;
 CHAR iterStr[100];

 cout << "Enter the number of beeps to produce: ";
 cin.getline(iterStr, 100);

 // convert string into integer
 iterations=atoi(iterStr);

 // create a thread which
 // executes the "HonkThread" function
 honkHandle=CreateThread(0, 0, (LPTHREAD_START_ROUTINE) HonkThread, (VOID
*) iterations, 0, &threadID);

 // wait until the thread has finished
 int count=0;
 while ( WaitForSingleObject(honkHandle, 0)== WAIT_TIMEOUT)
 {
  cout<< "waiting for the thread to finish "
  << count++
  << endl;
 }
}

  在你运行以上代码时,你将要输入一个整型的值,例如5。主程序将会启动线程,并
且通过参数将值5传送给它。该线程将会在后台运行,发出5次的嘟嘟声,然后退出。同
时,主程序会等待后台线程退出,这个操作通过使用一个循环中的WaitForSingleObject
函数完成。每次循环它都会增加一个整型变量的值,并且将它输出到标准输出中。

  WaitForSingleObject函数等待线程处理完毕。如果传入一个0值,则表示超时,这
将令WaitForSingleObject马上返回,并且指出线程是否已经完成。如果线程没有完成,
WaitForSingleObject返回WAIT_TIMEOUT。在这里,我们只是使用WaitForSingleObject
来检测后台线程是否已经完成。

  传送一个结构体给一个线程函数也是可能的,可以通过传送一个指向结构体的指针
参数来完成,以下就是一个这样的例子。结构体应该是稳定的,也就是说,它应该是一
个全局的变量、一个静态的局部变量或者由堆栈分配得来。结构体不应该使用一个函数
中的局部变量,因为它的值可能会在线程运行期间消失。

#include
#include
#include

typedef struct
{
 DWORD frequency;
 DWORD duration;
 DWORD iterations;
} honkParams;

void HonkThread(honkParams *params)
{
 DWORD i;

 for (i=0; i < params->iterations; i++)
 {
  Beep(params->frequency, params->duration);
  Sleep(1000);
 }
}

void main(void)
{
 HANDLE honkHandle;
 DWORD threadID;
 honkParams params;
 CHAR freqStr[100];
 CHAR durStr[100];
 CHAR iterStr[100];

 cout << "Enter the beep frequency to produce: ";
 cin.getline(freqStr, 100);
 params.frequency=atoi(freqStr);

 cout << "Enter the beep duration to produce: ";
 cin.getline(durStr, 100);
 params.duration=atoi(durStr);

 cout << "Enter the number of beeps to produce: ";
 cin.getline(iterStr, 100);
 params.iterations=atoi(iterStr);

 // create a thread and pass it the address of
 
file://the "params" structure
 honkHandle=CreateThread(0, 0, (LPTHREAD_START_ROUTINE) HonkThread, ms, 0,
&threadID);

 WaitForSingleObject(honkHandle, INFINITE);
}

  在上面的代码中,通过用户输入的三个值被放在一个结构体中,然后传送给线程。
主函数通过调用WaitForSingleObject,以防止在线程完成前终止。没有这个调用的话,
主函数将会很快地返回,并且终止主进程和该线程。
第三个例子

  以下代码更进一步,它展示了通过使用多线程函数或者多次调用一个单线程函数,
可以创建多个的后台线程。

#include
#include
#include

typedef struct
{
 DWORD frequency;
 DWORD duration;
 DWORD iterations;
} honkParams;

void HonkThread(honkParams *params)
{
 DWORD i;

 for (i=0; i < params->iterations; i++)
 {
  Beep(params->frequency, params->duration);
  Sleep(1000);
 }

 GlobalFree(params);
}

void main(void)
{
 HANDLE honkHandles[3];
 DWORD threadID;
 honkParams *params;
 DWORD count;
 CHAR freqStr[100];
 CHAR durStr[100];
 CHAR iterStr[100];

 for (count=0; count < 3; count++)
 {
  // allocate memory for a "params" structure
  params=(honkParams *) GlobalAlloc(GPTR,
  sizeof(honkParams));

  cout << "Enter the beep frequency: ";
  cin.getline(freqStr, 100);
  params->frequency=atoi(freqStr);

  cout << "Enter the beep duration: ";
  cin.getline(durStr, 100);
  params->duration=atoi(durStr);

  cout << "Enter the number of beeps: ";
  cin.getline(iterStr, 100);
  params->iterations=atoi(iterStr);

  // create a thread and pass it the pointer
  // to its "params" struct
  honkHandles[count]=CreateThread(0, 0,
  (LPTHREAD_START_ROUTINE) HonkThread,
  params, 0, &threadID);
 }

 // wait for all threads to finish execution
 WaitForMultipleObjects(3, honkHandles, TRUE, INFINITE);
}

  在运行以上代码时,该程序将会要求你输入一个频率、持续时间和发出响声的次数
。你可以做三次这样的处理,如果你为每个线程设置足够高的发声次数的话,你将可以
听到三个线程同时发出嘟嘟的响声。

  在上面的代码中,再次使用了一个等待的函数,以让主函数和进程等待三个线程全
部结束。WaitForMultipleObjects函数的作用和WaitForSingleObject一样,不过它是等
待全部的特定事件发生。WaitForMultipleObjects可接收对象句柄的数组,在这里是三
个线程的句柄。

  结论

  你可以通过上面的例子了解到线程的使用,虽然还很简单,不过要调用一个常用的
函数也并不是难很多。线程函数可如你预料那样运作,它在后台执行,返回很快,同时
可运行应用的主线程。


 
 
 
 
 
23:55  |  固定链接 | 评论 (0) | 引用通告 (0) | 记录它
 
 
固定链接  关闭
 
http://spaces.msn.com/members/GreenSui/Blog/cns!1p3EDoyO7R34_La61JPx-Kcg!118.
entry
 

 
 
 

 
虚拟设备驱动程序的设计与实现 选择自 ark1111 的 Blog 
 
作者: 陈国友 时间: 2004-11-05 出处: 中国操作系统支持

由于Windows对系统底层操作采取了屏蔽的策略,因而对用户而言,系统变得更为安全,
但这却给众多的硬件或者系统软件开发人员带来了不小的困难,因为只要应用中涉及到
底层的操作,开发人员就不得不深入到Windows的内核去编写属于系统级的虚拟设备驱动
程序。

  Win98与Win 95设备驱动程序的机理不尽相同,Win98不仅支持与Windows NT 5.0兼
容的WDM(Win32 Driver Mode)模式驱动程序,而且还支持与Win95兼容的虚拟设备驱动程
序VxD(Virtual Device Driver)。下面介绍了基于Windows 9x平台的虚拟环境、虚拟设
备驱动程序VxD的基本原理和设计方法,并结合开发工具VToolsD给出了一个为可视电话
音频卡配套的虚拟设备驱动程序VxD的设计实例。

  1.Windows 9x的虚拟环境

  Windows 9x作为一个完整的32位多任务操作系统,它不像Window 3.x那样依赖于MS-
DOS,但为了保证软件的兼容性,Windows 9x除了支持Win16应用程序和Win32应用程序之
外,还得支持MS-DOS应用程序的运行。Windows 9x是通过虚拟机VM(Virtual Machine)环
境来确保其兼容和多任务特性的。

  所谓Windows虚拟机(通常简称为Windows VM)就是指执行应用程序的虚拟环境,它
包括MS-DOS VM和System VM两种虚拟机环境。在每一个MS-DOS VM中都只运行一个MS-DOS
进程,而System VM能为所有的Windows应用程序和动态链接库DLL(Dynamic Link
Libraries)提供运行环境。每个虚拟机都有独立的地址空间、寄存器状态、堆栈、局部
描述符表、中断表状态和执行优先权。虽然Win16、Win32应用程序都运行在System VM环
境下,但Win16应用程序共享同一地址空间,而Win32应用程序却有自己独立的地址空间

  在编写应用程序时,编程人员经常忽略虚拟环境和实环境之间的差异,一般认为虚
拟环境也就是实环境。但是,在编写虚拟设备驱动程序VxD时却不能这样做,因为VxD的
工作是向应用程序代码提供一个与硬件接口的环境,为每一个客户虚拟机管理虚设备的
状态,透明地仲裁多个应用程序,同时对底层硬件进行访问。这就是所谓虚拟化的概念

  VxD在虚拟机管理器VMM(Virtual Machine Manager)的监控下运行,而VMM实际上是
一个特殊的VxD。VMM执行与系统资源有关的工作,提供虚拟机环境(能产生、调度、卸
载VM)、负责调度多线程占先时间片及管理虚拟内存等工作。VxD与VMM运行在其他任何
虚拟机之外,VxD事实上就是实现虚拟机的软件的一部分。

  与大操作系统一样,Windows也是采用层次式体系结构。VMM和VxDs构成了Win 95的r
ing0级的系统核心(应用程序运行在ring3级,ring1、ring2级未被使用),具有系统的
最高优先权。Windows还提供一些以"drv"为后缀名的驱动程序,主要是指串行口的通信
程序和并行口的打印机程序。这些程序与VxD不同,它们是运行在ring3级上的。图1可以
使你更好地理解Windows的虚拟环境。

  2.深入理解VMM和VxD

  如前所述,VxD是Virtual Device Driver的缩写,但有人将它理解为虚拟任何驱动
程序。实际上,VxD并非仅指那些虚拟化的某一具体硬件的设备驱动程序。比如某些VxD
能够虚拟化设备,而某些VxD作为设备驱动程序却并不虚拟化设备,还有些VxD与设备并
没有什么关系,它仅向其他的VxD或是应用程序提供服务。

  VxD可以随VMM一起静态加载,也可以根据需要动态加载或卸载。正是由于VxD与VMM
之间的紧密协作,才使得VxD具有了应用程序所不具备的能力,诸如可以不受限制地访问
硬件设备、任意查看操作系统数据结构(如描述符表、页表等)、访问任何内存区域、
捕获软件中断、捕获I/O端口操作和内存访问等,甚至还可以截取硬件中断。

  尽管VxD使用32位平面存储模式(flat memory model),但它的代码和数据仍使用分
段管理,段有六种类型,即实模式初始化、保护模式初始化、可分页、不可分页、静态
和只调试(debug only),每种类型又有代码段和数据段之分,所以VxD共有12个段。实模
式代码段和数据段为16位(分段模式),其他段则是32位(平面模式)。“实模式初始
化”段包含了在Windows初始化过程的最初阶段VMM变为保护模式之前要执行的代码。静
态加载的VxD此时可以查看Windows启动前的实模式环境,决定是否继续加载,并通知VMM
。加载完毕后,VMM进入保护模式并执行保护模式初始化代码,同样将执行结果再通知VM
M。初始化完成后,“实模式初始化”段和“保护模式初始化”段即被遗弃。VxD的大部
分代码都在其他的某一段中,“可分页”段允许虚拟存储管理器(Virtual Memory
Manager)进行分页管理,大多数的VxD代码都应当在“可分页”段。“不可分页”段的内
容主要包括:VxD的主入口点、硬件中断处理函数、所访问的数据以及能被另一个VxD中
断处理函数调用的异步服务。“静态”段仅用于可以动态加载的VxD,当VxD卸载后,静
态代码段和数据段都保留在内存中。“只调试”段只是VMM在Soft-ICE for Win 95等调
试环境下才将其载入。

  VMM是通过VxD的设备描述符块DDB(Device Descriptor Block)来识别的。DDB向VMM
提供了VxD的主入口点,还向应用程序和其他的VxD提供了入口点。VMM利用这个主入口点
将VM及Windows自身的状态通知给VxD,然后VxD通过相应的工作来响应这些事件。由于Vx
D不仅仅服务于一个物理设备(比如多个串口)或仅与一个VM发生联系,所以VxD需要产
生自己支持的数据结构(Supporting Data Structures)来保存每一个设备、每一个VM的
配置和状态信息。VxD用一个或多个设备上下文结构来保存设备信息,如I/O端口基地址
、中断向量等,VxD将自己的每个VM的状态信息保存在VMM的VM控制块中。

  VMM提供的服务包括:事件服务、内存管理服务、兼容执行和保护模式执行的服务、
登录表服务、调度程序服务、同步服务、调试服务、I/O捕获服务、处理错误和中断服务
、VM中断和回调服务、配置管理程序服务以及其他杂项服务。

  以上内容仅涉及到VxD设计的一小部分,作为VxD的开发人员必须掌握更多的知识。
首先是操作系统的知识,如地址空间、执行上下文、资源加锁、进程间通信和异步事件
处理等方面的知识;其次,对Intel处理器应有较深入的理解,包括寄存器、机器指令集
、保护机制、分页机制,以及虚拟8086模式;最后,还必须熟悉VMM提供的各类服务和接
口,熟悉Windows其他的系统VxD。

  3.开发工具VToolsD简介

  VToolsD是专门用于开发VxD程序的一种工具软件,它包括VxD框架代码生成器QuickV
xD、C运行库、VMM/VxD服务库、VxD的C++类库、VxDLoad和VxDView等实用工具以及大量
的C、C++例程。由VC++、BC++的32位编译器编译生成的VxD程序可以脱离VToolsD环境运
行。

  利用QuickVxD可以方便、快捷地生成VxD的框架,即生成后缀名为h、cpp和mak的三
个文件。源文件包含了运行VxD的基本组件,其中包含控制消息处理、API入口点、以及V
xD服务等函数框架,并且还定义了标志,设置了编译参数,声明了类,然后在C++环境下
,向生成的各个处理函数体内添加自己的代码,最后使用编译器NMAKE生成标准的VxD程
序。

  由于VxD运行在ring0级,所以调试程序相当困难。我使用的调试工具是Soft-ICE
for Win 95。

  目前VToolsD的最新版本为3.0,它支持设备访问体系结构DAA(Device Access
Architecture),所编写的程序代码将可以在所有Windows平台(包括Win 95、Win 98以
及Windows NT)上共享。当然也可以使用Microsoft公司的DDK(Device Developer Kit)
来开发VxD,但DDK不能像VToolsD那样通过屏蔽系统及VxD的底层技术细节提供丰富的C运
行库和C++类库,而是让开发人员充分享用面向对象编程方法的方便与快捷,因此仅就该
点而言,使用DDK是不方便的。

  4.VxD程序设计实例

  我在开发可视电话音频卡的设计过程中,用VToolsD 2.03、VC++ 5.0为自制的PC/XT
总线扩展卡开发了虚拟设备驱动程序Audcard.vxd。该卡每20ms申请一次中断,中断由应
用程序动态载入系统的Audcard.vxd响应并加以处理。中断服务程序ISR(Interrupt
Service Routine)结束后,调用函数Shell_PostMessage( )向应用程序窗口发送自定义
消息。应用程序接受消息后,再通过函数DeviceIoControl( )与VxD的接口函数OnW32Dev
iceIoControl( )互传缓冲区数据。程序结束即可动态卸载VxD。下图表示在Win 95下VxD
对硬件中断的处理过程。

Win95下硬件中断的处理过程

  当中断发生时,处理器转换为ring0级保护模式。Windows系统并不像DOS那样通过中
断描述符表IDT(Interrupt Descriptor Table)直接指向中断处理过程,而是由IDT入口
指向VMM中的程序。该程序将判断是否为中断调用,如果是,则把中断控制权交给虚拟可
编程中断控制器VPICD(Virtual Programmable Interrupt Controller Device),VPICD
实际上是一个重要的VxD。VPICD再将其交给另一个注册了该中断的VxD(如Audcard.vxd
)来处理。VxD程序是通过调用VPICD服务VPICD_Virtualize_IRQ来注册中断的。

  虚拟设备驱动程序Audcard.vxd的部分源代码Audcard.h和Audcard.cpp在网上,网址
为:
www.pccomputing.com.cn。此应用程序使用了下列函数:CreateFile()动态加载VxD
、CloseHandle()并动态卸载VxD、PreTranslateMessage()截获消息、DeviceIoControl(
)与VxD互传缓冲区数据。虚拟设备驱动程序Audcard.vxd经调试后工作正常,未发生过任
何丢失数据或死机的现象。

  下面是虚拟设备驱动程序Audcard.vxd的部分源代码Audcard.h和Audcard.cpp,限于
篇幅,由QuickVxD自动生成的Audcard.mak未列出。

  ①Audcard.h

  //AUDCARD.h - include file for VxD AUDCARD
  #include
  #define DEVICE_CLASS AudcardDevice
  #define AUDCARD_DeviceID UNDEFINED_DEVICE_ID
  #define AUDCARD_Init_Order UNDEFINED_INIT_ORDER#define AUDCARD_Major
  #define AUDCARD_Minor 0
  #define MY_IRQ 5 //定义5号中断
  class MyHwInt:public VHardwareInt
  {
  public:
  MyHwInt():VHardwareInt(MY_IRQ,0,0,0){}
  virtual VOID OnHardwareInt(VMHANDLE);
  };
  class AudcardDevice : public VDevice
  {
  public:
  virtual BOOL OnSysDynamicDeviceInit();
  virtual BOOL OnSysDynamicDeviceExit();
  virtual DWORD OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams);
  MyHwInt* pMyIRQ;
  };


class AudcardVM : public VVirtualMachine
  {
  public:
  AudcardVM(VMHANDLE hVM);
  };
  class AudcardThread : public VThread
  {
  public:
  AudcardThread(THREADHANDLE hThread);
  };
  ②Audcard.cpp

  //AUDCARD.cpp - main module for VxD AUDCARD
  #define DEVICE_MAIN
  #include "audcard.h"
  Declare_Virtual_Device(AUDCARD)
  #define WM_USER_POSTVXD 0x1000  //自定义消息
  #undef DEVICE_MAIN
  AudcardVM::AudcardVM(VMHANDLE hVM) : VVirtualMachine(hVM) {}
  AudcardThread::AudcardThread(THREADHANDLE hThread) : VThread(hThread)
{}
  BOOL AudcardDevice::OnSysDynamicDeviceInit() //动态加载时初始化
  {
  ......//硬件初始化

  pMyIRQ=new MyHwInt();
  if(
pMyIRQ&&pMyIRQ-$#@62;hook()) //挂接中断
  {
  
pMyIRQ-$#@62;physicalUnmask(); //允许中断
  return TRUE;
  }
  else return FALSE;
  }
  BOOL AudcardDevice::OnSysDynamicDeviceExit()

  //动态卸载过程

  {
  delete pMyIRQ;
  return TRUE;
  }
  DWORD AudcardDevice::OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams)

  //与Win32应用程序的接口函数

  {
  ......
  }

  VOID MyHwInt::OnHardwareInt(VMHANDLE hVM)
  {

  ...... // 中断处理

  SHELL_PostMessage(AppWnd,WM_USER_POSTVXD ,0,0,0,NULL);

  //向应用程序窗口发送消息

  sendPhysicalEOI(); //通知VPICD中断结束
  }
 
 
 
 
 
23:51  |  固定链接 | 评论 (0) | 引用通告 (0) | 记录它
 
 
固定链接  关闭
 
http://spaces.msn.com/members/GreenSui/Blog/cns!1p3EDoyO7R34_La61JPx-Kcg!117.
entry
 

 
 
 

 
工控系统串口通讯设计 选择自 jdzwq 的 Blog
 

工控系统通常由工控仪器和计算机终端组成,工控仪器和计算机终端之间通过符合RS-23
2协议的串口通讯,计算终端可以通过双方既定的数据协议,向工控仪器查询状态信号和
发送控制信号。
一、硬件协议:定义了RS-232串口的电气规范。
1)DTE/DCE:
一般把工控仪器称为DCE,计算机终端称为DTE,设备之间通过RS-232电缆连接,DCE端采
用母连接器(有槽),DTE端采用公连接器(有针)。但如果工控仪器和计算机终端都采
用公连接器,则两者都是DTE设备,它们之间的连接应采用零调制解调器方式。
2)RS-232信号:
标准的RS-232管脚通常有D-25PIN和D-9PIN两种类型,常用的信号如下:
信号分类 D-9PIN D-25PIN 信号名称 信号缩写 信号方向
数据信号   3            2            数据传输  TD            DTE->DCE
                 2            3            接收数据   RD           DTE<-DCE
控制信号   7            4            请求发送   RTS         DTE<-DCE
                 8            5            清除发送   CTS         DTE<-DCE
                 6            6            数据发送就绪 DSR   DTE<-DCE
                 1            8            载波检测   CD           DTE<-DCE
                 4            20          数据终端就绪 DTR   DTE->DCE
                 9            22          振铃指示   RI            
DTE<-DCE
接地信号   5            7            接地信号 GND 
 3)零调制解调连接(ZERO MODEM):
 ZERO MODEM处理DTE和DTE设备的对称连接,其连接原理为,一方的传送数据信号为另一
方的接收数据信号,一方的控制请求信号为另一方的控制应答信号,接地信号互连。连
接示意如下:
信号分类   DTE  DTE
数据信号   TD-- RD
                 RD-- TD
控制信号   RTS-- CTS
                 CTS-- RTS
                 (DSR-DCD-RI)-- DTR
                 DTR-- (DSR-DCD-RI )
接地信号   GND-- GND
 二、软件协议:定义了DTE的串口配置,DTE和DCE之间连接协议和数据传输协议。
 1)串口参数配置:
 波特率(BaudRate):在CBR_110到CBR_256000之间指定,参照仪器指定
 数据位(ByteSize):每个字节的位数,一般用7或8,默认为8
 停止位(StopBits):停止位的位数,一般有:ONESTOPBIT、TOWSTOPBITS、ONE5STOPBIT
S,默认为ONESTOPBIT
 奇偶校验(Parity): 定义了奇偶校验的模式,一般有:NO_PARITY、EVEN_PARITY、ODD
_PARITY,默认NO_PARITY
 流量控制(FlowCtrl):定义了流量控制方式,一般有:无控制、硬件方式、XON/XOFF方
式,详见握手协议。
 2)握手协议:常见有硬件方式RTS/CTS和DTR/DSR方式,软件方式有XON/XOFF和自定义
的方式。

 RTS/CTS:对于DTE来说,设置OutCtsFlow则CTS低水平位时停止输出,直至高水平位时
恢复输出。设置RtsControl为HANDSHAKE则当输入缓冲区数据小于1/4时,DTE将RTS置为
高水平位,通知DCE可以传输数据,当输入缓冲区数据大于3/4时,DTE将RTS置为低水平
位,通知DCE停止传输数据。DTE(计算机)的缓冲区较大,通常都将RtsControl设置位E
NABLE,即保持高水平位。
 DTR/DSR:对于DTE来说,设置OutDsrFlow则DSR低水平位时停止输出,直至高水平位时
恢复输出。设置DtrControl为HANDSHAKE则当DTR设置为高水平位时容许数据输入,当DTR
为低水平位时阻止数据输入。DTE(计算机)的缓冲区较大,通常都将DtrControl设置位
ENABLE,即保持高水平位。
 XON/XOFF:对于DTE来说,设置OutX时,输出流在DTE收到XoffChar时停止,在收到XonC
har时恢复。设置InX时,输入流在缓冲区空闲不足XoffLim时DTE发送XoffChar,通知DCE
中止传输数据。当输入流达到缓冲区空闲超过XonLim时,DTE发送XonChar,通知DCE恢复
传输数据。
三、编程模式:
 在WIN32环境中,串口作为文件访问,但与其他文件不同,串口文件的操作是采用阻塞
方式的,读写动作通常会在后台阻塞,用户可以通过响应串口事件,获知端口状态和控
制读写动作。因此在WIN32环境中处理串口,应采用重叠I/0机制访问串口文件和在线程
中完成读写操作,这样意味着当读写线程阻塞时,不会使主线程锁定而失去响应。
 1、串口文件操作方式:根据如上要求,串口一般采用独占和重叠方式打开,如:Creat
eFile(_T("
.//COM1"),/*端口名称*/
GENERIC_READ|GENERIC_WRITE,/*文件可读写*/
0,/*独占方式*/
NULL,/*无权限属性*/
OPEN_EXISTING,/*端口必须存在*/
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,/*重叠的操作方式*/
NULL/*不支持临时文件*/)。
有效的串口文件打开后,可以进行重叠的读写操作,其中要使用一个重叠操作结构OVERL
APPED:
struct { DWORD  Internal; /*内部使用*/
    DWORD  InternalHigh; /*内部使用*/
    DWORD  Offset; /*操作开始的文件位置(低位),串口文件不支持*/
    DWORD  OffsetHigh;/* 操作开始的文件位置(高位),串口文件不支持*/
    HANDLE hEvent; /*异步事件句柄,重叠操作完成或中断时被激发*/
} OVERLAPPED;
写串口的方式如下:
WriteFile(hCom,/*串口文件句柄*/
(void*)data,/*数据指针*/
dwDataBytes,/*请求写的数据字节数*/
&dwOperaBytes,/*函数返回的已写的字节数,在重叠I/O中通常返回0*/
&ov/*重叠操作结构指针*/);
读串口的方式如下:
ReadFile(hCom,/*串口文件句柄*/
(void*)buf,/*缓冲区指针*/
dwDataBytes,/*请求读的数据字节数*/
&dwOperaBytes,/*函数返回的已读的字节数,在重叠I/O中通常返回0*/
&ov/*重叠操作结构指针*/);
重叠方式调用读写函数后即返回,程序稍后调用等待事件函数进入阻塞状态,直至异步
事件被激发,调用方式如下:
WaitForSingleObject(hEvent,/*OVERLAPPED中异步事件句柄*/
dwTimeouts/*读写超时毫秒数*/)
读写超时设置可以由串口配置超时参数COMMTIMEOUTS获得,读超时数 =ReadTotalTimeo
utMultiplier * 读字节数 + ReadTotalTimeoutConstant; 写超时数 =WriteTotalTime
outMultiplier * 写字节数 + WriteTotalTimeoutConstant; 
异步事件返回后,可以调用重叠I/O查询函数查看后台读写状况:
GetOverlappedResult(hCom, /*端口文件句柄*/
&ov, /*重叠结构指针*/
&dwOperaBytes, /*重叠操作完成的字节数*/
FALSE/*是否需要等待重叠操作完成*/);
以上时串口文件的操作方式,需要注意的是,这些操作除了打开文件外,其他都应当在
某个读写线程中调用,让线程在后台阻塞,主线程保持响应。
 2、端口事件侦听:WIN32提供串口事件查询函数用以查看端口触发的事件,端口可侦听
事件一般有:
EV_BREAK :端口中断信号
EV_CTS :CTS信号改变
EV_DSR :DSR信号改变
EV_RXCHAR :收到一个或多个字符
EV_RXFLAG :收到特殊字符
EV_ERR :端口错误信号
EV_TXEMPTY:输出缓冲区数据发送完成
可以通过SetCommMask(hCom/*端口文件句柄*/,dwMask/*事件组合*/)来设置需要侦听的
事件,然后应采用重叠模式调用查询事件函数:
WaitCommEvent(hCom, /*端口文件句柄*/
&dwMask,/*端口事件组合*/
&ov/*重叠I/O结构*/);
该函数在重叠I/0方式调用后即返回,侦听例程稍后调用WaitForSingleObject进入阻塞
状态,OVERLAPPED中的异步事件被激发后返回,程序可以根据dwMask中返回的事件标志
做进一步的处理,如:
switch(dwMask)
{
 case EV_BREAK:
  /*向主线程发送端口中断消息*/break;
 case EV_CTS:
 case EV_DSR:
 /*向主线程发送端口状态消息*/break;
 case EV_ERR:
 /*向主线程发送端口错误消息*/break;
 case EV_RXFLAG:
 /*向主线程发送接收特殊字符消息,通知主线程读*/break;
 case EV_RXCHAR:
  /*向主线程发送接收字符消息,通知主线程读*/break;
 case EV_TXEMPTY:
/*向主线程发送数据已发送消息*/break;
}
 3、侦听、读写线程的处理模式:在串口编程模式中主线程启动时创建侦听线程,在主
线程结束时终止侦听线程。主线程根据读写要求创建读写线程,并在线程结束后返回。
在线程处理中重点要保护主线程中的临界资源的使用,如串口文件句柄、主线程的控制
变量,主线程退出前应当通知工作线程并等待其终止。
 工作线程启动需要一个传入参数用来访问主线程的资源,通常这样定义传入参数:
struct {
BOOL bActive; /*主线程活动标志,通常用于通知后台常驻线程(如侦听线程),主线
程即将退出*/
HANDLE hCom; /*串口文件句柄*/
HANDLE evTerm; /*用于通知主线程,本线程已经终止*/
CRITICAL_SECTION cs; /*用于控制临界资源访问*/
BYTE* data; /*读线程的数据指针或写线程的缓冲区指针*/
DWORD dwQueryBytes; /*请求读写操作的字节数*/
DWORD dwResultBytes; /*用于返回操作完成后的实际字节数*/
DWORD dwTimeouts; /*阻塞超时毫秒数*/
int nResultCode; /*用于返回操作的状态码*/
}ThreadParam;
1)、侦听线程的处理模式:
1-2)、主线程启动侦听线程:
ThreadParam tp_listen; /*主线程定义的侦听线程传入参数*/
tp_listen.bActive = TRUE; /*主线程当前活动*/
tp_listen.hCom = hCom; /*已打开的端口句柄*/
tp_listen.evTerm = CreateEvent(NULL,TRUE,FALSE,NULL); /*创建异步事件*/
tp_listen.cs = cs; /*已经创建的临界区*/
tp_listen.data = NULL; /*不使用*/
tp_listen.dwQueryBytes = tp.dwResultBytes = 0; /*不使用*/
tp_listen.dwTimeouts = nListenTimes; /*既定的侦听超时数*/
tp_listen.nResultCode = 0; /*初始状态码*/

/*创建侦听线程*/
CreateThread(NULL, /*无权限属性*/
0, /*默认线程堆栈大小*/
(LPTHREAD_START_ROUTINE)ListenProc, /*侦听线程回调函数*/
(LPVOID)&tp_listen, /*传入参数*/
0, /*线程创建后即运行*/
&dw/*返回线程ID*/);

1-2)、主线程终止侦听线程:
EnterCriticalSection(&tp_listen->cs); /*申请临界资源*/
ResetEvent(tp_listen->evTerm); /*重制异步事件*/
tp_listen.bActive = FALSE; /*通知后台线程,主线程准备退出*/
LeaveCriticalSection(&tp_listen->cs); /*释放临界资源*/
/*等待侦听线程中止*/
WaitForSingleObject(tp_listen->evTerm,INFINITE);
CloseHandle(tp_listen.evTerm); /*关闭打开时创建的异步事件*/

1-3)、侦听回调函数负责处理具体的端口事件响应,线程控制方式如下:
DWORD WINAPI ListenProc(LPVOID lpParam)
{
ThreadParam* pListen = (ThreadParam*)lpParam;
BOOL bActive;
OVERLAPPED ov; /*重叠I/O结构用于等待端口事件*/

EnterCriticalSection(&pListen->cs);
/*初始化工作,比如初始输入、输出缓冲区,设置事件标志*/

LeaveCriticalSection(&pListen->cs);
memset((void*)&ov,0,sizeof(ov));
ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
bActive = pListen->bActive;
while(bActive)
{
 /*异步等待端口事件*/
 dwMask = 0;
 /*重制信号*/
 ResetEvent(ov.hEvent);
 /*异步等待端口事件*/
 EnterCriticalSection(&pListen->cs);
 WaitCommEvent(pListen->hCom,&dwMask,&ov);
 LeaveCriticalSection(&pListen->cs);
 /*进入阻塞直至事件到来或超时*/
 WaitForSingleObject(ov.hEvent,pListen->dwTimeout);
 
 switch(dwMask)
 {
  /*事件分派处理*/
  …
}
 /*查询主线程终止信号*/
 EnterCriticalSection(&pListen->cs);
 bActive = pListen->bActive;
 LeaveCriticalSection(&pListen->cs);
}
/*侦听循环停止后处理*/
CloseHandle(ov.hEvent);

EnterCriticalSection(&pListen->cs);
/*做些端口现场清理*/

LeaveCriticalSection(&pListen->cs);
/*通知住线程侦听线程结束*/
SetEvent(pListen->evListen);
ExitThread(0);
return 0;
}

2)、读线程的处理模式:
2-1)、端口读的接口函数
long ReadData(…,long size, BYTE* data)
{
COMTIMEOUTS to;
GetCommTimeouts(hCom,&to); /*取超时参数*/

ThreadParam tp_read; /*主线程定义的读线程传入参数*/
tp_read.bActive = TRUE; /*主线程当前活动*/
tp_read.hCom = hCom; /*已打开的端口句柄*/
tp_read.evTerm = CreateEvent(NULL,TRUE,FALSE,NULL); /*创建异步事件*/
tp_read.cs = cs; /*已经创建的临界区*/
tp_read.data = data; /*缓冲区指针*/
tp_read.dwQueryBytes =  size;
tp_read.dwResultBytes = 0;
/*计算读的侦听超时数*/
tp_read.dwTimeouts = to.ReadTotalTimeoutConstant +
to.ReadTotalTimeoutMultiplier * size;
tp_read.nResultCode = 0; /*初始状态码*/
/*创建读线程*/
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ReadProc,(void*)&tp_read,0,&dw);
/*等待线程结束*/
WaitForSingleObject(tp_read.evTerm,INFINITE);
CloseHandle(tp_read.evTerm);

/*返回已经处理的字节数*/
return tp_read.dwResultBytes;
}
2-2)、端口读的线程回调函数
DWORD WINAPI ReadProc(LPVOID lpParam)
{
ThreadParam* pRead = (ThreadParam*)lpParam;
OVERLAPPED ov;
memset((void*)&ov,0,sizeof(OVERLAPPED));
ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
/*端口异步读*/
EnterCriticalSection(&(pRead->cs));
ReadFile(hCom,(void*)pRead->data,pRead->dwQueryBytes,&(pRead->dwResultBytes),
&ov);
LeaveCriticalSection(&(pRead->cs));

/*等待异步读结果*/
WaitForSingleObject(ov.hEvent,pRead->dwTimeouts)
/*取重叠操作结果*/
GetOverlappedResult(pRead->hCom,&ov,&pRead->dwResultBytes,FALSE);
CloseHandle(ov.hEvent);
/*通知主线程读操作完成*/
SetEvent(pRead->evRead);
ExitThread(0);
return 0;
}

3)、写线程的处理模式:
3-1)、端口写的接口函数
long WriteData(…,long size, BYTE* data)
{
COMTIMEOUTS to;
GetCommTimeouts(hCom,&to); /*取超时参数*/

ThreadParam tp_write; /*主线程定义的写线程传入参数*/
tp_write.bActive = TRUE; /*主线程当前活动*/
tp_write.hCom = hCom; /*已打开的端口句柄*/
tp_write.evTerm = CreateEvent(NULL,TRUE,FALSE,NULL); /*创建异步事件*/
tp_write.cs = cs; /*已经创建的临界区*/
tp_write.data = data; /*缓冲区指针*/
tp_write.dwQueryBytes =  size;
tp_write.dwResultBytes = 0;
/*计算写的侦听超时数*/
tp_write.dwTimeouts = to.WriteTotalTimeoutConstant +
to.WriteTotalTimeoutMultiplier * size;
tp_write.nResultCode = 0; /*初始状态码*/
/*创建写线程*/
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WriteProc,(void*)&tp_write,0,&dw)
;
/*等待线程结束*/
WaitForSingleObject(tp_write.evTerm,INFINITE);
CloseHandle(tp_write.evTerm);

/*返回已经处理的字节数*/
return tp_write.dwResultBytes;
}
3-2)、端口写的线程回调函数
DWORD WINAPI WriteProc(LPVOID lpParam)
{
ThreadParam* pWrite = (ThreadParam*)lpParam;
OVERLAPPED ov;
memset((void*)&ov,0,sizeof(OVERLAPPED));
ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
/*端口异步写*/
EnterCriticalSection(&(pWrite->cs));
WriteFile(hCom,(void*)pWrite->data,pWrite->dwQueryBytes,&(pWrite->dwResultByt
es),&ov);
LeaveCriticalSection(&(pWrite->cs));

/*等待异步写结果*/
WaitForSingleObject(ov.hEvent,pWrite->dwTimeouts)
/*取重叠操作结果*/
GetOverlappedResult(pWrite->hCom,&ov,&pWrite->dwResultBytes,FALSE);
CloseHandle(ov.hEvent);
/*通知主线程写操作完成*/
SetEvent(pWrite->evWrite);
ExitThread(0);
return 0;
}
 4、EasyComm串口控件简介:EasyComm控件是适用于WIN32环境下的串口异步侦听、读写
控件,采用前述的思路设计。主要特点在于为提高后台侦听线程的处理效率,在侦听线
程中捕获的事件都被异步发送到主线程的窗口消息循环中,然后由主线程的窗口消息处
理例程将串口事件消息通知给客户进程,客户进程根据事件类型决定下一步的动作。
 EasyComm的开发库包括:EasyComm.h EasyComm.lib EasyComm.dll
1)、EasyComm库初始化和终结:
BOOL InitEasyComm(HINSTANCE hIns);
/*说明:初始化EasyComm运行库*/
/*hIns:调用进程的句柄*/
/*返回:零值失败,非零值成功*/
/*举例:可以在进程初始化时调用*/
BOOL CSomeApp::InitInstance()
{

if(!InitEasyComm(this->m_hInstance))
 return FALSE;

}
void UnInitEasyComm(HINSTANCE hIns);
/*说明:终止EasyComm运行库*/
/*hIns:调用进程的句柄*/
/*举例:可以在进程退出时调用*/
BOOL CSomeApp::ExitInstance()
{

UnInitEasyComm(this->m_hInstance);

}

2)、EasyComm控件窗体的创建和销毁:
HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD
dwStyle,  int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU
hMenu, HANDLE hInstance,  LPVOID lpParam );
/*说明:采用WIN32函数创建EasyComm控件窗体*/
/*lpClassName: 窗体类名 “EasyCommCtrl”*/
/*lpWindowName:  窗体名*/
/*dwStyle: 窗体样式*/
/*x:  X位置*/
/*y:  Y位置*/
/* nWidth: 宽度*/
/* nHeight: 高度*/
/*hWndParent: 父窗体句柄*/
/*hMenu: 忽略*/
/*hInstance: 忽略*/
/*lpParam:  忽略*/
/*举例: 可以选择在客户窗体创建后创建EasyComm控件*/
int CCustomerWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
 return -1;
m_hCom=CreateWindow(“EasyCommCtrl”,NULL,WS_CHILD,0,0,20,20,m_hWnd,NULL,NULL
,NULL);
if(::IsWindow(m_hCom))
{
::SetWindowLong(hCom,GWL_ID,IDC_COM);
 return 0;
}else
 return -1;
}

BOOL DestroyWindow(HWND hWnd);
/*说明:采用WIN32函数销毁控件*/
/*hWnd:EasyComm控件句柄*/
/*举例:可以选择在客户窗体销毁前销毁EasyComm控件*/
void CCustomerWnd::OnDestroy()
{
if(::IsWindow(m_hCom))
 ::DestroyWindow(m_hCom);
CWnd::OnDestroy();
}

4)、EasyComm控件消息:
COM_SETPORT:设置端口名称
wParam: 0
lParam: 端口号字符串指针
举例:SendMessage(hCom,COM_SETPORT,0,(LPARAM)_T(“COM1”));

COM_GETPORT:取端口号
wParam: 缓冲区大小
lParam:缓冲区指针
举例:TCHAR buf[10];
SendMessage(hCom,COM_GETPORT,9,(LPARAM)buf);

COM_SETBAUDRATE:设置端口波特率
wParam:波特率长整数值
lParam:0
举例:SendMessage(hCom,COM_SETBAUDRATE,4800,0);

COM_GETBAUDRATE:取端口波特率
wParam: 0
lParam: 0
举例:long nBuad = (long)SendMessage(hCom,COM_GETBAUDRATE,0,0);

COM_SETBYTESIZE:设置端口位长
wParam: 位长短整数值
lParam: 0
举例:SendMessage(hCom,COM_SETBYTESIZE,8,0);

COM_GETBYTESIZE: 取端口位长
wParam: 0
lParam: 0
举例:short nByteSize = (short)SendMessage(hCom,COM_GETBYTESIZE,0,0);

COM_SETSTOPBITS: 设置端口停止位
wParam: 停止位短整数值
lParam: 0
#define STOPBITS_ONE  1 /*1位停止位*/
#define STOPBITS_TWO  2 /*2位停止位*/
#define STOPBITS_ONEHALF 3/*1.5停止位*/
举例:SendMessage(hCom,COM_SETSTOPBITS,1,0);

COM_GETSTOPBITS: 取端口停止位
wParam: 0
lParam: 0
举例:short nStopBits = (short)SendMessage(hCom,COM_GETSTOPBITS,0,0);

COM_SETPARITY : 设置端口校验
wParam: 校验短整数值
lParam: 0
#define PY_NONE    0 /*无奇偶校验*/
#define PY_EVEN    1 /*偶校验*/
#define PY_ODD    2 /*奇校验*/
举例:SendMessage(hCom,COM_SETPARITY,0,0);

COM_GETPARITY: 取端口校验
wParam: 0
lParam: 0
举例:short nParity = (short)SendMessage(hCom,COM_GETPARITY,0,0);

COM_SETFCTRL: 设置端口流控制
wParam: 流模式短整数值
lParam: 0
#define FLOWCTRL_NONE  0 /*无流控制*/
#define FLOWCTRL_HARD  1 /*硬件控制*/
#define FLOWCTRL_SOFT  2 /*软件控制*/
举例:SendMessage(hCom,COM_SETCTRL,0,0);

COM_GETFCTRL: 取端口流控制
wParam: 0
lParam: 0
举例:short nCtrl = (short)SendMessage(hCom,COM_GETCTRL,0,0);

COM_SETTIMEOUTS: 设置读写每字节超时毫秒数
wParam: LOWORD为读超时,HIWORD为写超时
lParam: 0
举例:SendMessage(hCom,COM_SETTIMEOUTS,MAKEWPARAM(1,1),0);

COM_GETTIMEOUTS: 取读写每字节超时毫秒数
wParam: 0
lparam: 0
举例:DWORD dw = (DWORD)SendMessage(hCom,COM_GETTIMEOUTS,0,0);
short nReadPerByte = LOWORD(dw); short nWritePerByte = HIWORD(dw);

COM_SETACKCHAR: 设置端口回应字符
wParam: 回应字符
lParam: 0
举例:SendMessage(hCom,COM_SETACKCHAR,(WPARAM)0x06,0);

COM_GETACKCHAR: 取端口回应字符
wParam: 0
lParam: 0
举例: char chAck = (char)SendMessage(hCom,COM_GETACKCHAR,0,0);

COM_SETLISTIMER: 设置端口侦听超时毫秒数
wParam: 短整数毫秒
lParam: 0
举例:SendMessage(hCom,COM_SETLISTIMER,100,0);

COM_GETLISTIMER: 取端口侦听超时毫秒数
wParam: 0
lParam: 0
举例:short nListemTimer = (short)SendMessage(hCom,COM_GETLISTIMER,0,0);

COM_SETQUEUE:  设置端口内部读写缓冲区大小
wParam:  读缓冲区长整数值
lParam: 写缓冲区长整数值
举例:SendMessage(hCom,COM_SETQUEUE,1024,1024)

COM_SETACTIVE: 设置端口状态,打开或关闭
wParam: TRUE则打开端口,FALSE则关闭端口
lParam: 0
举例:SendMesage(hCom,COM_SETACTIVE,(WPARAM)TRUE,0);
SendMessage(hCom,COM_SETACTIVE,(WPARAM)FALSE,0);

COM_GETACTIVE: 取端口状态
wParam:  0
lParam: 0
举例:BOOL bActive = SendMessage(hCom,COM_GETACTIVE,0,0);

COM_SETDATA: 向端口写数据
wParam: 长整数值的数据大小
lParam: 数据指针
返回:已写的字节数
举例:long nWrited =
SendMessage(hCom,COM_SETDATA,(WPARAM)size,(LPARAM)data);

COM_GETDATA: 从端口读数据
wParam: 长整数值的请求字节数
lParam: 缓冲区指针
返回:已读的字节数
举例:long nReaded = SendMessage(hCom,COM_GETDATA,(WPARAM)size,(LPARAM)buf);

5)、EasyComm的通知消息:控件通过WM_NOTIFY通知父客户窗体,该消息体的结构为:
struct {
 HWND hwndFrom;  /*控件的窗体句柄*/
    UINT idFrom;  /*控件的ID*/
    UINT code;  /*消息标识*/
 WPARAM wParam; /*根据消息标识设置*/
 LPARAM lParam; /*根据消息标识设置*/
}NMHDR_COMM;
用于通知端口状态的结构:
struct {
 BOOL bRLSDHold; /*非零则端口等待RLSD信号,输出阻塞*/
 BOOL bCTSHold; /*非零则端口等待CTS信号,输出阻塞*/
 BOOL bDSRHold; /*非零则端口等待DSR信号,输出阻塞*/
 BOOL bXOFFHold; /*非零则收到XOFF字符,输出阻塞*/
 BOOL bXOFFSent; /*非零则送出XOFF字符,输出阻塞*/
 int nInQueue; /*端口输入缓冲区字符数*/
 int nOutQueue; /*端口输出缓冲区字符数*/
}CommMonitor;

具体消息标识(code)分述如下:
NC_COMMBREAK:端口中断通知
wParam: 忽略
lParam: 忽略

NC_COMMERROR:端口错误通知
wParam: 错误代码
lParam: 错误文本指针

NC_COMMMONITOR:端口状态通知
wParam: 忽略
lParam: 端口状态结构CommMonitor指针

NC_COMMACK:端口收到回应字符
wParam:  输入缓冲区可读的字符数
lParam:  忽略

NC_COMMRECIVE:端口收到字符
wParam:  输入缓冲区可读的字符数
lParam:  忽略
举例:在客户窗体中响应控件通知
BOOL CCustomerWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
NMHDR* phdr = (NMHDR*)lParam;
if(phdr->idFrom == IDC_COM)
{
 NMHDR_COMM* pnc = (NMHDR_COMM*)lParam;
 if(pnc->code == NC_COMMBREAK)
 {
  AfxMessageBox(_T("Comm Breaked"));
 }else if(pnc->code == NC_COMMERROR)
 {
  AfxMessageBox((TCHAR*)pnc->lParam);
 }else if(pnc->code == NC_COMMMONITOR)
 {
  CommMonitor* pcm = (CommMonitor*)pnc->lParam;
  …
 }else if(pnc->code == NC_COMMRECIVE)
 {
  if(pnc->wParam)
  {
   TCHAR *tmp = (TCHAR*)calloc((long)pnc->wParam + 1,sizeof(TCHAR));
   ::SendMessage(hCom,COM_GETDATA,(WPARAM)pnc->wParam,(LPARAM)tmp);

   free(tmp);
  }
 }
}
return CWnd::OnNotify(wParam, lParam, pResult);
}

 
 
 
 
 
23:44  |  固定链接 | 评论 (0) | 引用通告 (0) | 记录它
 
 
固定链接  关闭
 
http://spaces.msn.com/members/GreenSui/Blog/cns!1p3EDoyO7R34_La61JPx-Kcg!116.
entry
 

 
 
 

 
Windows多线程多任务设计
 
[前言:]当前流行的Windows操作系统,它能同时运行几个程序(独立运行的程序又称
之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线
程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,
进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的应用软件无一
不是多线程多任务处理,单线城的软件是不可想象的。因此掌握多线程多任务设计方法
对每个程序员都是必需要掌握的。本文针对多线程技术在应用中经常遇到的问题,如线
程间的通信、同步等,对它们分别进行探讨。

  一、 理解线程

  要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有
的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程
的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独
立的执行单元,相当于一个子程序,它对应Visual C++中的CwinThread类的对象。单独
一个执行程序运行时,缺省的运行包含的一个主线程,主线程以函数地址的形式,如mai
n或WinMain函数,提供程序的启动点,当主线程终止时,进程也随之终止,但根据需要
,应用程序又可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。

  一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系
统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时
间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,
所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先
级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。

  线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通
常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对
象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着
该程序的结束,进城终止。工作者线程用来执行程序的后台处理任务,比如计算、调度
、对串口的读写操作等,它和用户界面线程的区别是它不用从CwinThread类派生来创建
,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线
程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线
程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。


二、 线程的管理和操作

  1. 线程的启动

  创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DEC
LARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。

  第二步是根据需要重载该派生类的一些成员函数如:ExitInstance();InitInstanc
e();OnIdle();PreTranslateMessage()等函数,最后启动该用户界面线程,调用AfxBeg
inThread()函数的一个版本:CWinThread* AfxBeginThread( CRuntimeClass*
pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0,
DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );其中
第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三
个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状
态,如为CREATE_SUSPENDED则线程启动后为挂起状态。

  对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分
并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用
AfxBeginThread(Fun1,param,priority)函数,返回值付给pThread变量的同时一并启动
该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所
说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定
义该线程的优先级别,它是预定义的常数,读者可参考MSDN。

  2.线程的优先级

  以下的CwinThread类的成员函数用于线程优先级的操作:

int GetThreadPriority();
BOOL SetThradPriority()(int nPriority);

上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所
处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不
同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,
自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具
有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没
有提供相应的函数,但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityCla
ss()来实现。

  3.线程的悬挂、恢复

  CwinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendTh
read()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你
对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()
来恢复线程的运行。

  4.结束线程

  终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行
;可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD
dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占
用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面
以第三种方法为例,给出部分代码:


//CtestView message handlers
/Set to True to end thread
Bool bend=FALSE;//定义的全局变量,用于控制线程的运行
//The Thread Function
UINT ThreadFunction(LPVOID pParam)//线程函数
{
while(!bend)
{Beep(100,100);
Sleep(1000);
}
return 0;
}
CwinThread *pThread;
HWND hWnd;
/
Void CtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程
pThread->m_bAutoDelete=FALSE;//线程为手动删除
Cview::OnInitialUpdate();
}

Void CtestView::OnDestroy()
{ bend=TRUE;//改变变量,线程结束
WaitForSingleObject(pThread->m_hThread,INFINITE);//等待线程结束
delete pThread;//删除线程
Cview::OnDestroy();
}


三、 线程之间的通信

  通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示
在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实
现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件
对象、使用消息。这里我们主要介绍后两种方法。

  1. 利用用户定义的消息通信

  在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线
程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用
户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,
在一个线程中调用

::PostMessage((HWND)param,WM_USERMSG,0,0)

CwinThread::PostThradMessage()

来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗
口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代
码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:

UINT ThreadFunction(LPVOID pParam)
{
while(!bend)
{
Beep(100,100);
Sleep(1000);
}
::PostMessage(hWnd,WM_USERMSG,0,0);
return 0;
}
WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,LPARAM lParam)
LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)
{
AfxMessageBox("Thread ended.");
Retrun 0;
}

上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模
式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消
息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消
息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为
空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息
队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。

  2.用事件对象实现通信

  在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的
对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号
状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:


Cevent threadStart,threadEnd;

UINT ThreadFunction(LPVOID pParam)
{
::WaitForSingleObject(threadStart.m_hObject,INFINITE);
AfxMessageBox("Thread start.");
while(!bend)
{
Beep(100,100);
Sleep(1000);
Int result=::WaitforSingleObject(threadEnd.m_hObject,0);
//等待threadEnd事件有信号,无信号时线程在这里悬停
If(result==Wait_OBJECT_0)
Bend=TRUE;
}
::PostMessage(hWnd,WM_USERMSG,0,0);
return 0;
}
/
Void CtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
threadStart.SetEvent();//threadStart事件有信号
pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程
pThread->m_bAutoDelete=FALSE;
Cview::OnInitialUpdate();
}

Void CtestView::OnDestroy()
{ threadEnd.SetEvent();
WaitForSingleObject(pThread->m_hThread,INFINITE);
delete pThread;
Cview::OnDestroy();
}

运行这个程序,当关闭程序时,才显示提示框,显示"Thread ended"


四、 线程之间的同步

  前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需
要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的
完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍
的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行
性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对
象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象
(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mu
texe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安
全。

  1. 临界区

  临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,
需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访
问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临
界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着
一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成
员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临
界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启
动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组
array[]操作,下面的代码说明了如何使用临界区对象:

#include "afxmt.h"
int array[10],destarray[10];
CCriticalSection Section;

UINT WriteThread(LPVOID param)
{Section.Lock();
for(int x=0;x<10;x++)
array[x]=x;
Section.Unlock();
}
UINT ReadThread(LPVOID param)
{
Section.Lock();
For(int x=0;x<10;x++)
Destarray[x]=array[x];
Section.Unlock();
}

上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,
如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。
 2. 互斥

  互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线
程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmu
tex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,
用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock
对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:

#include "afxmt.h"
int array[10],destarray[10];
CMutex Section;

/
UINT WriteThread(LPVOID param)
{ CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();
for(int x=0;x<10;x++)
array[x]=x;
singlelock.Unlock();
}
UINT ReadThread(LPVOID param)
{ CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();

For(int x=0;x<10;x++)
Destarray[x]=array[x];
singlelock.Unlock();

}

  3. 信号量

  信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同
一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量
对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或Cm
ltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下
面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才
得以显示。

/
Csemaphore *semaphore;
Semaphore=new Csemaphore(2,2);
HWND hWnd=GetSafeHwnd();
AfxBeginThread(threadProc1,hWnd);
AfxBeginThread(threadProc2,hWnd);
AfxBeginThread(threadProc3,hWnd);
//
UINT ThreadProc1(LPVOID param)
{CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);
return 0;
}
UINT ThreadProc2(LPVOID param)
{CSingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);
return 0;
}
UINT ThreadProc3(LPVOID param)
{CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);
return 0;


  对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处
理能力。本文讲述了线程中经常遇到的问题,希望对读者朋友有一定的帮助。
 
 
 
 
 
23:30  |  固定链接 | 评论 (0) | 引用通告 (0) | 记录它 | 计算机与 Internet
 
 
固定链接  关闭
 
http://spaces.msn.com/members/GreenSui/Blog/cns!1p3EDoyO7R34_La61JPx-Kcg!115.
entry
 

 
 
 

 
6月22日
 
 
 
开放式课程(Opencourceware)---MIT
 
在第五百门课程上线之后,麻省理工学院实践了我们在2001年当“开放式课程网页”上
线时所许下的承诺。我们很高兴全球各地的教育家、学生和自学者告诉我们,麻省理工
学院开放式课程对教育和学习造成了相当的冲击。我们认为“开放式课程网页”是个让
教育大众化的新天地。我们希望藉由分享麻省理工学院教材的概念,以及推广麻省理工
学院开放式课程的经验,可以启发其它机构开放、分享它们的课程,建立一个将造福全
人类的知识网络。这个知识网络将可以提升学习的品质,更藉此进一步的提升全世界的
生活品质。

我们感谢我们的赞助者William and Flora Hewlett 基金会与Andrew W. Mellon 基金会
,还有我们全校的教职员工,他们投注了非常多的创造力和时间来努力达成这目标。我
们很高兴和你们一同携手走上这趟教育的旅程。

                          麻省理工学院前校长,查尔斯.M.威斯特(Charles M.
Vest)

 

机会与开放性


查尔斯.M.威斯特校长在2002年6月7日,对麻省理工学院第136届毕业生的演讲
http://web.mit.edu/president/communications/com02.html

“我们也必须下定决心利用我们的新科技:网际网络和全球资讯网;利用它们来对全世
界的人类赋予知识的力量以及让教育更为平民化。明年春天,麻省理工学院的教职员工
将会公开麻省理工学院开放式课程的计划-这个计划将会让我们的两千余学门的基础教材
放上网页,让任何地方的任何人都可以免费取用。‘我们为什么要这样做呢?因为我们
认为这是我们的使命:协助提升全世界每个角落的高等教育。’这个计划奠基于两个比
肩而行的价值观:机会与开放性。这两个价值观让我们的大学与国家强盛。这两个价值
观也会让我们的世界变得安全与繁荣。当你们进入这个社会时,这也是你们应该珍惜和
保护的价值观。”

 

搅乱教育界的一池春水:在数位时代的大学,要做恐龙还是作先驱?


查尔斯.M.威斯特校长在2000-01学年的年度报告,内容针对科技对高等教育的冲击,
以及评估未来以研究为导向的大学所需扮演的角色。
http://web.mit.edu/president/communications/rpt00-01.html

“计算机产业从封闭性软件系统中学到了惨痛的教训,这种以独占知识的架构所设计出
来的系统并不能融入他们自己所创造的世界中。开放性软件的有机世界和开放性系统才
是未来真正的趋势。高等教育也必须从这学到教训。我们必须替教导和学系建立新的,
以开放式系统为主的架构。以这个精神,麻省理工学院不禁自问,借用诗人T.S. Eliot
的话,‘我是否敢...扰动这个宇宙?’我们的答案是肯定的。我们称这个计划为麻省理
工学院“开放式课程网页”(OCW)。我们认为“开放式课程网页”开启了通往教育强而
有力的教化人心和平民化趋势的新入口。”

 

世界银行总裁詹姆士.D.沃分桑(James D. Wolfensohn)于2002年6月7日麻省理工学
院毕业典礼上的演讲


http://web.mit.edu/newsoffice/nr/2002/comm-jdw.html

我们和我们所属的机构(世界银行),非常荣幸可以和诸位合作,而且,特别是我认为
这可能是敝机构不管是过去或是未来最大的跃进。我们和贵校所推动的麻省理工学院“
开放式课程网页”计划的合作有了更进一步的发展,我们不只让我们的非洲虚拟大学加
入合作,更希望在近年内将这所学校内的知识、经验和教育的力量拓展出去。以我一个
在世界银行任职,专职负责世界发展的工作者角度看来,这样的贡献可能是诸位所做过
最伟大的付出。诸位不只对这个国家开放这课程,更对全世界毫无保留的付出,我在此
恭贺各位,并对各位的努力致敬。”
 
 
 
 
 
23:39  |  固定链接 | 评论 (0) | 引用通告 (0) | 记录它
 
 
固定链接  关闭
 
http://spaces.msn.com/members/GreenSui/Blog/cns!1p3EDoyO7R34_La61JPx-Kcg!110.
entry
 

 
 
 

 
6月21日
 
 
 
CreateProcess
 
CreateProcess
The CreateProcess function creates a new process and its primary thread.
The new process executes the specified executable file.

BOOL CreateProcess( LPCTSTR lpApplicationName, // pointer to name of
executable module LPTSTR lpCommandLine, // pointer to command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // process security attributes
LPSECURITY_ATTRIBUTES lpThreadAttributes, // thread security attributes
BOOL bInheritHandles, // handle inheritance flag DWORD dwCreationFlags, //
creation flags LPVOID lpEnvironment, // pointer to new environment block
LPCTSTR lpCurrentDirectory, // pointer to current directory name
LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO
LPPROCESS_INFORMATION lpProcessInformation // pointer to
PROCESS_INFORMATION ); Parameters lpApplicationName Pointer to a
null-terminated string that specifies the module to execute.
The string can specify the full path and filename of the module to execute
or it can specify a partial name. In the case of a partial name, the
function uses the current drive and current directory to complete the
specification.

The lpApplicationName parameter can be NULL. In that case, the module name
must be the first white space-delimited token in the lpCommandLine string.
If you are using a long filename that contains a space, use quoted strings
to indicate where the filename ends and the arguments begin, otherwise, the
filename is ambiguous. For example, consider the string "c:/program
files/sub dir/program name". This string can be interpreted in a number of
ways. The system tries the possibilities in the following order:

c:/program.exe files/sub dir/program name
c:/program files/sub.exe dir/program name
c:/program files/sub dir/program.exe name
c:/program files/sub dir/program name.exe

The specified module can be a Win32-based application. It can be some other
type of module (for example, MS-DOS or OS/2) if the appropriate subsystem
is available on the local computer.

Windows NT: If the executable module is a 16-bit application,
lpApplicationName should be NULL, and the string pointed to by
lpCommandLine should specify the executable module. A 16-bit application is
one that executes as a VDM or WOW process.

lpCommandLine Pointer to a null-terminated string that specifies the
command line to execute. The system adds a null character to the command
line, trimming the string if necessary, to indicate which file was actually
used.
The lpCommandLine parameter can be NULL. In that case, the function uses
the string pointed to by lpApplicationName as the command line.

If both lpApplicationName and lpCommandLine are non-NULL,
*lpApplicationName specifies the module to execute, and *lpCommandLine
specifies the command line. The new process can use GetCommandLine to
retrieve the entire command line. C runtime processes can use the argc and
argv arguments.

If lpApplicationName is NULL, the first white space-delimited token of the
command line specifies the module name. If you are using a long filename
that contains a space, use quoted strings to indicate where the filename
ends and the arguments begin (see the explanation for the lpApplicationName
parameter). If the filename does not contain an extension, .EXE is assumed.
If the filename ends in a period (.) with no extension, or the filename
contains a path, .EXE is not appended. If the filename does not contain a
directory path, the system searches for the executable file in the
following sequence:

The directory from which the application loaded.
The current directory for the parent process.
Windows 95 and Windows 98: The Windows system directory. Use the
GetSystemDirectory function to get the path of this directory.
Windows NT: The 32-bit Windows system directory. Use the GetSystemDirectory
function to get the path of this directory. The name of this directory is
SYSTEM32.

Windows NT: The 16-bit Windows system directory. There is no Win32 function
that obtains the path of this directory, but it is searched. The name of
this directory is SYSTEM.
The Windows directory. Use the GetWindowsDirectory function to get the path
of this directory.
The directories that are listed in the PATH environment variable.
If the process to be created is an MS-DOS - based or 16-bit Windows-based
application, lpCommandLine should be a full command line in which the first
element is the application name. Because this also works well for
Win32-based applications, it is the most robust way to set lpCommandLine.

lpProcessAttributes Pointer to a SECURITY_ATTRIBUTES structure that
determines whether the returned handle can be inherited by child processes.
If lpProcessAttributes is NULL, the handle cannot be inherited.
Windows NT: The lpSecurityDescriptor member of the structure specifies a
security descriptor for the new process. If lpProcessAttributes is NULL,
the process gets a default security descriptor.

lpThreadAttributes Pointer to a SECURITY_ATTRIBUTES structure that
determines whether the returned handle can be inherited by child processes.
If lpThreadAttributes is NULL, the handle cannot be inherited.
Windows NT: The lpSecurityDescriptor member of the structure specifies a
security descriptor for the main thread. If lpThreadAttributes is NULL, the
thread gets a default security descriptor.

bInheritHandles Indicates whether the new process inherits handles from the
calling process. If TRUE, each inheritable open handle in the calling
process is inherited by the new process. Inherited handles have the same
value and access privileges as the original handles. dwCreationFlags
Specifies additional flags that control the priority class and the creation
of the process. The following creation flags can be specified in any
combination, except as noted: Value Meaning CREATE_DEFAULT_ERROR_MODE The
new process does not inherit the error mode of the calling process.
Instead, CreateProcess gives the new process the current default error
mode. An application sets the current default error mode by calling
SetErrorMode.
This flag is particularly useful for multi-threaded shell applications that
run with hard errors disabled.

The default behavior for CreateProcess is for the new process to inherit
the error mode of the caller. Setting this flag changes that default
behavior.

CREATE_NEW_CONSOLE The new process has a new console, instead of inheriting
the parent's console. This flag cannot be used with the DETACHED_PROCESS
flag. CREATE_NEW_PROCESS_GROUP The new process is the root process of a new
process group. The process group includes all processes that are
descendants of this root process. The process identifier of the new process
group is the same as the process identifier, which is returned in the
lpProcessInformation parameter. Process groups are used by the
GenerateConsoleCtrlEvent function to enable sending a ctrl+c or ctrl+break
signal to a group of console processes. CREATE_SEPARATE_WOW_VDM Windows NT:
This flag is valid only when starting a 16-bit Windows-based application.
If set, the new process is run in a private Virtual DOS Machine (VDM). By
default, all 16-bit Windows-based applications are run as threads in a
single, shared VDM. The advantage of running separately is that a crash
only kills the single VDM; any other programs running in distinct VDMs
continue to function normally. Also, 16-bit Windows-based applications that
are run in separate VDMs have separate input queues. That means that if one
application hangs momentarily, applications in separate VDMs continue to
receive input. The disadvantage of running separately is that it takes
significantly more memory to do so. You should use this flag only if the
user requests that 16-bit applications should run in them own VDM.
CREATE_SHARED_WOW_VDM Windows NT: The flag is valid only when starting a
16-bit Windows-based application. If the DefaultSeparateVDM switch in the
Windows section of WIN.INI is TRUE, this flag causes the CreateProcess
function to override the switch and run the new process in the shared
Virtual DOS Machine. CREATE_SUSPENDED The primary thread of the new process
is created in a suspended state, and does not run until the ResumeThread
function is called. CREATE_UNICODE_ENVIRONMENT If set, the environment
block pointed to by lpEnvironment uses Unicode characters. If clear, the
environment block uses ANSI characters. DEBUG_PROCESS If this flag is set,
the calling process is treated as a debugger, and the new process is a
process being debugged. The system notifies the debugger of all debug
events that occur in the process being debugged.
If you create a process with this flag set, only the calling thread (the
thread that called CreateProcess) can call the WaitForDebugEvent function.

Windows 95 and Windows 98: This flag is not valid if the new process is a
16-bit application.

DEBUG_ONLY_THIS_PROCESS If not set and the calling process is being
debugged, the new process becomes another process being debugged by the
calling process's debugger. If the calling process is not a process being
debugged, no debugging-related actions occur. DETACHED_PROCESS For console
processes, the new process does not have access to the console of the
parent process. The new process can call the AllocConsole function at a
later time to create a new console. This flag cannot be used with the
CREATE_NEW_CONSOLE flag.

The dwCreationFlags parameter also controls the new process's priority
class, which is used in determining the scheduling priorities of the
process's threads. If none of the following priority class flags is
specified, the priority class defaults to NORMAL_PRIORITY_CLASS unless the
priority class of the creating process is IDLE_PRIORITY_CLASS. In this case
the default priority class of the child process is IDLE_PRIORITY_CLASS. One
of the following flags can be specified: Priority Meaning
HIGH_PRIORITY_CLASS Indicates a process that performs time-critical tasks
that must be executed immediately for it to run correctly. The threads of a
high-priority class process preempt the threads of normal-priority or
idle-priority class processes. An example is the Task List, which must
respond quickly when called by the user, regardless of the load on the
system. Use extreme care when using the high-priority class, because a
high-priority class CPU-bound application can use nearly all available
cycles. IDLE_PRIORITY_CLASS Indicates a process whose threads run only when
the system is idle and are preempted by the threads of any process running
in a higher priority class. An example is a screen saver. The idle priority
class is inherited by child processes. NORMAL_PRIORITY_CLASS Indicates a
normal process with no special scheduling needs. REALTIME_PRIORITY_CLASS
Indicates a process that has the highest possible priority. The threads of
a real-time priority class process preempt the threads of all other
processes, including operating system processes performing important tasks.
For example, a real-time process that executes for more than a very brief
interval can cause disk caches not to flush or cause the mouse to be
unresponsive.


lpEnvironment Pointer to an environment block for the new process. If this
parameter is NULL, the new process uses the environment of the calling
process.
An environment block consists of a null-terminated block of null-terminated
strings. Each string is in the form: name=value

Because the equal sign is used as a separator, it must not be used in the
name of an environment variable.

If an application provides an environment block, rather than passing NULL
for this parameter, the current directory information of the system drives
is not automatically propagated to the new process. For a discussion of
this situation and how to handle it, see the following Remarks section.

An environment block can contain Unicode or ANSI characters. If the
environment block pointed to by lpEnvironment contains Unicode characters,
the dwCreationFlags field's CREATE_UNICODE_ENVIRONMENT flag will be set. If
the block contains ANSI characters, that flag will be clear.

Note that an ANSI environment block is terminated by two zero bytes: one
for the last string, one more to terminate the block. A Unicode environment
block is terminated by four zero bytes: two for the last string, two more
to terminate the block.

lpCurrentDirectory Pointer to a null-terminated string that specifies the
current drive and directory for the child process. The string must be a
full path and filename that includes a drive letter. If this parameter is
NULL, the new process is created with the same current drive and directory
as the calling process. This option is provided primarily for shells that
need to start an application and specify its initial drive and working
directory. lpStartupInfo Pointer to a STARTUPINFO structure that specifies
how the main window for the new process should appear. lpProcessInformation
Pointer to a PROCESS_INFORMATION structure that receives identification
information about the new process. Return Values
If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error
information, call GetLastError.

Remarks
The CreateProcess function is used to run a new program. The WinExec and
LoadModule functions are still available, but they are implemented as calls
to CreateProcess.

In addition to creating a process, CreateProcess also creates a thread
object. The thread is created with an initial stack whose size is described
in the image header of the specified program's executable file. The thread
begins execution at the image's entry point.

The new process and the new thread handles are created with full access
rights. For either handle, if a security descriptor is not provided, the
handle can be used in any function that requires an object handle to that
type. When a security descriptor is provided, an access check is performed
on all subsequent uses of the handle before access is granted. If the
access check denies access, the requesting process is not able to use the
handle to gain access to the thread.

The process is assigned a 32-bit process identifier. The identifier is
valid until the process terminates. It can be used to identify the process,
or specified in the OpenProcess function to open a handle to the process.
The initial thread in the process is also assigned a 32-bit thread
identifier. The identifier is valid until the thread terminates and can be
used to uniquely identify the thread within the system. These identifiers
are returned in the PROCESS_INFORMATION structure.

When specifying an application name in the lpApplicationName or
lpCommandLine strings, it doesn't matter whether the application name
includes the filename extension, with one exception: an MS-DOS – based or
Windows-based application whose filename extension is .COM must include the
.COM extension.

The calling thread can use the WaitForInputIdle function to wait until the
new process has finished its initialization and is waiting for user input
with no input pending. This can be useful for synchronization between
parent and child processes, because CreateProcess returns without waiting
for the new process to finish its initialization. For example, the creating
process would use WaitForInputIdle before trying to find a window
associated with the new process.

The preferred way to shut down a process is by using the ExitProcess
function, because this function notifies all dynamic-link libraries (DLLs)
attached to the process of the approaching termination. Other means of
shutting down a process do not notify the attached DLLs. Note that when a
thread calls ExitProcess, other threads of the process are terminated
without an opportunity to execute any additional code (including the thread
termination code of attached DLLs).

ExitProcess, ExitThread, CreateThread, CreateRemoteThread, and a process
that is starting (as the result of a call by CreateProcess) are serialized
between each other within a process. Only one of these events can happen in
an address space at a time. This means the following restrictions hold:

During process startup and DLL initialization routines, new threads can be
created, but they do not begin execution until DLL initialization is done
for the process.
Only one thread in a process can be in a DLL initialization or detach
routine at a time.
The ExitProcess function does not return until no threads are in their DLL
initialization or detach routines.
The created process remains in the system until all threads within the
process have terminated and all handles to the process and any of its
threads have been closed through calls to CloseHandle. The handles for both
the process and the main thread must be closed through calls to
CloseHandle. If these handles are not needed, it is best to close them
immediately after the process is created.

When the last thread in a process terminates, the following events occur:

All objects opened by the process are implicitly closed.
The process's termination status (which is returned by GetExitCodeProcess)
changes from its initial value of STILL_ACTIVE to the termination status of
the last thread to terminate.
The thread object of the main thread is set to the signaled state,
satisfying any threads that were waiting on the object.
The process object is set to the signaled state, satisfying any threads
that were waiting on the object.
If the current directory on drive C is /MSVC/MFC, there is an environment
variable called =C: whose value is C:/MSVC/MFC. As noted in the previous
description of lpEnvironment, such current directory information for a
system's drives does not automatically propagate to a new process when the
CreateProcess function's lpEnvironment parameter is non-NULL. An
application must manually pass the current directory information to the new
process. To do so, the application must explicitly create the =X
environment variable strings, get them into alphabetical order (because the
system uses a sorted environment), and then put them into the environment
block specified by lpEnvironment. Typically, they will go at the front of
the environment block, due to the previously mentioned environment block
sorting.

One way to obtain the current directory variable for a drive X is to call
GetFullPathName("X:",. .). That avoids an application having to scan the
environment block. If the full path returned is X:/, there is no need to
pass that value on as environment data, since the root directory is the
default current directory for drive X of a new process.

The handle returned by the CreateProcess function has PROCESS_ALL_ACCESS
access to the process object.

The current directory specified by the lpcurrentDirectory parameter is the
current directory for the child process. The current directory specified in
item 2 under the lpCommandLine parameter is the current directory for the
parent process.

Windows NT: When a process is created with CREATE_NEW_PROCESS_GROUP
specified, an implicit call to SetConsoleCtrlHandler(NULL,TRUE) is made on
behalf of the new process; this means that the new process has ctrl+c
disabled. This lets good shells handle ctrl+c themselves, and selectively
pass that signal on to sub-processes. ctrl+break is not disabled, and may
be used to interrupt the process/process group.

Windows CE: The name of the module to execute must be specified by the
lpApplicationName parameter. Windows CE does not support passing NULL for
lpApplicationName. The execution module cannot be specified in the command
line string.

Windows CE searches the directories indicated by the lpApplicationName
parameter in the following order:

The root of the PC Card if it exists
The windows (/windows) directory
The root (/ ) directory of the device
The following parameters are not supported and require the following
settings:

lpProcessAttributes must be NULL
lpThreadAttributes must be NULL
bInheritHandles must be FALSE
lpEnvironment must be NULL
lpCurrentDirectory must be NULL
lpStartupInfo must be NULL
 

 


进程

  进程是当前操作系统下一个被加载到内存的、正在运行的应用程序的实例。每一个
进程都是由内核对象和地址空间所组成的,内核对象可以让系统在其内存放有关进程的
统计信息并使系统能够以此来管理进程,而地址空间则包括了所有程序模块的代码和数
据以及线程堆栈、堆分配空间等动态分配的空间。进程仅仅是一个存在,是不能独自完
成任何操作的,必须拥有至少一个在其环境下运行的线程,并由其负责执行在进程地址
空间内的代码。在进程启动的同时即同时启动了一个线程,该线程被称作主线程或是执
行线程,由此线程可以继续创建子线程。如果主线程退出,那么进程也就没有存在的可
能了,系统将自动撤消该进程并完成对其地址空间的释放。

  加载到进程地址空间的每一个可执行文件或动态链接库文件的映象都会被分配一个
与之相关联的全局唯一的实例句柄(Hinstance)。该实例句柄实际是一个记录有进程加
载位置的基本内存地址。进程的实例句柄在程序入口函数WinMain()中通过第一个参数
HINSTANCE hinstExe传递,其实际值即为进程所使用的基本地址空间的地址。对于VC++
链接程序所链接产生的程序,其默认的基本地址空间地址为0x00400000,如没有必要一
般不要修改该值。在程序中,可以通过GetModuleHandle()函数得到指定模块所使用的
基本地址空间。
子进程的创建

  进程的创建通过CreateProcess()函数来实现,CreateProcess()通过创建一个
新的进程及在其地址空间内运行的主线程来启动并运行一个新的程序。具体的,在执行C
reateProcess()函数时,首先由操作系统负责创建一个进程内核对象,初始化计数为1
,并立即为新进程创建一块虚拟地址空间。随后将可执行文件或其他任何必要的动态链
接库文件的代码和数据装载到该地址空间中。在创建主线程时,也是首先由系统负责创
建一个线程内核对象,并初始化为1。最后启动主线程并执行进程的入口函数WinMain(
),完成对进程和执行线程的创建。

  CreateProcess()函数的原型声明如下:

BOOL CreateProcess(
 LPCTSTR lpApplicationName, // 可执行模块名
 LPTSTR lpCommandLine, // 命令行字符串
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性
 BOOL bInheritHandles, // 句柄继承标志
 DWORD dwCreationFlags, // 创建标志
 LPVOID lpEnvironment, // 指向新的环境块的指针
 LPCTSTR lpCurrentDirectory, // 指向当前目录名的指针
 LPSTARTUPINFO lpStartupInfo, // 指向启动信息结构的指针
 LPPROCESS_INFORMATION lpProcessInformation // 指向进程信息结构的指针
);

  在程序设计时,某一个具体的功能模块可以通过函数或是线程等不同的形式来实现
。对于同一进程而言,这些函数、线程都是存在于同一个地址空间下的,而且在执行时
,大多只对与其相关的一些数据进行处理。如果算法存在某种错误,将有可能破坏与其
同处一个地址空间的其他一些重要内容,这将造成比较严重的后果。为保护地址空间中
的内容可以考虑将那些需要对地址空间中的数据进行访问的操作部分放到另外一个进程
的地址空间中运行,并且只允许其访问原进程地址空间中的相关数据。具体的,可在进
程中通过CreateProcess()函数去创建一个子进程,子进程在全部处理过程中只对父进
程地址空间中的相关数据进行访问,从而可以保护父进程地址空间中与当前子进程执行
任务无关的全部数据。对于这种情况,子进程所体现出来的作用同函数和线程比较相似
,可以看成是父进程在运行期间的一个过程。为此,需要由父进程来掌握子进程的启动
、执行和退出。下面这段代码即展示了此过程:

// 临时变量
CString sCommandLine;
char cWindowsDirectory[MAX_PATH];
char cCommandLine[MAX_PATH];
DWORD dwExitCode;
PROCESS_INFORMATION pi;
STARTUPINFO si = {sizeof(si)};
// 得到Windows目录
GetWindowsDirectory(cWindowsDirectory, MAX_PATH);
// 启动"记事本"程序的命令行
sCommandLine = CString(cWindowsDirectory) + "
//NotePad.exe";
::strcpy(cCommandLine, sCommandLine);
// 启动"记事本"作为子进程
BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL,
NULL, &si, &pi);
if (ret) {
 // 关闭子进程的主线程句柄
 CloseHandle(pi.hThread);
 // 等待子进程的退出
 WaitForSingleObject(pi.hProcess, INFINITE);
 // 获取子进程的退出码
 GetExitCodeProcess(pi.hProcess, &dwExitCode);
 // 关闭子进程句柄
 CloseHandle(pi.hProcess);
}

  此段代码首先通过CreateProcess()创建Windows自带的“记事本”程序为子进程
,子进程启动后父进程通过WaitForSingleObject()函数等待其执行的结束,在子进程
没有退出前父进程是一直处于阻塞状态的,这里子进程的作用同单线程中的函数类似。
一旦子进程退出,WaitForSingleObject()函数所等待的pi.hProcess对象将得到通知
,父进程将得以继续,如有必要可以通过GetExitCodeProcess()来获取子进程的退出
代码。

  相比而言,更多的情况是父进程在启动完子进程后就再不与其进行任何数据交换和
通讯,由其创建的子进程的执行成功与否均与父进程无关。许多大型软件在设计时也多
采用了这类思想,将某些功能完全通过独立的应用程序来完成,当需要执行某操作时只
要通过主程序启动相应的子进程即可,具体的处理工作均由子进程去完成。这类子进程
的创建过程更为简单,例如对于上面那段代码只需去除对子进程句柄pi.hProcess的等待
即可:

BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL,
NULL, &si, &pi);
if (ret) {
 // 关闭子进程的主线程句柄
 CloseHandle(pi.hThread);
 // 关闭子进程句柄
 CloseHandle(pi.hProcess);
}

  可以通过dwCreationFlags参数在创建进程时设置子进程的优先级。前面的示例代码
在创建子进程时使用的均是默认的优先级,如果要将优先级设置为高,可以修改如下:

BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE,
HIGH_PRIORITY_CLASS, NULL, NULL, &si, &pi);

  如果在进程创建时没有特别设置优先级,可以通过SetPriorityClass()函数来动
态设定,该函数需要待操作进程的句柄和优先级标识符作为入口参数,函数原型为:

BOOL SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass);

  对于前面没有设定优先级的例子代码,可以在子进程启动后由父进程来动态改变其
优先级设置:

SetPriorityClass(pi.hProcess, HIGH_PRIORITY_CLASS);

  或是由子进程在其启动后自行改变优先级设置,需要注意的是这时进程句柄应设置
为子进程自身的句柄,可通过GetCurrentProcess()函数来获取:

HANDLE hProcess = GetCurrentProcess();
SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS);
进程的互斥运行

  正常情况下,一个进程的运行一般是不会影响到其他正在运行的进程的。但是对于
某些有特殊要求的如以独占方式使用串行口等硬件设备的程序就要求在其进程运行期间
不允许其他试图使用此端口设备的程序运行的,而且此类程序通常也不允许运行同一个
程序的多个实例。这就引出了进程互斥的问题。

  实现进程互斥的核心思想比较简单:进程在启动时首先检查当前系统是否已经存在
有此进程的实例,如果没有,进程将成功创建并设置标识实例已经存在的标记。此后再
创建进程时将会通过该标记而知晓其实例已经存在,从而保证进程在系统中只能存在一
个实例。具体可以采取内存映射文件、有名事件量、有名互斥量以及全局共享变量等多
种方法来实现。下面就分别对其中具有代表性的有名互斥量和全局共享变量这两种方法
进行介绍:

// 创建互斥量
HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07");
// 检查错误代码
if (GetLastError() == ERROR_ALREADY_EXISTS) {
 // 如果已有互斥量存在则释放句柄并复位互斥量
 CloseHandle(m_hMutex);
 m_hMutex = NULL;
 // 程序退出
 return FALSE;
}

  上面这段代码演示了有名互斥量在进程互斥中的用法。代码的核心是CreateMutex(
)对有名互斥量的创建。CreateMutex()函数可用来创建一个有名或无名的互斥量对象
,其函数原型为:

HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针
 BOOL bInitialOwner, // 初始化互斥对象的所有者
 LPCTSTR lpName // 指向互斥对象名的指针
);

  如果函数成功执行,将返回一个互斥量对象的句柄。如果在CreateMutex()执行前
已经存在有相同名字的互斥量,函数将返回这个已经存在互斥量的句柄,并且可以通过G
etLastError()得到错误代码ERROR_ALREADY_EXIST。可见,通过对错误代码ERROR_ALR
EADY_EXIST的检测可以实现CreateMutex()对进程的互斥。

  使用全局共享变量的方法则主要是在MFC框架程序中通过编译器来实现的。通过#pra
gma data_seg预编译指令创建一个新节,在此节中可用volatile关键字定义一个变量,
而且必须对其进行初始化。Volatile关键字指定了变量可以为外部进程访问。最后,为
了使该变量能够在进程互斥过程中发挥作用,还要将其设置为共享变量,同时允许具有
读、写访问权限。这可以通过#pragma comment预编译指令来通知编译器。下面给出使用
了全局变量的进程互斥代码清单:

#pragma data_seg("Shared")
int volatile g_lAppInstance =0;
#pragma data_seg()
#pragma comment(linker,"/section:Shared,RWS")
……
if(++g_lAppInstance>1)
return FALSE;

  此段代码的作用是在进程启动时对全局共享变量g_nAppInstancd 加1 ,如果发现其
值大于1,那么就返回FALSE以通知进程结束。这里需要特别指出的是,为了使以上两段
代码能够真正起到对进程互斥的作用,必须将其放置在应用程序的入口代码处,即应用
程序类的初始化实例函数InitInstance()的开始处。

结束进程

  进程只是提供了一段地址空间和内核对象,其运行是通过在其地址空间内的主线程
来体现的。当主线程的进入点函数返回时,进程也就随之结束。这种进程的终止方式是
进程的正常退出,进程中的所有线程资源都能够得到正确的清除。除了这种进程的正常
推出方式外,有时还需要在程序中通过代码来强制结束本进程或其他进程的运行。ExitP
rocess()函数即可在进程中的某个线程中使用,并将立即终止本进程的运行。ExitPro
cess()函数原型为:

VOID ExitProcess(UINT uExitCode);

  其参数uExitCode为进程设置了退出代码。该函数具有强制性,在执行完毕后进程即
已经被结束,因此位于其后的任何代码将不能被执行。虽然ExitProcess()函数可以在
结束进程的同时通知与其相关联的动态链接库,但是由于它的这种执行的强制性,使得E
xitProcess()函数在使用上将存在有安全隐患。例如,如果在程序调用ExitProcess(
)函数之前曾用new操作符申请过一段内存,那么将会由于ExitProcess()函数的强制
性而无法通过delete操作符将其释放,从而造成内存泄漏。有鉴于ExitProcess()函数
的强制性和不安全性,在使用时一定要引起注意。

  ExitProcess()只能强制执行本进程的退出,如果要在一个进程中强制结束其他的
进程就要用TerminateProcess()来实现。与ExitProcess()不同,TerminateProcess
()函数执行后,被终止的进程是不会得到任何关于程序退出的通知的。也就是说,被
终止的进程是无法在结束运行前进行退出前的收尾工作的。所以,通常只有在其他任何
方法都无法迫使进程退出时才会考虑使用TerminateProcess()去强制结束进程的。下
面给出TerminateProcess()的函数原型:

BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);


  参数hProcess和uExitCode分别为进程句柄和退出代码。如果被结束的是本进程,可
以通过GetCurrentProcess()获取到句柄。TerminateProcess()是异步执行的,在调
用返回后并不能确定被终止进程是否已经真的退出,如果调用TerminateProcess()的
进程对此细节关心,可以通过WaitForSingleObject()来等待进程的真正结束。

小结

  多进程是多任务管理中的重要内容,文中上述部分对其基本概念和主要的技术如子
进程的创建与结束、进程间的互斥运行等做了较详细的介绍。通过本文读者应能对多进
程管理有一个初步的认识。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值