windows编程之一

  Windows是个多任务/多线程的操作系统,并且还是一个事件驱动的操作系统。和DOS程序不同的是,Windows程序都是等着用户去使用,由此而触发一个事件,然后Windows对该事件发生响应,进行动作。

按照Microsoft方式编程:匈牙利符号表示法
    表    匈牙利符号表示法的前缀代码指导说明书
  ━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━
  前缀    │数据类型(基本类型)
  ──────┼────────────────────────
  c     │字符
  by    │字节(无符号字符)
  n     │短整数和整数(表示一个数)
  i     │整数
  x,y   │短整数(通常用于x坐标和y坐标)
  cx,cy │短整数(通常用于表示x和y的长度:c表示计数)
  b     │布尔型(整数)
  w     │UINT(无符号整数)和WORD(无符号字)
  l     │LONG(长整数)
  dw    │DWORD(无符号长整数)
  fn    │函数指针
  s     │串
  sz,str│以0字节终止的字符串
  lp    │32位的长整数指针
  h     │编号(常用于表示Windows对象)
  msg   │消息
  ──────┴───────────────────────

变量的命名

  应用匈牙利符号表示法,变量可用表中的前缀代码来表示。另外,当一个变量是由一个或几个子名构成时,每一个子名都要以大写字母开头。下面是几个例子:
  char *szFileName;//a nulla terminated string
  int *lpiDate;//a 32-bit pointer to an int
  BOOL bSemaphore;//a boolean value
  WORD dwMaxCount;//a 32-bit unsigned WORD
  尽管我了解一个函数的局部变量没有说明,但是也有个别表示全局变量:
  int g_iXPos;//a global x-position
  int g_iTimer;//a global y-position
  char *g_szString;//a global NULL terminated string
  总的来说,变量以g_开头,或者有时就只用g。

函数的命名

  函数和变量命名方式相同,但是没有前缀。换句话说,子名的第一个字母要大写。下面是几个例子:
  int PlotPixel(int ix,int iy,int ic);
  void *MemScan(char *szString);
  而且,下划线是非法的,例如,下面的函数名表示是无效的匈牙利符号表示法:
  int Get_Pixel(int ix,int iy);

类型和常量的命名

  所有的类型和常量都是大写字母,但名字中可以允许使用下划线。例如:
  const LONG NUM_SECTORS = 100;//a C++ style constant
  #define MAX_CELLS 64;//a C style constant
  #define POWERUNIT 100;//a C style constant
  typedef unsigned char UCHAR;//a user defined type
  这儿并没有什么不同的地方——非常标准的定义。尽管大多数Microsoft程序员不使用下划线,但我还是喜欢用,因为这样能使名字更具有可读性。

  C++ 在C++中,关键字const不止一个意思。在前面的代码行中,它用来创建一个常数变量。这和#define相似,但是它增加了类型信息这个特性,const不仅仅像#define一样是一个简单的预处理文本替换,而且更像是一个变量,它允许编译器进行类型检查和替换。

类的命名

  类命名的约定可能要麻烦一点。但我也看到有很多人在使用这个约定,并独立地进行补充。不管怎样说,所有C++的类必须以大写C为前缀,类名字的每一个子名的第一个字母都必须大写。下面是几个例子:
  class CVector
  {
   public
   CVector();{ix=iy=yz=imagnitude = 0;}
   CVector(int x,int y,int z){ix=x;iy=y;iz=z;}
   .
   .
   private:
   int ix,iy,iz;//the position of the vector
   int imagnitude;//the magnitude of the vector
  };

参数的命名

  函数的参数命名和标准变量命名的约定相同,但也不总是如此。例如下面例子给出了一个函数定义:
  UCHAR GetPixel(int x,int y);
  这种情况下,更准确的匈牙利函数原型是:
  UCHAR GetPixel(int ix,int iy);
  但我认为这并没有什么两样。
  最后,你甚至可能都看不到这些变量名,而仅仅看到类型,如下所示:
  UCHAR GetPixel(int, int);
  当然,这仅仅是原型使用的,真正的函数声明必须带有可赋值的变量名,这一点你已经掌握了。
  注意:仅仅会读匈牙利符号表示并不代表你能使用它。实际上,我进行编程工作已经有20多年了,我也不准备为谁改变我的编程风格。因此,本书中的代码使用类匈牙利符号表示法的编码风格,这是Win32 API造成的,在其他位置将使用我自己的风格,必须注意的是,我使用的变量名的第一个字母没有大写,并且我还使用下划线。

真实的Windows应用程序

  一个Windows程序的关键就是打开一个窗口。一个窗口就是一个显示文本和图形信息的工作区。要创建一个完全实用的Windows程序,只要进行下列工作:

  1.创建一个Windows类。

  WNDCLASSEX winclass;//this will hole the class we create
  //first fill in the window class structure
  winclass.cbSize = sizeof(WNDCLASSEX);
  winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
  winclass.lpfnWndProc = WindowProc;
  winclass.cbClsExtra = 0;
  winclass.cbWndExtra = 0;
  winclass.hInstance = hinstance;
  winclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
  winclass.hCursor = LoadCursor(NULL,IDC_ARROW);
  winclass.hbrBacground = GetStockObject(BLACK_BRUSH);
  winclass.lpszMenuName = NULL;
  winclass.lpszClassName = "WINCLASS";
  winclass.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
  当然,如果想节省一些打字时间的话,可以像下面这样简单地初始化该结构:
  WNDCLASSEX winclass = {
    winclass.cbSize = sizeof(WNDCLASSEX);
    CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
    WindowProc,
    0,
    0,
    hinstance,
    LoadIcon(NULL,IDI_APPLICATION),
    LoadCursor(NULL,IDC_ARROW),
    GetStockObject(BLACK_BRUSH),
    NULL,
    "WINCLASS1",
    LoadIcon(NULL,IDI_APPLICATION)};
  这样就省去了许多输入!


  2.创建一个事件句柄或WinProc。



  3.用Windows注册Windows类。

  现在Windows类已经定义并且存放在winclass中,必须将新的类通知Windows。该功能通过RegisterClassEx()函数,使用一个指向新类定义的指针来完成,如下所示:
  RegisterClassEx(&winclass);

  4.用前面创建的Windows类创建一个窗口。
  下面是使用标准控件在(0,0)位置创建一个大小为400×400像素的、简单的重叠式窗口。
  HWND hwnd;//window handle
  //create the window,bail if problem
  if(!(hwnd = CreateWindowEx(NULL,//extended style
   " WINCLASS";//class
   " Your Basic Window",//title
   WS_OVERLAPPEDWINDOW|WS_VISIBLE;
   0,0 //initial x,y
   400,400 /initital width,height
   NULL,//handle to parent
   NULL,//handle to menu
   hinstance,//instance of this application
   NULL)))//extra creation parms
  return(0);
  一旦创建了该窗口,它可能是可见或不可见的。但是,在这个例子中,我们增加了自动显示的类型标识符WS_VISIBLE。如果没有添加该标识符,则调用下面的函数来人工显示该窗口:
  //this shows the window
  ShowWindow(hwnd,ncmdshow);
  记住WinMain()中的ncmdshow参数吗?这就是使用它的方便之处。尽管我们使用WS_VISIBLE覆盖了ncmdshow参数,但还是应将其作为一个参数传递给ShowWindow()。下面让Windows更新窗口的内容,并且产生了一个WM_PAINT信息,这通过调用函数UpdateWindow()来完成:
  //this sends a WM_PAINT message to window and makes
  //sure the contents are refreshed
  UpdateWindow();


  5.创建一个能够从事件句柄获得或向事件句柄传递Windows信息的主事件循环。

        事件处理程序就是当事件发生时Windows从主事件循环调用的回调函数
  当用户和Windows运行任务时,你的窗口和/或其他应用程序窗口产生事件和消息。所有消息都进入一个队列,而你窗口的消息发送到你的窗口专用队列中。然后主事件循环检索这些消息,并且将它们发送到你的窗口的WinProc中来处理。


简单列出了一些基本的消息说明符。
  ────────┬────────────────────────
   类型     │ 所创建的内容
  ────────┼────────────────────────
  WN_ACTIVATE  │当窗口被激活或者成为一个焦点时传递
  WM_CLOSE    │当窗口关闭时传递
  WM_CREATE   │当窗口第一次创建时传递
  WM_DESTROY  │当窗口可能要被破坏时传递
  WM_MOVE    │当窗口移动时传递
  WM_MOUSEMOVE │当移动鼠标时传递
  WM_KEYUP   │当松开一个键时传递
  WM_KEYDOWN  │当按钮一下键时传递
  WM_TIMER   │当发生定时程序事件时传递
  WM_USER   │允许传递消息
  WM_PAINT   │当一个窗口需重画时传递
  WM_QUIT   │当Windows应用程序最后结束时传递
  WM_SIZE   │当一个窗口改变大小时传递
  ───────┴─────────────────────────
  ·WM_CREATE——当窗口第一次创建时传递该消息,以便你进行启动、初始化或资源配置工作。
  ·WM_PAINT——当一个窗口内容需要重画时传递该消息。这可能有许多原因:用户移动窗口或改变其尺寸、弹出其他应用程序而遮挡了你的窗口等。
  ·WM_DESTROY——当窗口可能要被破坏时该消息传递到你的窗口。通常 这是由于用户单击该窗口的关闭按钮,或者是从该窗口的系统菜单中关闭该窗口造成的。无论上述哪一种方式,都应用释放所有的资源,并且通过发送一个WM_QUIT消息来通知Windows完全终止应用程序。

  OK!让我们看一看WinPorc处理这些消息的整个过程。
  LRESULT CALLBACK WindowProc(HWND hwnd,
                 UINT msg,
                 WPARAM wparam,
                 LPARAM lparam);
  {//this if the main message haneler of the system
   PAINTSTRUCT ps;//Used in WM_PAINT

   HDC hdc;//handle to a device context
   //what is the message
   switch(msg)
   {
    case WM_CREATE;
    {
     //do initialization stuff here
     //return success
     return(0)
    }break;
    case WM_PAINT;
    {
     //simply validate the window
     hdc = BeginPaint(hwnd,&ps);
     //you woudle do all your painting here
     EndPaint(hwnd,&ps);
     //return success
     return(0)
    }break;
    case WM_DESTROY;
    {
     //Kill the application,this sends a WM_QUIT message
     PostQuitMessage(0);
     //return success
     return(0)
    }break;
    default:break;
   }//end switch
   //process any message that we didn't take care of
   return (DefWindowProc(hwnd,msg,wparam,lparam));
  }//end WinProc
让我们就以WM_CREATE处理程序开始吧。该函数所作的一切就是return(0)。这就是通知Windows由编程人员自己处理该函数,因此就无需更多的操作。当然,也可以在WM_CREATE消息中进行全部的初始化工作,但那是你的事了。
  下一个消息WM_PAINT非常重要。该消息在窗口需要重画时被发送。一般来说这表示你应当进行重画工作。对于DirectX游戏来说,这并不是件什么大事,因为你将以30到60帧/秒的频率来重画屏幕。但是对于标准Windows应用程序来说,它就是件大事了。我将在后面章节中更详细地介绍WM_PAINT,目前的功能就是通知Windows,你要重画该窗口了,因此就停止发送WM_PAINT消息。
  要完成该功能,你必须激活该窗口的窗户区。有许多方法可以做到,但调用函数BeginPaint()和EndPaint()最简单。这一对调用将激活窗口,并使用原先存储在Windows类中的变量hbrbackground的背景刷来填充背景。下面是程序代码。
  //begin painting
  hdc = BdginPaint(hwnd,&ps);
  //you would do all your painting here
  EndPaint(hwnd,&ps);
   调用BeginPaint()函数应注意的最后一件事情是,它返回一个指向图形环境或hdc的句柄:
  HDC hdc;//handle to graphics context
  hdc = BeginPaint(hwnd,&ps);

  WM_DESTROY消息实际上非常有意思。WM_DESTROY在用户关闭窗口时被发送。当然仅仅是关闭窗口,而不是关闭应用程序。应用程序继续运行,但是没有窗口。对此要进行一些处理。大多数情况下,当用户关闭主要窗口时,也就意味着要关闭该应用程序。因此,你必须通过发送一个消息来通知系统。该消息就是WM_QUIT。因为该消息经常使用,所以有一个函数PostQuitMessage()来替你完成发送工作。
  在WM_DESTROY处理程序中你所要做的就是清除一切,然后调用PostQuitMessage(0)通知Windows终止应用程序。接着将WM_QUIT置于消息队列,这样在某一个时候终止主事件循环。
  在我们所分析的WinProc句柄中还有细节应当了解。首先,你肯定注意到了每个处理程序体后面的return(0)。它有两个目的:退出WinPorc以及通知Windows你已处理的信息。第二个重要的细节是默认消息处理程序DefaultWindowProc()。该函数是一个传递Windows默认处理消息的传递函数。因此,如果不处理该消息的话,可通过如下所示的调用来结束你的所有事件处理函数:
  //porcess any message that we didn't take card of
  return (DefWindowProc(hwnd,msg,wparam,lparam));

主事件循环

  最难的一部分终于结束了。我正要脱口而出:主事件循环太简单了。下面讨论一下:
  //enter main event loop
  while(GetMessage(&msg,NULL,0,0))
  {
  //translate any accelerator keys
  TranslateMessage(&msg);
  //send the message to the window proc
  DispatchMessage(&msg);
  }//end while
  这是什么?OK!让我们来研讨一下。只要GetMessage()返回一个非零值,主程序while()就开始执行。GetMessage()是主事件循环的关键代码,其唯一的用途就是从事件队列中获得消息,并进行处理。你会注意到GetMessage()有四个参数。第一个参数对我们非常重要,而其余的参数都可以设置为NULL或0。

     GetMessage()从时间序列中获得下一个消息,然后下一个被调用的函数就是TranslateMessage()。TranslateMessage()是一个虚拟加速键转换器——换句话说就是输入工具。现在只是调用它,不必管其功能。最后一个函数DispatchMessage()指出所有操作发生的位置。当消息被GetMessage获得以后,由函数TranslateMessae()稍加处理和转换,通过函数DispatchMessage()调用WinProc进行进一步的处理。
  DispatchMessage()调用WinProc,并从最初的MSG结构中传递适当的参数。


  程序清单 一个基本的Windows程序
--------------------

// INCLUDES ///////////////////////////////////////////////
#define WIN32_LEAN_AND_MEAN // just say no to MFC

#include <windows.h> // include all the windows headers
#include <windowsx.h> // include useful macros
#include <stdio.h>
#include <math.h>

// DEFINES ////////////////////////////////////////////////

// defines for windows
#define WINDOW_CLASS_NAME "WINCLASS1"

// GLOBALS ////////////////////////////////////////////////


// FUNCTIONS //////////////////////////////////////////////
LRESULT CALLBACK WindowProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
// this is the main message handler of the system
PAINTSTRUCT ps; // used in WM_PAINT
HDC hdc; // handle to a device context

// what is the message
switch(msg)
{
case WM_CREATE:
{
// do initialization stuff here

// return success
return(0);
} break;

case WM_PAINT:
{
// simply validate the window
hdc = BeginPaint(hwnd,&ps);
// you would do all your painting here
EndPaint(hwnd,&ps);

// return success
return(0);
} break;

case WM_DESTROY:
{
// kill the application, this sends a WM_QUIT message
PostQuitMessage(0);

// return success
return(0);
} break;

default:break;

} // end switch

// process any messages that we didn't take care of
return (DefWindowProc(hwnd, msg, wparam, lparam));

} // end WinProc

// WINMAIN ////////////////////////////////////////////////
int WINAPI WinMain( HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{

WNDCLASSEX winclass; // this will hold the class we create
HWND hwnd; // generic window handle
MSG msg; // generic message

// first fill in the window class stucture
winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_DBLCLKS | CS_OWNDC |
CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hinstance;
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = WINDOW_CLASS_NAME;
winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

// register the window class
if (!RegisterClassEx(&winclass))
return(0);

// create the window
if (!(hwnd = CreateWindowEx(NULL, // extended style
WINDOW_CLASS_NAME, // class
"Your Basic Window", // title
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0,0, // initial x,y
400,400, // initial width, height
NULL, // handle to parent
NULL, // handle to menu
hinstance,// instance of this application
NULL))) // extra creation parms
return(0);

// enter main event loop
while(GetMessage(&msg,NULL,0,0))
{
// translate any accelerator keys
TranslateMessage(&msg);

// send the message to the window proc
DispatchMessage(&msg);
} // end while

// return to Windows like this
return(msg.wParam);

} // end WinMain

///////////////////////////////////////////////////////////

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

产生一个实时事件循环

  这种实时的无等侯的事件循环很容易实现.你所需要的就是一种测试在消息序列中是否有消息的方法。如果不,你就处理它;否则,继续处理其他的游戏逻辑并重复进行。运行的测试函数是PeekMessage()。其原形几乎和GetMessage()相同,如下所示:
  BOOL PeekMessage{
   LPMSG lpMag,//pointer to structeure for message
   HWND hWnd,//handle to window
   UINT wMsgFilterMin,//first message
   UINT wMsgFilterMax,//last message
   UINT wRemoveMsg);//removal flags
  如果有可用消息的话返回值非零。
  区别在于最后一个参数,它控制如何从消息序列中检索消息。对于wRemoveMsg,有效的标志有:
  ·PM_NOREMOVE——PeekMessage()处理之后,消息没有从序列中去除。
  ·PM_REMOVE——PeekMessage()处理之后,消息已经从序列中去除。
  如果将这两种情况考虑进去的话,你可以做出两个选择:如果有消息的话,就使用PeekMessage()和PM_NOREMOVE,调用GetMessate();另一种选择是:使用PM_REMOVE。如果有消息则使用PeekMessage()函数本身来检索消息。一般使用后一种情况。下面是核心逻辑的代码,我们在主事件循环中消作改动以体现这一新技术:

  while(TRUE)
  {
   //test if there is a message in queue,if so get it
   if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)
   {
    //test if this a quit
    if(msg.message == WM_QUIT)
     break;
    //translate any accelerator keys
    TranslateMessage(&msg);
    //send the message to the window proc
    DispatchMessage(&msg);
   }//end if
   //main game processing goes here
   Game_Main();
  }//end while

  我已经将程序中的重要部分用黑体显示。黑体的第一部分内容是:
  if(msg.message == WM_QUIT) break;
  下面是如何测试从无限循环体while(true)中退出。请记住,当在WinProc中处理WM_DESTROY消息时,你的工作就是通过调用PostQuitMessage()函数来传递WM_QUIT消息。WM_QUIT就在事件序列中慢慢地移动,你可以检测到它,所以可以跳出主循环。
  用黑体显示的程序最后一部分指出调用主游戏程序代码循环的位置。但是请不要忘记,在运行一幅动画或游戏逻辑之后,调用Game_Main()或者调用任意程序必须返回。否则,Windows主事件循环将不处理消息。

打开多个窗口

  实际上,这是小事一桩,其实你已经知道如何打开多个窗口。所需要做的就是多次调用函数CreateWindowEx()来创建这些窗口,事实也的确如此。但是,对此还有一些需要注意的问题。
  首先,请记住当创建一个窗口时,是建立在Windows类的基础之上的。在该类中定义了WinProc或者整个类的事件处理程序。这是非常重要的细节,因此应当注意。你可以使用同一个类来创建多个窗口,这些窗口的所有消息都要传递到同一个WinProc中,正如由WINCLASSEX结构中定义lpfnWndProc字段指向的事件处理程序一样。

  如果想应用不同的WinProc打开各个窗口的话,你必须创建多个Windows类,并且使用每一个类创建各个窗口。这亲每一个类的窗口就指向各自的WinProc并同其传递消息。了解了这些内容后,下面是基于同一个类来创建多个窗口的程序代码:

  //create the first window
  if(!(hwnd = CreateWindowEx(NULL,//extended style
    WINDOW_CLASS_NAME,//class
    "Window 1 Based on WINCLASS1",//title
    WS_OVERLAPPEDWINDOW|WS_VISIBLE,
    0,0,//initial x,y
    400,400//initial width,height
    NULL,//handle to parent
    NULL,//handle to menu
    hinstance,//instance of this application
    NULL)))//extra creation parms
  return (0);

  //create the second window
  if(!(hwnd = CreateWindowEx(NULL,//extended style
    WINDOW_CLASS_NAME,//class
    "Window 2 Also Based on WINCLASS1",//title
    WS_OVERLAPPEDWINDOW|WS_VISIBLE,
    100,100,//initial x,y
    400,400//initial width,height
    NULL,//handle to parent
    NULL,//handle to menu
    hinstance,//instance of this application
    NULL)))//extra creation parms
  return (0);

  当然,可能希望用不同的变量而不是同一个变量来跟踪每一个窗口,就像hwnd中的例子那样,其实已掌握了其要义,如一次打开两个窗口的例子。注意当你关闭任何一个窗口时,两个窗口将同时关闭,并且应用程序也终止。看一看是否能够找到每次只关闭一个窗口的方法。(提示:创建两个Windows类,直到两个窗口都关闭之后再传递WM_QUIT消息。)

总结

  通过上面学习了解了Windows和多任务的结构,并且也知道了如何创建一个Windows类、注册类、创建窗口、编写事件循环和句柄等等内容.

posted on 2007-07-12 16:56  Wenguan  阅读(346)  评论(0)    收藏  举报

导航