《Windows核心编程》读书笔记(一)

    昨天深夜整理了一下Judge的需求和存在的bug,发现自己在Judge这个泥潭里已经越陷越深,很多问题都不是用一种查阅式的阅读所能解决,所以决定仔细的读一读《Windows核心编程》,来深刻领悟一下Windows相关的编程知识。

一:3.2.2 关闭内核对象

如果将一个无效句柄传递给CloseHandle,将会出现两种情况之一。如果进程运行正常,CloseHandle 返回FALSE ,而GetLastError 则返回ERROR_INVALID_HANDLE。如果进程正在排除错误,系统将通知调试程序,以便能排除它的错误。 
这个说明或许可以解释我Judge中现在存在的某个Bug:那个Bug仅在Debug模式下出现。

二:9.1等待函数

下面这个函数Wa i t F o r M u l t i p l e O b j e c t s与Wa i t F o r S i n g l e O b j e c t函数很相似,区别在于它允许调用线程同时查看若干个内核对象的已通知状态:


DWORD WaitForMultipleObjects(DWORD dwCount,
   CONST HANDLE
*  phObjects, 
   BOOL fWaitAll, 
   DWORD dwMilliseconds);
d w C o u n t参数用于指明想要让函数查看的内核对象的数量。这个值必须在1与M A X I M U M _WA I T _ O B J E C T S(在Wi n d o w s头文件中定义为6 
4 )之间。p h O b j e c t s参数是指向内核对象句柄的数组的指针。
可以以两种不同的方式来使用Wa i t F o r M u l t i p l e O b j e c t s函数。一种方式是让线程进入等待状态,直到指定内核对象中的任何一个变为已通知状态。另一种方式是让线程进入等待状态,直到所有指定的内核对象都变为已通知状态。f Wa i tAl l参数告诉该函数,你想要让它使用何种方式。如果为该参数传递T R U E,那么在所有对象变为已通知状态之前,该函数将不允许调用线程运行。

d w M i l l i s e c o n d s参数的作用与它在Wa i t F o r S i n g l e O b j e c t中的作用完全相同。如果在等待的时候规定的时间到了,那么该函数无论如何都会返回。同样,通常为该参数传递I N F I N I T E,但是在编写代码时应该小心,以避免出现死锁情况。

Wa i t F o r M u l t i p l e O b j e c t s函数的返回值告诉调用线程,为什么它会被重新调度。可能的返回值是WA I T _ FA I L E D和WA I T _ T I M E O U T,这两个值的作用是很清楚的。如果为f Wa i tAl l参数传递T R U E,同时所有对象均变为已通知状态,那么返回值是WA I T _ O B J E C T _ 
0 。如果为f Wa i t A l l传递FA L S E,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WA I T _ O B J E C T _ 0与(WA I T _ O B J E C T _  0   +  d w C o u n t  -   1 )之间的一个值。换句话说,如果返回值不是WA I T _ T I M E O U T,也不是WA I T _ FA I L E D,那么应该从返回值中减去WA I T _ O B J E C T _  0 。产生的数字是作为第二个参数传递给Wa i t F o r M u l t i p l e O b j e c t s的句柄数组中的索引。该索引说明哪个对象变为已通知状态。下面是说明这一情况的一些示例代码:


HANDLE h[
3 ];
h[
0 =  hProcess1;
h[
1 =  hProcess2;
h[
2 =  hProcess3;
DWORD dw 
=  WaitForMultipleObjects( 3 , h, FALSE,  5000 );
switch (dw) 
{
   
case WAIT_FAILED:
      
// Bad call to function (invalid handle?)
      break;

   
case WAIT_TIMEOUT:
      
// None of the objects became signaled within 5000 milliseconds.
      break;

   
case WAIT_OBJECT_0 + 0:
      
// The process identified by h[0] (hProcess1) terminated.
      break;

   
case WAIT_OBJECT_0 + 1:
      
// The process identified by h[1] (hProcess2) terminated.
      break;

   
case WAIT_OBJECT_0 + 2:
      
// The process identified by h[2] (hProcess3) terminated.
      break;
}


如果为f Wa i t A l l参数传递FA L S E,Wa i t F o r M u l t i p l e O b j e c t s就从索引0开始向上对句柄数组进行扫描,同时已通知的第一个对象终止等待状态。这可能产生一些你不希望有的结果。例如,通过将3个进程句柄传递给该函数,你的线程就会等待3个子进程终止运行。如果数组中索引为0的进程终止运行,Wa i t F o r M u l t i p l e O b j e c t s就会返回。这时该线程就可以做它需要的任何事情,然后循环反复,等待另一个进程终止运行。如果该线程传递相同的3个句柄,该函数立即再次返回WA I T _ O B J E C T _ 
0 。除非删除已经收到通知的句柄,否则代码就无法正确地运行。

用WaitForMultipleObjects改写我的Judge,使得中止线程时,可以让线程自己中止,而不是由其他线程杀。这样可以解决另一Bug。

三:17.1.2 在可执行文件或DLL的多个实例之间共享静态数据

我可以创建一个称为“S h a r e d”的节,它包含单个L O N G值,如下所示:

#pragma data_seg(
"Shared")
LONG g_lInstanceCount 
= 0;
#pragma data_seg()
当编译器对这个代码进行编译时,它创建一个新节,称为S h a r e d,并将它在编译指示后面看到的所有已经初始化(i n i t i a l i z e d)的数据变量放入这个新节中。在上面这个例子中,变量放入S h a r e d节中。该变量后面的#pragma dataseg()一行告诉编译器停止将已经初始化的变量放入S h a r e d节,并且开始将它们放回到默认数据节中。需要记住的是,编译器只将已经初始化的变量放入新节中。例如,如果我从前面的代码段中删除初始化变量(如下面的代码所示),那么编译器将把该变量放入S h a r e d节以外的节中。

#pragma data_seg(
"Shared")
LONG g_lInstanceCount;
#pragma data_seg()
Microsoft 的Visual C
++编译器提供了一个A l l o c a t e说明符,使你可以将未经初始化的数据放入你希望的任何节中。请看下面的代码:

// Create Shared section & have compiler place initialized data in it.
#pragma data_seg("Shared")

// Initialized, in Shared section
int a = 0;

// Uninitialized, not in Shared section
int b;

// Have compiler stop placing initialized data in Shared section.
#pragma data_seg()

// Initialized, in Shared section
__declspec(allocate("Shared")) int c = 0;

// Uninitialized, in Shared section
__declspec(allocate("Shared")) int d;

// Initialized, not in Shared section
int e = 0;

// Uninitialized, not in Shared section
int f;        
上面的注释清楚地指明了指定的变量将被放入哪一节。若要使A l l o c a t e声明的规则正确地起作用,那么首先必须创建节。如果删除前面这个代码中的第一行#pragma data_seg,上面的代码将不进行编译。
之所以将变量放入它们自己的节中,最常见的原因也许是要在. e x e或D L L文件的多个映像之间共享这些变量。按照默认设置, . e x e或D L L文件的每个映像都有它自己的一组变量。然而,可以将你想在该模块的所有映像之间共享的任何变量组合到它自己的节中去。当给变量分组时,系统并不为. e x e或D L L文件的每个映像创建新实例。

仅仅告诉编译器将某些变量放入它们自己的节中,是不足以实现对这些变量的共享的。还必须告诉链接程序,某个节中的变量是需要加以共享的。若要进行这项操作,可以使用链接程序的命令行上的
/ S E C T I O N开关:


/SECTION:name,attributes
在冒号的后面,放入你想要改变其属性的节的名字。在我们的例子中,我们想要改变S h a r e d节的属性。因此应该创建下面的链接程序开关:

/SECTION:Shared,RWS
在逗号的后面,我们设定了需要的属性。用R代表R E A D ,W代表W E I T E,E代表E X E C U T E,S代表S H A R E D。上面的开关用于指明位于S h a r e d节中的数据是可以读取、写入和共享的数据。如果想要改变多个节的属性,必须多次设定
/ S E C T I O N开关,也就是为你要改变属性的每个节设定一个/ S E C T I O N开关。
也可以使用下面的句法将链接程序开关嵌入你的源代码中:


#pragma comment(linker, 
"/SECTION:Shared,RWS")
这一行代码告诉编译器将上面的字符串嵌入名字为“ . d r e c t v e”的节。当链接程序将所有的. o b j模块组合在一起时,链接程序就要查看每个. o b j模块的“ . d r e c t v e”节,并且规定所有的字符串均作为命令行参数传递给该链接程序。我一直使用这种方法,因为它非常方便。如果将源代码文件移植到一个新项目中,不必记住在Visual C
++的Project Settings(项目设置)对话框中设置链接程序开关。
虽然可以创建共享节,但是,由于两个原因, M i c r o s o f t并不鼓励你使用共享节。第一,用这种方法共享内存有可能破坏系统的安全。第二,共享变量意味着一个应用程序中的错误可能影响另一个应用程序的运行,因为它没有办法防止某个应用程序将数据随机写入一个数据块。


用这个方法可以在两个进程间共享内存数据。

你可能感兴趣的:(《Windows核心编程》读书笔记(一))