控制台应用程序

  • 本篇复习 — 控制台应用程序和长文件名

      在本章中包含的内容有控制台应用程序。它看起来象是 DOS 应用程序,但它可以访问 WIN32 提供的 4GB 地址空间。在本章中你还将看到如何使用长文件名以及 WIN32 文件 I/O 等内容的介绍。
      本章中所有程序都是专门针对 Windows NT 和 Windows 9x 的,它们不能在 16 位模式和 Windows 3.x 下运行。
      本章还包括下列内容:
      ·对面向事件程序设计从头到尾的过程作一个概括,尤其是你还有机会建立你自己的事件循环。
      ·在控制台应用程序中用鼠标作输入。
      ·低级控制台 I/O 例程。
      ·在控制台应用程序中调用 GUI 例程。如果你需要得到一个文件名或者要执行一些由 Windows 界面处理的操作,这些情况下调用 GUI 是非常有用的。
      ·在 GUI 和控制台应用程序中得到和使用长文件名。
      ·在目录中用 FindFirstFile 和 FindNextFile 函数反复扫描长文件名。
      ·在 GUI 应用程序中建立一个控制台窗口,这对调试非常有用。

    8.1 控制台应用程序


      多数人一想到 Windows 9x,脑子中首先想到的是 GUI。Windows 9x 看来比 Windows 3.x 更加面向图形,具有更多的图形元素,用户更依赖这种方式来与系统打交道。
      不过,当你知道 Windows 9x 还完全支持正文模式时可能会有些奇怪。你可以建立 32 位 Windows 9x 应用程序,它完全使用 WIN32 API,但它在正文模式下运行。你可以在命令提示符下启动它们,而且它们总不会离开命令提示符。换句话说,它不必一定要弹出一个窗口。这里我说的是“不必做”,意思是如果它们需要窗口的话,它们也完全能够弹出窗口。事实上,本章中就有一个例子说明了如何在一个控制台应用程序中弹出一个公共对话框(这不是一种必要的推荐,但这是可以实现的)。
      程序清单 8.1 列出了 Emily 程序,它是本章中介绍的几个控制台应用程序中的第一个程序。
    • 程序清单 8.1  Emily 应用程序是一个 WIN32 控制台应用程序
      ///
      // Emily.cpp
      // Copyleft (c) 1999 skyline
      // Demostrate simple console application
      ///
      
      #include &ltwindows.h>
      #include &ltstdio.h>
      #include &ltconio.h>
      
      /
      // Program entry point
      /
      int main(void)
      {
          printf("/n/nThe soul selects her own society,/n");
          printf("Then shuts the door;/n");
          printf("On her devine majority/n");
          printf("Obtrude no more./n/n");
          printf("Unmoved,she notes the chariot's pausing/n");
          printf("At her low gate;/n");
          printf("Unmoved,an emperor is kneeling/n");
          printf("Upon her mat./n/n");
          printf("I've known her from an ample nation/n");
          printf("Choose one;/n");
          printf("Then close the valves of her ayyention/n");
          printf("like stone/n");
          printf("/n-Emily Dickinson/n");
          printf("/n/nPress any key to continue .../n");
      
          _getch();
      
          return 0;
      }
      
      这个程序运行时,会以正文的模式输出下列一首诗:
    •     The soul selects her own society,
          Then shuts the door;
          On her devine majority
          Obtrude no more.
      
          Unmoved,she notes the chariot's pausing
          At her low gate;
          Unmoved,an emperor is kneeling
          Upon her mat.
      
          I've known her from an ample nation
          Choose one;
          Then close the valves of her ayyention
          like stone
      
          -Emily Dickinson
      
      如果你用 Microsoft 的编译器,你可以按如下步骤操作:选择 New Project,然后将 Project Name 设置成 Emily,把 Project Type 设置为 Win32 Console Application。在 Project 中插入 C++ 源文件,最后将你的程序代码输入到刚插入的文件中,编译并运行它。
      把 Emily 程序简化后留下最基本的能说明问题的内容,就剩下如下代码了:
    • #include &ltstdio.h>
      
      int main(void)
      {
          printf("/n/nThe soul selects her own society,/n");
          return 0;
      }
      
      这不是一个程序员的梦吧?在这里,我所要做的一切是要指出,它只是一个传统的 DOS 应用程序。这个程序充分利用了 WINDOWS.H 文件。
      上面所述的内容的要点是:控制台的应用程序的编码可以象 DOS 应用程序一样,你只要把 WINDOWS.H 列在要包含的文件中。

    8.2 构造 4GB 数组


      如果你做过一段时间程序设计工作,你很可能碰到 64KB 限制的问题,这种限制是针对 16 位应用程序,因为你只能用 16 位,就不能表示一个大于 64KB 的数。甚至在 Windows 3.x 中这个讨厌的 64KB 仍然限制着我们。这个环境虽然让我们摆脱了 640KB 的大小限制,但我们仍然感到非常麻烦,在那儿有数据段、栈和堆等乱放在一起。
      不过,控制台应用程序不受 64KB 边界的限制,只要你需要,你可以分配巨大的内存块。当然,没有人能在 32 位系统可以寻址的机器中有整个 4GB 的内存。即使真的有 4GB 的内存,它们也不能真正建立一个 4GB 的数组。因为 4GB 中几乎有一半的空间是属于系统的。无论如何,WIN32 程序与旧的 16 位世界相比,使我们感到非常自由和开放。
      也许从这个新的自由王国获益最多的是数学家和图形程序员。需要构造大数组的程序员也不再受 64KB 的限制了。当然,每一个人都不同程度从中得到好处。至少这意味着大的数据块可以驻留在数据段中而不必放到堆中。
      关于 4GB 的优点已说的够多了,现在我们来看看构造一个大数组的感觉象什么,它不是用某种巧妙的手段得到的,它是真实的事情——大数组在均匀的内存空间中展开。现在我们看一看程序清单 8.2 的大数组程序。
    • 程序清单 8.2  BigArray 程序
      ///
      // BigArray.cpp
      // Copyleft (c) 1999 skyline
      // Demostrate simple console application
      ///
      
      #include &ltwindows.h>
      #include &ltstdio.h>
      #include &ltconio.h>
      
      #define MAXX 20
      #define MAXY 2000
      
      /
      // Program entry point
      /
      int main(void)
      {
          int i,j;
          int BigOne[MAXX][MAXY];
      
          printf("/n/nThe size of the array is %d bytes.",sizeof(BigOne));
      
          printf("/n/nPress any key to continue .../n");
          _getch();
      
          for(j=0;j&ltMAXY;j++)
      	for(i=0;i&ltMAXX;i++)
      	    BigOne[i][j]=i;
      
          for(j=0;j&ltMAXY;j++)
          {
      	printf("Row: %d Data => ",j);
      	for(i=0;i&ltMAXX;i++)
      	    printf("%d ",BigOne[i][j]);
      	printf("/n");
          }
      
          printf("/n/nPress any key to continue .../n");
          _getch();
      
          return 0;
      }
      
      当你运行 BigArray 程序时,你会首先得到一个数组大小的报告。此例中屏幕上的正文显示出这个数组的大小是 160,000 字节。
      当你按下任意键时,整个数组情况会在屏幕上滚动显示出来。不过,用这种方式让你从视觉上对所发生的一切加以确认并没有什么实际用途,这只不过是让你具体地感觉到使用了整个 32 位地址空间。
      不用说,你可以建立比 160,000 字节大的多的数组。我之所以让这个数组相对来说比较小,是因为我想在屏幕上打印出数组的全部内容。
      这里我想我应该指出,我是通过 sizeof 操作符得到数组的大小的:
      printf("/n/nThe size of the array is %d bytes.",sizeof(BigOne));
      在这一节你已看到了 Windows 9x 已经突破了老的 64KB 的限制,这一点曾使许多用 Intel 芯片计算机的程序员受尽约束。但 Windows 9x 的存储模式并不像最初听起来那么简单。
      当我知道我的程序通常被加载到内存的 4MB 地址处(它在十六进制中表示为 0x00400000),我感到很奇怪。在你第一次考虑这个问题时,你也会觉得把程序放在这个地方并不是原来所想象的。而且,你的程序被加载的位置被记录下来作为你的应用程序的 HINSTANCE 的值。如果你继续进行并运行程序,而且把 HINSTANCE 装到调试器的监视窗口中,你看到的数字就是你的程序被加载到虚拟存储空间的地址。

    8.3 构造控制台应用程序


      正如你所看到的那样,屏幕 I/O 的许多基本动作只能在控制台模式下才能正常工作。也就是说,在控制台应用程序中,你必须包含 STDIO.H 和(或)CONIO.H 文件才能使用 printf,gets,getchar,getch 等函数。这些函数让你可以控制在屏幕上输出数据。
      不过,你还应该了解 WIN32 中的几个控制台函数。例如,你可以得到标准输入和标准输出的句柄,然后用 WIN32 本身的命令在屏幕的指定位置用指定的前景色和背景色进行输出。
      在前面介绍的 Mouse 程序中,你已看到还可以知道鼠标的按键动作和用户对键盘的击键动作。同样,这些控制有些是可以从 C 的运行库中得到,但我希望向你说明如何使用 Windows 本身的命令来达到同样的目的,这样你才能理解控制台中发生的实际情况。
      我将让你们看一个 Faith 程序,这个代码中除了在程序的最后用了 getch 例程外,没有使用 C 语言中其他的标准库函数,所有的工作全部用的是 WIN32 本身的调用来实现的。
    • 程序清单 8.3  Faith 程序
      ///
      // Faith.cpp
      // Copyleft (c) 1999 skyline
      // Demostrate simple console application
      ///
      
      #include &ltwindows.h>
      #include &ltstdio.h>
      #include &ltconio.h>
      
      #define ALT1 FOREGROUND_BLUE|FOREGROUND_GREEN
      
      void ClrScr();
      void WriteXY(int x,int y,LPSTR s,WORD Attr);
      
      HANDLE hOut;
      
      /
      // Program entry point
      /
      int main(void)
      {
          SetConsoleTitle("skyline console");
      
          hOut=GetStdHandle(STD_OUTPUT_HANDLE);
      
          ClrScr();
      
          WriteXY(0,1,"Faith is a fine invention.",ALT1);
          WriteXY(0,2,"For gentlemen who see;",ALT1);
          WriteXY(0,3,"But microscopes are prudent",ALT1);
          WriteXY(0,4,"In an energency!",ALT1);
      
          _getch();
      
          return 0;
      }
      
      void ClrScr()
      { 
          DWORD NumWritten;
          COORD c;
      
          c.X=0;
          c.Y=0;
          FillConsoleOutputCharacter(hOut,' ',80*25,c,&NumWritten);
      }
      
      void WriteXY(int x,int y,LPSTR s,WORD Attr)
      {
          COORD c;
          DWORD result;
      
          c.X=x;
          c.Y=y;
      
          SetConsoleTextAttribute(hOut,ALT1);
          SetConsoleCursorPosition(hOut,c);
          WriteConsole(hOut,s,strlen(s),&result,NULL);
      }
      
      上面的程序中没有下面这两行代码,但你可以把它们加到程序中,用来分配它自己的控制台:
      FreeConsole();
      AllocConsole();
      这样,程序用第一个调用释放当前控制台,结束了它的命令对话期。然后用第二个调用分配了一个新的控制台,就启用一个它自己的窗口。大多情况下可不必这样做,这只会让用户糊涂。如果你要在标准 Windows 程序内部启动一个控制台,调用 AllocConsole 函数通常是有用的。
      为了给控制台设置一个标题,Faith 程序中用了下列 API 函数:
      SetConsoleTitle("skyline console");
      如果程序是作为 Windows 桌面的子窗口来运行,这个标题就会出现,不过它不是出现在控制台中的某个地方而是在 Windows GUI 提供的标题栏中。
      下一步是要取得输出句柄,程序输出到该句柄代表的输出地点:
      hOut=GetStdHandle(STD_OUTPUT_HANDLE);
      hOut 说明为 HANDLE 类型。如果你要得到输入句柄,可以用下列一行代码:
      hOut=GetStdHandle(STD_INPUT_HANDLE);
      Faith 程序希望取得对整个屏幕的控制。也就是说它可以在屏幕上随意移动光标,所以它要将屏幕置为空白:
    • void ClrScr()
      { 
          DWORD NumWritten;
          COORD c;
      
          c.X=0;
          c.Y=0;
          FillConsoleOutputCharacter(hOut,' ',80*25,c,&NumWritten);
      }
      
      这个例程和许多 C 运行库中见到的 ClrScr 有相同的功能。

      FillConsoleOutputCharacter

      语法:
      FillConsoleOutputCharacter函数定义如下:
      BOOL FillConsoleOutputCharacter(
        HANDLE hConsoleOutput,	
        CHAR  cCharacter,
        DWORD  nLength,
        COORD  dwWriteCoord,
        LPDWORD lpNumberOfCharsWritten
        );
    
      FillConsoleOutputCharacter 函数把标准输出句柄作为第一个参数,要输出的字符放在第二个参数中。
      输出字符的次数放在第三个参数中。最后一个参数用来存放所写的字节数,并作为函数的返回值。
      如果你写的字符个数超过一行,输出会转到下一行继续进行。如果你试图写的字符数超出了缓冲区范围,什么事也不会发生,也不会有额外的调用来处理这些多出的字符。这样,如果你用 80x25 格式的代码写程序就很安全。如果你要计算用来刷新整个屏幕字符的确切数目,可以使用 GetConsoleScreenBufferInfo 函数。
      你应注意到这里还有一个 FillConsoleOutputAttribute 例程,它将在本章后面说明。它可以让你为屏幕区域设置前景和背景色,就象 FillConsoleOutputCharacter 可以让你在屏幕的一个指定区域中显示字符一样。
      下面是一个例子:
      FillConsoleOutputCharacter(hOut,' ',80*25,c,&NumWritten);
      如果你想在屏幕上输出正文,你可以用 WriteConsole,SetConsoleTextAttribute 和 SetConsoleCursorPosition 函数:
    • void WriteXY(int x,int y,LPSTR s,WORD Attr)
      {
          COORD c;
          DWORD result;
      
          c.X=x;
          c.Y=y;
      
          SetConsoleTextAttribute(hOut,ALT1);
          SetConsoleCursorPosition(hOut,c);
          WriteConsole(hOut,s,strlen(s),&result,NULL);
      }
      
      在这个例程中我用了三个函数共同来完成在屏幕上用一组特定颜色,在特定位置上输出正文的任务,当然你也可以只用 WriteConsole 函数就可以做到这些。
      SetConsoleTextAttribute 把标准输出句柄作为第一个参数。第二个参数是属性,属性可以为下列值中的一个或多个组成:FOREGROUND_BLUE,FOREGROUND_GREEN,FOREGROUND_RED,FOREGROUND_INTENSITY,BACKGROUND_BLUE,BACKGROUND_GREEN,BACKGROUND_RED 和 BACKGROUND_INTENSITY。对这些值作不同的组合就可以产生不同的效果。
      SetConsoleCursorPosition 函数依赖下列数据结构:
      typedef struct_COORD{
        SHORT X;
        SHORT Y;
      }COORD;
      显然,这个结构只是 POINT 结构的一种变体,标准的 Windows GUI 应用程序中经常用到 POINT 结构。你可以把这个结构传给 SetConsoleCursorPosition 函数的第二个参数,把控制台缓冲区的句柄作为第一个参数。
      下面是有关 WriteConsole 例程的说明,它是 WIN32 本身的例程,可以用来代替 puts 函数。

      WriteConsole

      语法:
      下面是 WriteConsole 函数的说明:
    • BOOL WriteConsole(
          HANDLE hConsoleOutput,
          CONST VOID *lpBuffer,
          DWORD nNumberOfCharsToWrite,
          LPDWORD lpNumberOfCharsWritten,
          LPVOID lpReserved
          );
      
      WriteConsole 函数用来把一个字符串写到屏幕上。传给它的第一个参数是控制台屏幕缓冲区的句柄;字符串为第二个参数;要写的字符个数作为第三个参数。已写入的字符个数的地址返回时放在第四个参数中,最后一个参数保留给 Windows 或以后另有用途。
      下面是一个例子:
      WriteConsole(hOut,"hello",5,&result,NULL);
      我想再次指出,这里我只是对控制台应用程序作一个简单的介绍,如果对这个问题要作进一步了解,可以打开 Wincon.h 文件来阅读。

    8.4 处理鼠标和键盘


      本节即将介绍的 Mouse 程序会让你了解到在控制台应用程序中如何处理鼠标和键盘的输入。这是一个简单的程序,它能报告鼠标当前的位置,鼠标键当前的状态以及用户按下的任何字母数字键。
      我想强调一下,在以命令行为基础的控制台应用程序中,C 的标准库中大多数 I/O 操作同样可以把这些事情做的很好。例如,可以调用 gets 函数来处理用户的正文输入。但是如果你想追踪鼠标,你当然要用到控制台内部例程。不仅如此,如果你从一个 GUI 应用程序中启动一个控制台窗口,某些标准的 I/O 库例程的动作会有问题。如果你想在 GUI 应用程序中运行一个控制台,你应该用我在此处介绍的几个 WIN32 自身的例程。
      在本章的前面章节中讨论了面向事件的程序设计,此处的程序是一个如何从头开始创建一个面向事件程序的例子。也就是说,这个代码说明了面向事件 Windows 例程内部是如何实际工作的。如果你以前没有见过这类代码,它能给你很大帮助,因为它对于面向消息的操作系统是如何构造的问题给出某些提示。
      现在让我们准备好 Mouse 程序并运行它。程序清单 8.4 是它的源代码。以下几小节将对这个简单的应用程序的要点展开讨论。
    • 程序清单 8.4  Mouse 程序说明如何在控制台应用程序中使用鼠标
      ///
      // Mouse.cpp
      // Copyleft (c) 1999 skyline
      // Demostrate simple console application
      ///
      
      #include &ltwindows.h>
      #include &ltstdio.h>
      
      void GotoXY(SHORT x,SHORT y);
      void BlankLine(SHORT y);
      void ClrScr();
      void Write(LPSTR s);
      void HandleMouse(MOUSE_EVENT_RECORD Mouse);
      void HandleKey(KEY_EVENT_RECORD key);
      void SayGoodBye();
      
      HANDLE hOut,hIn;
      
      /
      // Program entry point
      /
      int main(void)
      {
          SetConsoleTitle("skyline console");
      
          DWORD Result;
          INPUT_RECORD Buf;
      
          hOut=GetStdHandle(STD_OUTPUT_HANDLE);
          hIn=GetStdHandle(STD_INPUT_HANDLE);
      
          ClrScr();
      
          GotoXY(0,24);
          Write("Move mouse,press keys,double click to Exit ...");
      
          do
          {
              ReadConsoleInput(hIn,&Buf,1,&Result);
      
              if(Buf.EventType==MOUSE_EVENT)
                  HandleMouse(Buf.Event.MouseEvent);
      
              if(Buf.EventType==KEY_EVENT)
                  HandleKey(Buf.Event.KeyEvent);
          }while(!(Buf.EventType==MOUSE_EVENT&&
                   Buf.Event.MouseEvent.dwEventFlags==DOUBLE_CLICK));
      
          SayGoodBye();
          
          return 0;
      }
      
      //
      // GotoXY Move cursor to Col,Row
      //
      void GotoXY(SHORT x,SHORT y)
      {
          COORD c;
      
          c.X=x;
          c.Y=y;
      
          SetConsoleCursorPosition(hOut,c);
      }
      
      //
      // Blank a line of text at position y
      //
      void BlankLine(SHORT y)
      {
          DWORD NumWritten;
          COORD c;
      
          c.X=0;
          c.Y=y;
          FillConsoleOutputCharacter(hOut,' ',80,c,&NumWritten);
      }
      
      //
      // Clear the screen
      //
      void ClrScr()
      {
          SHORT i;
      
          for(i=0;i<25;i++) BlankLine(i); GotoXY(0,0); } // // Write a line of text // void Write(LPSTR s) { DWORD result; WriteConsole(hOut,s,strlen(s),&result,NULL); } // // Report on position of mouse // void HandleMouse(MOUSE_EVENT_RECORD Mouse) { char s[150]; sprintf(s,"Buttons: %lu,X: %2lu Y: %2lu/n", Mouse.dwButtonState, Mouse.dwMousePosition.X, Mouse.dwMousePosition.Y); GotoXY(0,0); Write(s); } // // Report on key strokes entered // void HandleKey(KEY_EVENT_RECORD key) { char s[100]; sprintf(s,"You pressed: %c",key.uChar); GotoXY(0,1); Write(s); sprintf(s,"Virtual key: %3d",key.wVirtualKeyCode); GotoXY(0,2); Write(s); } // // Input a string,process it,spit it back out. // void SayGoodBye() { char s[100]; ClrScr(); printf("Enter your name:"); gets(s); printf("/nGoodBye %s!/n",s); } 
      这个程序只做追踪鼠标的当前位置,报告用户按键动作这些事。它的核心代码是下列事件循环:
        do
        {
            ReadConsoleInput(hIn,&Buf,1,&Result);
    
            if(Buf.EventType==MOUSE_EVENT)
                HandleMouse(Buf.Event.MouseEvent);
    
            if(Buf.EventType==KEY_EVENT)
                HandleKey(Buf.Event.KeyEvent);
        }while(!(Buf.EventType==MOUSE_EVENT&&
                 Buf.Event.MouseEvent.dwEventFlags==DOUBLE_CLICK));
    
      这段代码反复循环运行直到用户用鼠标双击屏幕为止。每次循环开始都用 ReadConsoleInput 例程来检查用户的输入。

      语法:
      ReadConsoleInput
        BOOL ReadConsoleInput(
    	HANDLE hConsoleInput,
    	PINPUT_RECORD lpBuffer,
    	DWORD nLength,
    	LPDWORD lpNumberOfEventsRead
    	);
    
      这个例程的许多参数和本章前面讨论过的例程是相同的,因此这里不必再重复说明。
      ReadConsoleInput 例程中新出现的信息是 INPUT_RECORD 类型的缓冲区:
    • typedef struct _INPUT_RECORD {
          WORD EventType;
          union {
              KEY_EVENT_RECORD KeyEvent;
              MOUSE_EVENT_RECORD MouseEvent;
              WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
              MENU_EVENT_RECORD MenuEvent;
              FOCUS_EVENT_RECORD FocusEvent;
          } Event;
      } INPUT_RECORD;
      
      这个记录中包含了对键盘事件、鼠标事件、缓冲区大小的改变、菜单事件以及当前焦点事件等的追踪信息。比如,当用户移动了鼠标或用键盘做一些输入,这些动作的结果会被记录在这个结构中。下面我将讨论 MOUSE_EVENT_RECOND 和 KEY_EVENT_RECOND,如果你对其他结构感兴趣,可以参阅联机帮助信息。后面还将讨论 EventType 域。
      对于 ReadConsoleInput 例程你要掌握的基本思想是:它让系统把程序当前的状态信息填入到 INPUT_RECORD 结构中。这个例程并不等待用户输入信息,它只是简单地检查一下当前发生了什么事,如果没有什么事它就返回并把情况向你报告。
      下面是一个使用该函数的例子:
      ReadConsoleInput(hIn,&Buf,1,&Result);
      你应当花些时间仔细检测这个事件循环,并了解 ReadConsoleInput 函数是如何搭配这个循环的。关键的是你要理解,在 Windows 内部底层的某个地方,也有一个与此循环类似的循环正在执行着,所有面向事件的程序都是按这种方式组织的。
      在调用 ReadConsloeInput 函数后,下一步就是检查是否有某种重要事件已经发生。例如,要想了解是否有鼠标事件出现或键盘上是否有任何一个键被按下。对这些事件的检查是通过查看 INPUT_RECORD 记录中 EventType 字段来实现的:
        if(Buf.EventType==MOUSE_EVENT)
            HandleMouse(Buf.Event.MouseEvent);
    
        if(Buf.EventType==KEY_EVENT)
            HandleKey(Buf.Event.KeyEvent);
    
      如果出现鼠标事件,就调用下列例程:
    • void HandleMouse(MOUSE_EVENT_RECORD Mouse)
      {
          char s[150];
      
          sprintf(s,"Buttons: %lu,X: %2lu Y: %2lu/n",
                  Mouse.dwButtonState,
                  Mouse.dwMousePosition.X,
                  Mouse.dwMousePosition.Y);
          GotoXY(0,0);
          Write(s);
      }
      
      鼠标事件的记录为:
        typedef struct _MOUSE_EVENT_RECORD {
    	COORD dwMousePosition;
    	DWORD dwButtonState;
    	DWORD dwControlKeyState;
    	DWORD dwEventFlags;
        } MOUSE_EVENT_RECORD;
    
      这里关键的字段是前两个。当前的鼠标位置放置在第一个字段,鼠标按键按下的情况记录在第二字段。在大多数情况下,你可以假定按鼠标左键时按键状态置为 1,按鼠标第二个按键时置为 2。不过,要想确切地读出鼠标状态字段的信息来了解发生了什么事件,需要执行一些位操作。也就是说,每个鼠标按键用这个 32 位字段中的一位来代表。这也意味着该例程能够处理可以想象的设备,它们可以有多于二个或三个的按键。你可以通过联机帮助来了解有关的解释。
      第三个字段 dwControlKeyState 用来记录是否有特殊的键被按下。例如,它可以用来追踪 Ctrl 键,Alt 键和 Shift 键等。
      最后一个字段记录是否有双击鼠标事件发生或鼠标是否做了简单地移动。为了帮助阅读,定义了下列值:
      MOUSE_MOVED
      DOUBLE_CLICK
      单击按键用 0 表示。
      理解了这个记录也就能了解 HandleMouse 函数是怎么工作的,这个函数只是简单报告这个记录前两个字段的状态,把它们的值写在屏幕顶部。
      围绕 HandleMouse 例程的代码是面向事件程序的主要部分。在 Mouse 程序主要执行时间中,以极快的速度一遍遍执行循环。在循环中每一次检查到有事件发生时,就调用相应的例程。在此处的例子中,这些例程只是把有关事件的数据在屏幕上输出,在 Windows 内部没有向窗口发送任何东西,而是把消息打包并发送出去。你的程序捕获到 WM_MOUSEMOVE、WM_KEYDOWN、WM_LBUTTONDOWN 和其它消息并作出响应。当你对这些了解了你就会觉得很简单了。
      处理键盘输入事件比处理鼠标输入更简单:
    void HandleKey(KEY_EVENT_RECORD key)
    {
        char s[100];
    
        sprintf(s,"You pressed: %c",key.uChar);
        GotoXY(0,1);
        Write(s);
        sprintf(s,"Virtual key: %3d",key.wVirtualKeyCode);
        GotoXY(0,2);
        Write(s);
    }
    
      其中 KEY_EVENT_RECORD 记录的结构如下:
    typedef struct _KEY_EVENT_RECORD {
        BOOL bKeyDown;
        WORD wRepeatCount;
        WORD wVirtualKeyCode;
        WORD wVirtualScanCode;
        union {
            WCHAR UnicodeChar;
            CHAR   AsciiChar;
        } uChar;
        DWORD dwControlKeyState;
    } KEY_EVENT_RECORD;
    
      其中最重要的字段是 wVirtualKeyCode 和 uChar。虚键的编码已在前面介绍过并且在联机帮助的 Virtual Key Codes 标题下列出。它有助于你追踪那些与特殊键相关的信息。这些特殊键有 Enter 键,功能键(F1,F2)和小键盘上的数字键等等。
      只要可能,就能把这些特殊键的代码转换成标准的 ASCII 字符并放在 uChar 字段。这就是说,如果这个键是字母数字值或 ASCII 字符集中前半部分的符号,就把它放入 uChar 字段。例如,对于 A 键,wVirtualKeyCode 字段的值将是 65,而 uChar 字段中放的字符将是 A 或 a,这将依据 Shift 键的状态而定。

    8.5 长文件名


      在本章的前面我已介绍了如何按块(Chunk)来分配内存,块的大小可超过 64KB。很多程序员多年来就期待着在 PC 机上能具有这种功能。现在,这个愿望终于实现了。
      对于长文件名的情况也是如此。尽管长文件名的实现已拖了多久,但它终于出现了。你不用做任何特别的事情就可以得到这种十分期待的功能。当你开始使用有关的函数,这些函数中有许多你可能已经用了多年,你会发现你可以用长文件名。在长文件名中,你甚至用不着包含一个特殊的标记或定义一些特殊的值。
      下面有一个这样的例子。如果你安装了 Windows 9x 并有一个 32 位编译程序,没有任何宣传也没有任何警告,你会突然发现,可以在 fopen 函数的第一个参数中用长文件名。这样你就可以告别下列这类代码:
      fp=fopen("LL071595.txt","w+");
      而欢迎现在这样的代码了:
      fp=foepn("Love letter to Margie dated 07-15-95.txt","w+");
      这是一个非常美妙的改进,当然我们大家对这一天的到来感到高兴。无论如何,用一个叫做 LL071595.TXT 的文件名是很难感到它有什么浪漫的气息的。
      不过,你应该意识到,系统在实现长文件名的细节上仍有一些限制。例如你不能这样写:
      fp=foepn("Letter to Dad dated 07/15/95.txt","w+");
      对这个特殊的限制你应当不会感到奇怪,因为在路径中,斜杠符已有一个更基本的含义,因此不能用来当作日期的分割符。此外,对长文件名还有一些可以做什么、不可以做什么的实际规定。这意味着你要提醒你自己,在写例程时,路径名中不要含有无效的字符。你可以参阅由 FileOpen 和 FileSave 公共对话框中得到的帮助信息。
      新的长文件名系统规定文件名可以用下列字符:
      标准的英文字母。
      标准的数字。
      标准的三个字母构成的扩展名。
      其值大于 127 的 ASCII 代码(不推荐这种用法)。
      空格字符(ASCII 值为 20h)。
      你还可以用下面这些特殊的字符:
      $ % ' _ @ ! ( ) { } ^ # & " + , ; = [ ]
      文件名可以长达 256 个字符。文件名前的路径名可以长达 246 个字符(这是 256 减去 DOS 8.3 名字的长度)。这些是对你在建立非常长的目录名,尤其是建立嵌套子目录时的限定。
      长文件名中也可以辨别你使用的字母,也就是说它能区分大写 A 和小写 a 之间的不同。但它不用来将一个文件和另一个文件区分开来。例如,有一个文件名叫 My File.txt,然后你又想在同一个目录下建立一个名为 My file.txt 的文件,系统将把它们看作是相同的文件名,后建立的文件就把前一个文件覆盖了,但保留前一个文件的名字。也就是说,文件的内容已经变了但文件名还保留为 My File.txt。
      如果在建立一个长文件名后,你转到命令提示符下,你会看到象下面这样的奇怪的内容:
    • acreat~1.txt      33   7-15-95  13:23
      anfope~1.txt     721   7-15-95  13:05
      anfope~2.txt     721   7-15-95  13:23
      longname.cpp    3077   7-15-95  13:23
      longname.def      78   7-14-95  12:28
      longname.exe   46340   7-15-95  13:23
      longname.mak     622   7-14-95  12:27
      
      最后四个名字是完全可以理解的。那么前三个名字是什么?这些是创造出的长文件名的别名,这是为了让这些长文件名的文件也可以在旧的 DOS 文件系统下工作。
      前面三个文件的化名可以假定是下面三个文件名字:
      A CreateFile long name.txt
      An fopen long name 05-23-95.txt
      An fopen long name 07-17-95.txt
      一旦你接受了这种基本的化名方法,就对 Windows 如何把下面的名字:
      A CreateFile long name.txt
      改成这样的名字:
      acreat~1.txt
      注意,下面两个长文件名直到第八个字符甚至超过第八个字符都是一样的。这意味着 Windows 不能简单地截短文件名,这是行不通的。
      系统为了避免这种不合适的做法采取了简单的策略,即在截短的文件名后面加上一个数字,这样第一个文件名最后加一个 1,第二个文件名最后加一个 2,如此类推。研究下面的例子就可理解系统的工作方式了。下面是这两个长文件名:
      An fopen long name 05-23-95.txt
      An fopen long name 07-17-95.txt
      相应截短后的文件名为:
      anfope~1.txt 721 7-15-95 13:05
      anfope~2.txt 721 7-15-95 13:23
      注意,长文件名中的空格符去掉了。在长文件名系统中空格是合法的字符,但 DOS 文件系统对此处理的不是太好。
      不过问题在于你不能猜到在长文件名出现的地方系统将用什么样的短名字。我很了解人们都希望能开发出在大多情况都能正常工作的系统,但有关长文件名的问题还没有文档化,在未来的系统中还可能会有变化,它还没有固定下来。
      但是,如果你真的想深入探讨这个问题,你应该注意到下面这些字符在长文件名中是合法的,但是在 DOS 提示符下使用的别名中是不合法的。这些字符是:加号(+),逗号(,),分号(;),等号(=)以及开括号和闭括号([])。
      我不推荐使用这些符号,但如果你需要用它们是可以的。
      现在让我们看看一个长文件名工作的实例。我在前面已经说过,有关长文件名这个题材实际上没有多少内容可说。长文件名自然而就出现了。我在这里给出的代码只不过是提供一点事实给你看。程序清单 8.5 是这个实例。
    • 程序清单 8.5  LongName 显示了在 Windows 应用程序中如何使用长文件名
      //
      // LongName.cpp
      // Copyleft 1999 by skyline
      // How to create long file names,plus some file IO
      //
      
      #include &ltwindows.h>
      #include &ltstdio.h>
      #include &ltconio.h>
      
      // Macros
      
      #define OPEN_ERROR    1000
      #define WRITE_ERROR   1001
      #define CLOSE_ERROR   1002
      #define FILE_SUCCESS  1003
      
      // Funcs
      void Report(int OutCome,LPSTR s);
      int  FOpenMethod(void);
      BOOL CreateFileMethod(void);
      void ShowFiles(void);
      
      
      // Program enrty point
      
      int main(void)
      {
          Report(FOpenMethod(),"File 1");
      
          Report(CreateFileMethod(),"File 2");
      
          ShowFiles();
      
          printf("/nPress any key to continue ...");
          _getch();
      
          return 0;
      }
      
      
      // Report out come of file operation
      
      void Report(int OutCome,LPSTR s)
      {
          switch(OutCome)
          {
      	case OPEN_ERROR:
      	    printf("Error opening %s/n",s);
      	    break;
      	case WRITE_ERROR:
      	    printf("Error writing %s/n",s);
      	    break;
      	case CLOSE_ERROR:
      	    printf("Error closing %s/n",s);
      	    break;
      	case FILE_SUCCESS:
      	    printf("Success: %s/n",s);
      	    break;
          }
      }
      
      
      // Use fopen,etc,to open files
      
      int FOpenMethod(void)
      {
          FILE *fp;
          int Num;
      
          fp=fopen("An fopen long name.txt","w+");
          if(fp==NULL)
      	return OPEN_ERROR;
      
          for(Num=0;Num<43;num++) fprintf(fp,"InforMation: %d/n",Num); if(fclose(fp)!="0)" return CLOSE_ERROR; return FILE_SUCCESS; }  // Use CreateFile,etc,to open files  BOOL CreateFileMethod(void) { DWORD NumWritten; char * s="A little data for the second file" ; HANDLE hFile="CreateFile("A" CreateFile long name.txt", GENERIC_WRITE,0,NULL,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL); if(hFile="=INVALID_HANDLE_VALUE)" return OPEN_ERROR; if(!WriteFile(hFile,s,strlen(s),&NumWritten,NULL)) return WRITE_ERROR; if(!CloseHandle(hFile)) return CLOSE_ERROR; return FILE_SUCCESS; }  // Show all the files in the current directory  void ShowFiles(void) { WIN32_FIND_DATA FindData; HANDLE Data; printf("/nDirectory Output:/n"); Data="FindFirstFile("*.*",&FindData);" while(Data!="INVALID_HANDLE_VALUE)" { printf("%s/n",FindData.cFileName); if(!FindNextFile(Data,&FindData)) break; } FindClose(Data); } 
      这个程序的输出如下。这个结果有些依赖于你的系统的目录结构的具体处理方式:
    • Success: File 1
      Success: File 2
      
      Directory Output:
      .
      ..
      LongName.ncb
      LongName.opt
      LongName.dsw
      LongName.dsp
      LongName.cpp
      LongName.plg
      Debug
      An fopen long name.txt
      A CreateFile long name.txt
      
      Press any key to continue ...
      
      前两行报告程序的文件操作是否成功:
      Success: File 1
      Success: File 2
      如果有某些错误发生,那么此处出现的就不是成功信息而是出错信息了。这些出错信息可能是由 LongName 程序本身产生的。从 LongName.cpp 的 Report 例程可了解进一步信息。
      这个程序的核心是建立下列两个文件:
      An fopen long name.txt
      A CreateFile long name.txt
      它使用 WIN32 自身的 CreateFile API 来建立第一个文件,用标准的 C 库中的 fopen 例程来建立第二个文件。显然,这个程序的主要目的就是建立长文件名的文件。
      让人感兴趣的关键问题是 CreateFileMethod 例程:
    • BOOL CreateFileMethod(void)
      {
          DWORD NumWritten;
      
          char * s ="A little data for the second file";
      
          HANDLE hFile=CreateFile("A CreateFile long name.txt",
      			GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
      			FILE_ATTRIBUTE_NORMAL,NULL);
      
          if(hFile==INVALID_HANDLE_VALUE)
      	return OPEN_ERROR;
      
          if(!WriteFile(hFile,s,strlen(s),&NumWritten,NULL))
      	return WRITE_ERROR;
      
          if(!CloseHandle(hFile))
      	return CLOSE_ERROR;
      
          return FILE_SUCCESS;
      }
      
      这个例程中用了 WIN32 API 中的三个函数 CreateFile、WriteFile 和 CloseHandle。这些函数是 Windows 9x 的基本文件 I/O 例程,它们用来代替过时的 Windows API 的 _lOpen,_lclose 等例程。
      你会发现,在 CreateFile 例程中,取得一个句柄是有价值的。因为它不仅可用来打开标准的磁盘文件,也可以用来打开命名管道、通信资源和磁盘设备,还可以用于控制台。当然,在这个地方使用标准的 C 库例程也可以,它们中许多例程会映射成这个函数,而这才是 Windows 9x 自身的文件 I/O 的执行方式。
      语法:
      CreateFile
       HANDLE  CreateFile(
       LPCSTR lpFileName,            // FileName
       DWORD dwDesiredAccess,        // read_write access
       DWORD dwShareMode,            // can other apps read it?
       LPVOID lpSecurityAttributes,  // Security
       DWORD dwCreationDisposition,  // overwrite,truncate,etc
       DWORD dwFlagsAndAttributes,   // hidden,system,etc
       HANDLE hTemplateFile          // file with attributes to copy
      );
    
      这是打开和关闭文件的标准 Windows 例程。你不但可以使用这个函数来创建一个文件,还可以用它来关闭一个已经存在的文件。如上面所说的那样,它还可以用于命名管道、通信工具、存储设备和控制台。
      在整个 Windows API 中,这个函数是最复杂的调用之一。我这样说并不是表明这个函数必然很难使用,而是说要传给它许许多多的标志。如果你想要对这个例程做全面的了解,可以看联机帮助信息。在这里我只是给出足够的信息使你能够用这个例程来打开和关闭标准的磁盘文件。

      第一个参数很简单,这是指向一个文件名的指针。第二个参数可以取下列值:
      0:                  询问设备而不是实际访问设备。
      GENERIC_READ:       你可以读文件和移动文件指针。
      GENERIC_WRITE:      你可以写文件和移动文件指针。
    
      第三个参数 dwShareMode 可取下列值:
      0:                  不允许文件共享。
      FILE_SHARE_READ:    其他人可以打开文件来读文件。
      FILE_SHARE_WRITE:   其他人可以打开文件来写文件。
    
      安全性问题一般只和 Windows NT 有关,所以 Windows 9x 程序员可以将 lpSecurityAttributes 参数置为 NULL。

      参数 dwCreationDisposition 可以置下列的值:
        CREATE_NEW                如果指定的文件已存在则失败。
        CREATE_ALWAYS             如果文件已存在则重写此文件。
        OPEN_EXISTING             如果文件不存在则本函数失败。
        OPEN_ALWAYS               如果文件不存在则创建此文件。
        TRUNCATE_EXISTING         文件截短成 0 字节(与 GENERIC_WRITE 一起使用)。
    
      参数 dwFlagsAndAttributes 可以置下列的值:
        FILE_ATTRIBUTE_READONLY   只读文件。
        FILE_ATTRIBUTE_HIDDEN     隐藏文件。
        FILE_ATTRIBUTE_SYSTEM     只能被操作系统使用。
        FILE_ATTRIBUTE_DIRECTORY  目录。
        FILE_ATTRIBUTE_ARCHIVE    档案文件(已被修改)。
        FILE_ATTRIBUTE_NORMAL     没有属性,只有在使用时有效。
    
      还有很多可能的属性可以用于 CreateFile 函数。但对于标准的文件操作,这里列出的属性是你需要了解的全部属性。
      在学习了控制台例程后,现在学习 WriteFile 函数就没有什么问题了。当然,这个函数是用来把数据写到文件中。这个函数定义如下:
      BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer,
    		DWORD nNumberOfBytesToWrite,
    		LPDWORD lpNumberOfBytesWritten,
    		LPVOID lpOverlapped);
    
      例如,在 LongName 程序中就用下面这行代码把一行简单字符串写到文件中:
      WriteFile(hFile,s,strlen(s),&NumWritten,NULL)
      其中,第一个参数是由 CreateFile 函数返回的文件句柄。第二个参数是你要写的字符串,第三个参数是字符串的长度。第四个参数是写入的字节数。当向命名管道、插口(Socket)、邮件插口(mail slot)写入时,第五个参数应采用异步 I/O 方式。
      当你要关闭一个文件时,你所要做的只是把它的句柄传给 CloseHandle 函数:
      CloseHandle(hFile);
      和 WriteFile 函数一样,CloseHandle 函数也是一个 Boolean 例程,当它成功完成时返回 TRUE。
      如果你想从磁盘上读一个长文件,最好的办法是使用一个标准的公共对话框。如果你想通过应用程序来做这件事,可以使用 FindFirstFile,FindNetFile 和 FindClose 函数:
    • void ShowFiles(void)
      {
          WIN32_FIND_DATA FindData;
          HANDLE Data;
      
          printf("/nDirectory Output:/n");
      
          Data=FindFirstFile("*.*",&FindData);
          while(Data!=INVALID_HANDLE_VALUE)
          {
      	printf("%s/n",FindData.cFileName);
      	if(!FindNextFile(Data,&FindData))
      	    break;
          }
          FindClose(Data);
      }
      
      FindFirstFile 使用两个参数。第一个参数是用掩码描述的你要找的文件类型。例如,你可以把“ *.c”或“ab*.txt”传给这个参数。如果找到这类文件,第二个参数包含一个填好的 FindData 结构:
    • typedef struct _WIN32_FIND_DATA {          // wfd
          DWORD       dwFileAttributes;          // Attributes
          FILETIME    ftCreationTime;            // Creation time
          FILETIME    ftLastAccessTime;          // Last opened/accessed
          FILETIME    ftLastWriteTime;           // Time file was last changed
          DWORD       nFileSizeHigh;             // Zero,unless very big
          DWORD       nFileSizeLow;              // The size of the file
          DWORD       dwReserved0;               // Reserved for future use
          DWORD       dwReserved1;               // Reserved for future use
          CHAR        cFileName[ MAX_PATH ];     // Long File Name
          CHAR        cAlternateFileName[ 16 ];  // The 8.3 alias
      } WIN32_FIND_DATA;
      
      这个结构所包含的内容比你对一个文件想知道的东西还多。不过,如果你们想知道的不仅仅是文件最后修改的时间,还想知道它是什么时候创建的和最后一次访问的时间,那么这些信息都是有用的。
      无论如何,当你得到一个 WIN32_FIND_DATA 数据结构的内容后,你会看到,CFileName 字段可以用来存放从一个目录中检索出来的长文件名。文件的长度记录在 nFileSizeLow 字段中。如果文件太长的话,表示文件长度的数字的高位部分放在 nFileSizeHigh 字段中。
      你可以用这个函数寻找目录、隐藏文件和其他秘密。只要检查第一个字段中的属性就可以知道返回的文件类型。这里使用的属性和以前在 CreateFile 函数描述的属性是一样的。
      如果调用 FindFirstFile 函数成功,由函数返回的句柄是不等于 INVALID_HANDLE_VALUE 这个值的。当你成功地找到一个文件,你可能还想找找是否有其它的文件,其属性是你输入的掩码的属性。如果希望找到这些其它的文件,可以使用 FindNextFile 例程:
      FindNextFile(Data,&FindData);
      FindNextFile 函数的第一个参数是由 FindFirstFile 返回的句柄,第二个参数是 WIN32_FIND_DATA 结构。如果函数成功则返回 TRUE,失败则返回 FALSE。
      你可能注意到,在 LongName 程序中 while 循环一直运行到 FindNextFile 函数返回 FALSE 为止。当然也可以用其它办法来构造这种“寻找第一个”的的循环,但这个程序是工作很好的。
      当反复选代工作进行到把一个目录中所有的文件都找出来后,应该调用 FindClose 来把这个过程终结。它将关闭用来操作的句柄。这种需要是源于多任务操作系统,在这种系统中会有多个程序同时调用 FindFirstFile 函数。这就意味着操作系统要运行两个类似的 FindFirstFile 循环。这类处理的背后是通过使用句柄来实现的。

    8.6 控制台和 GUI


      下面将要介绍的一个程序包含两个内容:
      ·如何从 Windows 的 GUI 中访问长文件名。
      ·如何把一个 GUI 对话框加到控制台应用程序中。
      这听起来复杂但实现却很容易。真正难做的事是弹出一个公用对话框的动作。在本节中只对弹出一个公用对话框的基本原理做一个简单的介绍。
      下面给出的 ConDlg 程序的功能是简单地弹出一个 Windows 公用对话框,这个对话框就是你从 BCW、MSVC、NotePad、WordPad 以及无数其它应用程序中选择 File/Open 时所见到的那类对话框。不过,让人感兴趣的是,这里不是在一个标准的 Windows 应用程序中做这件事,而是在一个控制台应用程序中弹出对话框。当然,这一切听起来很新奇,但在一个控制台应用程序中和在一个 GUI 程序中,创建一个公用对话框并没有什么不同。事实上,这里所看到的代码可以剪贴到一个标准的 Windows 应用程序中。
      程序清单 8.6 是 ConDlg 的源代码。“ConDlg”代表“Console Dialog”,取这个名字是为了显示如何从一个控制台应用程序中弹出一个对话框。
    • 程序清单 8.6  只包含两个例程的 ConDlg.cpp 程序
      //
      // ConDlg.cpp
      // Copyleft 1999 skyline
      // How to pop up a GUI dialog from inside a console
      //
      
      #include &ltwindows.h>
      #include &ltstdio.h>
      #include &ltconio.h>
      #include &ltcommdlg.h>
      #pragma warning (disable:4068)
      
      //
      // GetFileName
      //
      LPSTR GetFileName(HWND hWnd,LPSTR szFile,int StringSize)
      {
          OPENFILENAME ofn;
          char szFileTitle[256];
          char szFilter[256];
      
          strcpy(szFilter,"All Files");
          strcpy(&szFilter[strlen(szFilter)+1],"*.*");
          strcpy(szFileTitle,"Long File Name Search");
          szFile[0]=0;
      
          memset(&ofn,0,sizeof(OPENFILENAME));
      
          ofn.lStructSize  = sizeof(OPENFILENAME);
          ofn.hwndOwner    = hWnd;
          ofn.lpstrFilter  = szFilter;
          ofn.nFilterIndex = 1;
          ofn.lpstrFile    = szFile;
          ofn.nMaxFile     = StringSize;
          ofn.lpstrTitle   = szFileTitle;
          ofn.Flags        = OFN_FILEMUSTEXIST;
      
          if(GetOpenFileName(&ofn)!=TRUE)
          {
      	DWORD Errval;
      	char Errstr[50]="Comman Dialog Error: ";
      	char buf[5];
      	Errval=CommDlgExtendedError();
      	if(Errval!=0)
      	{
      	    wsprintf(buf,"%ld",Errval);
      	    strcat(Errstr,buf);
      	    MessageBox(NULL,Errstr,"Warning",MB_OK|MB_ICONSTOP);
      	}
          }
          return szFile;
      }
      
      //
      // Program Entry Point
      //
      void main(void)
      {
          char FileName[MAX_PATH*2];
          HWND hWnd;
      
          printf("Press any key to see dialog");
          _getch();
      
          hWnd=GetForegroundWindow();
      
          GetFileName(hWnd,FileName,MAX_PATH*2);
      
          printf("/n/nYou choose: %s",FileName);
      
          _getch();
      }
      
      当你运行这个 Console Dialog 程序时,首先出现一个控制台窗口。在窗口中是一个简单的说明,告诉你如果要看到对话框只要按任意键。这部分程序除了对用户作这个提示外没有其它作用。我希望你应该清楚地知道,现在你是在一个控制台应用程序中,你会看到它启动一个 Windows 公用对话框。没有上面这些提示信息,这两个窗口之间的关系可能会不清楚。当你按下任意键后,就出现一个对话框,你可以从中浏览你的硬盘上的目录。注意,它还能够让你看到长文件名。
      当你看了这些文件名后,可以选择一个文件,再按 OK 键就返回到控制台窗口。你所选择的名字就以标准的正文模式字符串显示出来。
      这里的关键是公用对话框也能让你自动访问长文件名,你不需要做任何特别的事情就能做到这一点。只要启动一个对话框,它就自动让你浏览和检索长文件名。
      这个程序的核心部分是围绕着下面一行代码来运转的:
      hWnd=GetForegroundWindow();
      这一行代码返回控制台的 HWND。如果需要,也可以用 GetFocus 例程来检索这个句柄。
      一旦你得到了控制台窗口的句柄,你就没有什么事情不能做了。
      弹出一个公用对话框的实际动作是相当复杂的,因为有一批参数要指定。在后面的部分会对这个函数做深入的讨论,现在我只是介绍一些要点。
      一种办法是,把这里给你的例程当作一个黑匣子,只要把它复制到其它应用程序中去,并传给它一个有效的 HWND,一个存放文件名的缓冲区和缓冲区的长度。注意,这个例程先把文件名清零。如果你希望在对话框第一次出现时就显示你所传送的文件名,只要把下列这一行代码去掉:
      szFile[0]=0;
      创建一个 Open File 对话框的动作分三部分:
      ·将 OPENFILENAME 结构清零。
      ·向你要用的这个结构的各个字段填入信息。
      ·把这个结构传给 GetOpenFileName 函数。
      Windows 将弹出这个对话框并返回用户所选择的名字,然后把 lpstrFile 字段中的名字传给你。注意,OPENFILENAME 结构中有一个字段 InitialDir 可以用来指定用户要浏览的目录。要进一步了解信息可以在联机帮助中查找 OPENFILENAME 和 GetOpenFileName。

    8.7 调试工具:从一个 GUI 中弹出一个控制台窗口


      现在,你已了解如何从一个控制台程序中得到 GUI 界面,下面要介绍一个相反的使用方式:在一个标准的窗口中弹出一个控制台窗口。我把这个题材放在最后并不是说它最不重要。事实上,从 GUI 中弹出一个控制台窗口正是 Windows 9x 界面的很大优点之一。当然你不会在作为商品发行的应用程序中这样做,但当我们深陷入纷乱的调试期的乱麻中时,这种方式是十分有用的。
    • 程序清单 8.7  GuiText.cpp 程序的代码
      //
      // GuiText.cpp
      // Copyleft 1999 skyline
      // GuiText windows program
      //
      
      #define STRICT
      
      #define WIN32_LEAN_AND_MEAN
      #include &ltwindows.h>
      #include &ltwindowsx.h>
      #include &ltstdio.h>
      #include "GuiText.h"
      #include "conbox.h"
      #pragma warning (disable:4068)
      
      // ---------------------------------------
      // Interface
      // ---------------------------------------
      
      static char szAppName[]="GuiText";
      static HWND MainWindow;
      static HINSTANCE hInstance;
      
      // ----------------------------------------
      // Initialization
      // ----------------------------------------
      
      //
      // Program entry point.    
      //
      #pragma argsused                                         
      int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInstance,
      			LPSTR lpszCmdParam,int nCmdShow)
      {
          MSG Msg;
      
          if (! hPrevInstance)
          if (! Register(hInst)) 
      	return FALSE; 
      
          if(! Create(hInst,nCmdShow))
       	return FALSE;
      
          while(GetMessage(&Msg,NULL,0,0))
          {
      	TranslateMessage(&Msg);
      	DispatchMessage(&Msg);              
          }
          return Msg.wParam;
      }
      
      
      // Register the window
      
      BOOL Register(HINSTANCE hInst)
      {
          WNDCLASS WndClass;
      
          WndClass.style = CS_HREDRAW|CS_VREDRAW;
          WndClass.lpfnWndProc = WndProc;
          WndClass.cbClsExtra = 0;
          WndClass.cbWndExtra = 0;
          WndClass.hInstance = hInst;
          WndClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
          WndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
          WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
          WndClass.lpszMenuName = "GuiTextMenu";
          WndClass.lpszClassName = szAppName;
      
          return RegisterClass(&WndClass);
      }
      
      
      // Creat the Window
      
      HWND Create(HINSTANCE hInst,int nCmdShow)
      {                               
          hInstance = hInst;
      
          HWND hwnd = CreateWindowEx (0,szAppName,szAppName,
      			WS_OVERLAPPEDWINDOW,
      			CW_USEDEFAULT,CW_USEDEFAULT,
      			CW_USEDEFAULT,CW_USEDEFAULT,
      			NULL,NULL,hInst,NULL);
          if(hwnd == NULL)
      	return FALSE;
      
          ShowWindow(hwnd,nCmdShow);
          UpdateWindow(hwnd);
      
          return hwnd;
      }
                      
      // ---------------------------
      // WndProc and Implementation
      // ---------------------------
      
      //
      // The window procedure
      //
      LRESULT CALLBACK WndProc(HWND hwnd,UINT Message,
      			WPARAM wParam,LPARAM lParam)  
      { 
          switch(Message)
          {
        	HANDLE_MSG(hwnd,WM_DESTROY,skyline_OnDestroy);
        	HANDLE_MSG(hwnd,WM_COMMAND,skyline_OnCommand);
      
            default:
        	return skyline_DefProc(hwnd,Message,wParam,lParam);
          }
      }
      
      ///
      // Handle WM_DESTROY
      ///
      #pragma argsused
      void skyline_OnDestroy(HWND hwnd)
      {
          PostQuitMessage(0);
      }
                  
      ///
      // WM_COMMAND: pop up a text mode console window
      ///
      #pragma argsused
      void skyline_OnCommand(HWND hwnd,int id,
      		HWND hwndCtl,UINT codeNotify)
      {
          char s[100];
      
          if(id==CM_CONSOLE)
          {
      	AllocConsole();
      	InitStdOut();
      	ClrScr(GOLDBLUE);
      	WriteXY(1,1,"Console ststus report");
      	sprintf(s,"HWND %x HINSTANCE %x ID %d",hwnd,hInstance,id);
      	WriteXY(1,2,s);
      	WriteXY(1,3,"Any key to close");
      	WaitForKeyPress();
      	FreeConsole();
          }
      }
      
      程序清单 8.8  GuiText 程序的主要头文件
      //
      // GuiText.h
      // Copyleft 1999 skyline
      //
      
      #define CM_CONSOLE 101
      
      BOOL Register(HINSTANCE hInst);
      HWND Create(HINSTANCE hInst,int nCmdShow);
      LRESULT CALLBACK WndProc(HWND hwnd,UINT Message,
      			WPARAM wParam,LPARAM lParam);
      
      #define skyline_DefProc DefWindowProc
      void skyline_OnDestroy(HWND hwnd);
      void skyline_OnCommand(HWND hwnd,int id,
      		HWND hwndCtl,UINT codeNotify);
      
      程序清单 8.9  用于访问控制台的实用例程
      //
      // ConBox.cpp
      // Copyleft 1999 skyline
      // Routines for handling Screen IO from a console
      //
      
      #include &ltwindows.h>
      
      static HANDLE hOut;
      static HANDLE hIn;
      
      ///
      // Initialize handle:Call this routine before using others
      ///
      void InitStdOut(void)
      {
          hOut=GetStdHandle(STD_OUTPUT_HANDLE);
          hIn=GetStdHandle(STD_INPUT_HANDLE);
      }
      
      ///
      // GOTOXY Move cursor to Col,Row
      ///
      void GotoXY(int x,int y)
      {
          COORD c;
      
          c.X=x;
          c.Y=y;
      
          SetConsoleCursorPosition(hOut,c);
      }
      
      ///
      // WriteXY
      ///
      void WriteXY(int x,int y,LPSTR s)
      {
          DWORD result;
      
          GotoXY(x,y);
          WriteConsole(hOut,s,strlen(s),&result,NULL);
      }
      
      //
      // Blank out a line of text
      //
      void BlankLine(SHORT y)
      {
          DWORD NumWritten;
          COORD c;
      
          c.X=0;
          c.Y=y;
          FillConsoleOutputCharacter(hOut,' ',80,c,&NumWritten);
      }
      
      //
      // Clear the screen
      //
      void ClrScr(WORD Attr)
      { 
          DWORD NumWritten;
          COORD c;
      
          c.X=0;
          c.Y=0;
          FillConsoleOutputCharacter(hOut,' ',80*25,c,&NumWritten);
          FillConsoleOutputAttribute(hOut,Attr,80*25,c,&NumWritten);
      }
      
      //
      // Write a line of text 
      //
      void Write(LPSTR s)
      {
          DWORD result;
      
          WriteConsole(hOut,s,strlen(s),&result,NULL);
      }
      
      //
      // Wait for a key
      //
      void WaitForKeyPress(void)
      {
          DWORD Result;
          INPUT_RECORD Buf;
      
          do
          {
              ReadConsoleInput(hIn,&Buf,1,&Result);
          }while(Buf.EventType!=KEY_EVENT);
      }
      
      程序清单 8.10  ConBox.cpp 中使用的应用例程的头文件
      //
      // ConBox.h
      // Copyleft 1999 skyline
      //
      
      #define BW FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE
      #define GOLDBLUE FOREGROUND_GREEN|FOREGROUND_RED| /
      		FOREGROUND_INTENSITY|BACKGROUND_BLUE
      #define PB FOREGROUND_RED|FOREGROUND_BLUE
      #define GB FOREGROUND_GREEN
      
      void InitStdOut(void);
      void GoyoXY(int x,int y,LPSTR s,WORD Attr);
      void WriteXY(int x,int y,LPSTR s);
      void BlankLine(SHORT y);
      void ClrScr(WORD Attr);
      void Write(LPSTR s);
      void WaitForKeyPress(void);
      
      程序清单 8.11  GuiText 程序的资源文件
      //
      // GuiText.RC
      //
      #include "GuiText.h"
      
      GuiTextMenu MENU
      BEGIN
          MenuItem "Run Console" CM_CONSOLE
      END
      
      这个程序是一个标准的 Windows 应用程序。它包含一个主窗口,其中只有一个菜单选项。如果你选择这个菜单选项,就会出现一个控制台窗口,其中显示出一些标准的调试信息。尤其是它显示出程序的 HINSTANCE 和 HWND。
      当一个菜单项被选中后,所调用的代码如下:
    • void skyline_OnCommand(HWND hwnd,int id,
      		HWND hwndCtl,UINT codeNotify)
      {
          char s[100];
      
          if(id==CM_CONSOLE)
          {
      	AllocConsole();
      	InitStdOut();
      	ClrScr(GOLDBLUE);
      	WriteXY(1,1,"Console ststus report");
      	sprintf(s,"HWND %x HINSTANCE %x ID %d",hwnd,hInstance,id);
      	WriteXY(1,2,s);
      	WriteXY(1,3,"Any key to close");
      	WaitForKeyPress();
      	FreeConsole();
          }
      }
      
      这个程序几乎用不着我再作任何解释了。这个代码简单地分配一个控制台,然后把输出写在它上面,这样它就可以用于调试。尤其是它显示 HINSTANCE 和 HWND 的当前值,这样你可以常常检查这些值是不是为 0。这是一种简单的确认方法。
      我用来作输出工作的所有例程都属于 WIN32 API 的 WINCON.H 部分。不过,我把这些例程的大部分包在一些函数之中,这些函数取比较简单的名字,例如:WriteXY 和 ClrScr。
      你应该清楚了解这些例程的功能。例如下面一个例程可以用来清屏并将屏幕置成某个特定颜色:
    • void ClrScr(WORD Attr)
      { 
          DWORD NumWritten;
          COORD c;
      
          c.X=0;
          c.Y=0;
          FillConsoleOutputCharacter(hOut,' ',80*25,c,&NumWritten);
          FillConsoleOutputAttribute(hOut,Attr,80*25,c,&NumWritten);
      }
      
      这个例程假定你希望清除一个 80x25 正文屏幕。如前所述,如果你需要,可以对当前屏幕的大小作精确的计算。
      ClrScr 例程只有一个参数,它用来指定特定的一组前景和背景色。这些颜色说明如下:
    • #define BW FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE
      #define GOLDBLUE FOREGROUND_GREEN|FOREGROUND_RED| /
      		FOREGROUND_INTENSITY|BACKGROUND_BLUE
      #define PB FOREGROUND_RED|FOREGROUND_BLUE
      #define GB FOREGROUND_GREEN
      
      BW 代表标准的黑白屏幕。
      另一个要提及的函数是 WaitForKeyPress:
    • void WaitForKeyPress(void)
      {
          DWORD Result;
          INPUT_RECORD Buf;
      
          do
          {
              ReadConsoleInput(hIn,&Buf,1,&Result);
          }while(Buf.EventType!=KEY_EVENT);
      }
      
      这个函数一直运行直到用户按下一个键。如果需要的话还可以修改它,让它返回所按下的键值,这个例程中通过 ReadConsoleInput 来取得用户的数据,然后检查是否为任何一种键盘事件。如果是键盘事件,则循环结束让你的程序恢复运行。如果不是键盘事件或用户没有做任何事情,这个循环便一直疯狂地运转不停。有关这个过程的细节可以参阅本章前面介绍的 Mouse 程序。
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值