回 帖 发 新 帖 刷新版面

主题:[翻译]WinAPI编程指南

这是一份讲述WinAPI编程的指南,原文为英文,小弟准备陆续的将它翻译过来,一则是为了自己学习,再则是为了那些英文稍弱点的朋友提供点便利。(不过还是忠告一句,英文赶快学好)由于小弟水平有限,翻译的过程中难免有很多错误,请务必指出,小弟必欣然接受!如果您有什么意见或者建议,请直接与我联系:
      casm@163.com     QQ:21859891(常年隐身,加时请注明来意)
或登陆我的space,URL是:
http://spaces.msn.com/fetag
另外,对回帖的兄弟有点小小的要求。那些“顶”,“赞扬”之类的就免了,小弟是来“讨骂”的^_^ 。我要的是大家的交流,是您的建议。但骂归骂,请不要“伤及无辜”噢,这份文档只是我一个人翻译的,与父母无关。

译者注:API编程正在被人们渐渐的遗忘,现在还在用纯的API来编程的人已经是凤毛麟角了。在MFC,组件级编程泛滥的今天,还有多少人能体会到API的精髓呢?随着程序规模的不断增大,面向对象技术的不断普及,程序员的效率越来越高,但程序员的整体技术却在一步步的下降。封装技术的日臻完善,使编程也变成了普通的劳动。但是一流的程序员,还是在孜孜不倦的追求着背后的真相。
我只是个初学者,出于爱好和share的目的,准备陆续翻译这份Tutorial,如果文中有什么不恰当的地方或者翻译错误的地方,请您指正,我会很乐意接受您的建议!这是我的E-mail:casm@163.com
 
文档原文:http://www.winprog.org/tutorial/
 
起步
这份指南的内容
这份指南将要展示给您使用Win32 API来开发windows程序的基本方法。开发语言采用C语言,绝大多数C++编译器也都能够正常编译。事实上,指南中的大部分内容对于调用API的任何语言都是可用的,像JAVA,汇编语言,以及Visual Basic。但我不会用这些语言来编写任何的范例代码,先前也有很多人尝试将这份指南用其他的语言来实现,很可惜,成功的人很少。
这份指南不会教您什么是C语言,也不会教您如何应用特定的编译器(Borland C++, Visual C++,LCC-Win32,等等)。然而,我会在附录中提供一些我个人的使用编译器的经验。
如果您不知道宏或者typedef是什么,或者不知道switch语句是如何工作的,请您现在就放下这份文档,先去找一本介绍C语言的书读一读。
非常重要的注释
通常,在全文中,我会指出一些非常重要的东西,这些是必读的。因为它们常常被人理解的一团糟,如果您不读,您也很有可能陷入其中。
首先:
范例压缩包里的源代码是必读的!我不会在文档内包含全部的代码,只会包含那些与我们正在讨论的内容有关的部分。如果您想详细了解代码在程序中是作用,您必须去参看压缩包内提供的源代码。
其次:
读遍所有的内容!如果您在指南的某个部分有什么疑问,请耐心的继续读下去,很可能这个问题在后面不久就会回答。如果您非要立刻弄清这个问题,请暂时停下来,通过IRC或者Email寻求答案。
另外,请您记住,在课题A中涉及的问题,有可能在B或者C甚至L中才做出解答,所以还是先泛读一遍。
好了,这些就是我想说的全部内容,接下来让我们看一些实际的例子吧。
最简单的Win32程序
如果您完全是个初学者,至少您也要保证能够编译一个基本的windows程序。将下面的代码敲入您的编辑器,如果一切正常,您就能创建一个最基本的程序。
记得,要将它作为一个C源文件来编译,而不是C++源文件。虽然这可能没什么关系,但是,既然我们的代码完全是用C写的,就让它保证从开始的时候就向一个有益的方向前进吧。大多数情况下,都要求您的代码保存为a.c而不是a.cpp。如果这个名字很让您头疼,请将它保存为test.c。
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, int nCmdShow)
{
    MessageBox(NULL, "Goodbye, cruel world!", "Note", MB_OK);
    return 0;
}
如果程序报错了,首先请您看一下出错的信息您是否理解,在您的编译器的帮助或者联机文档中搜索下这个错误的描述。确保您的程序指明为Win32 GUI程序,而不是控制台程序。很抱歉,对于这个问题,我也没什么办法。因为根据编译器的不同,错误信息的提示也不同。(而且人与人也不同)
也有可能会有一些警告,类似于没有使用WinMain()函数中的参数,这很正常,不用理会。至此,我们已经建立了一个能正常运行的程序,让我们来分析一下这段代码....
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpCmdLine, int nCmdShow)
WinMain函数等价于DOS或者UNIX编程中的main()函数。这是我们的程序执行的入口点。参数的含义如下:
HINSTANCE hInstance
 程序执行模块的句柄。(内存中的.exe文件)
HINSTANCE hPrevInstance
 在Win32程序中总为NULL
LPSTR lpCmdLine
 一个字符串的命令行参数。不包括程序名
int nCmdShow
 一个可能传递给ShowWindow()函数的整数。我们稍候会解释。
hInstance主要用来装载资源和执行一些基于其它模块的功能。模块是指装载到程序中的exe或者dll文件。在本指南的大部分内容中,他们都是指exe文件。
hPrevInstance在过去的Win16程序中,用来指向一个已经运行了的程序的实例。现在已经不再使用了,在Win32中,你完全可以忽略这个参数。
调用方式
WINAPI指明了调用的方式,它被定义为_stdcall。即使您不明白,也不用担心。它并不影响我们的学习。记住在这里它是必需的就行了。
Win32数据类型
您可能发现了,许多关键字在windows下面都被重新定义了,例如UINT是unsigned int,LPSTR是char* 等等。至于用哪种写法完全取决于您自己。如果您觉得用char* 要比用LPSTR舒服,您尽可以自由的使用。只是要确定所使用数据类型能够正确的匹配。
其实只是记住几个要点就很容易理解了。LP前缀代表指向长整形的指针(long pointer)。在Win32中,long型是一种陈旧的类型了,不用担心。如果您不知道指针是什么,您有两种选择:1)去找本C的书读一读。 2)继续读下去,弄得一团糟。我强烈建议您选择第一种,但还是有很多人选择第二种(我已经说了哦:) 别怪我没提醒您哦!)
接下来是跟在LP后面的一个表示常量的字符C,LPSTR表示一个指向字符串常量的长指针,它指向的类型是不能够修改的。LPSTR是指向字符串的长指针,没有const关键字,是可以修改的。

回复列表 (共29个回复)

沙发

译者注:API编程正在被人们渐渐的遗忘,现在还在用纯的API来编程的人已经是凤毛麟角了。在MFC,组件级编程泛滥的今天,还有多少人能体会到API的精髓呢?随着程序规模的不断增大,面向对象技术的不断普及,程序员的效率越来越高,但程序员的整体技术却在一步步的下降。封装技术的日臻完善,使编程也变成了普通的劳动。但是一流的程序员,还是在孜孜不倦的追求着背后的真相。
我只是个初学者,出于爱好和share的目的,准备陆续翻译这份Tutorial,如果文中有什么不恰当的地方或者翻译错误的地方,请您指正,我会很乐意接受您的建议!这是我的E-mail:casm@163.com
 
文档原文:http://www.winprog.org/tutorial/
 
一个简单的窗口
范例:simple_window
许多人在IRC里面问,“如何创建一个窗口?”...其实,这并不是一件非常简单的事。但是一旦您知道了您想要做什么,这也就不那么困难了。为了创建一个能够正常显示的窗口,确实要做很多事情;这不是在聊天室里三言两语能说清楚的。
我总是倾向于先实际的做点什么,然后再进行分析讨论。...所以先给出创建一个简单窗口的代码,稍后在对其进行讲解。
 
#include <windows.h>
const char g_szClassName[] = "myWindowClass";
 
// Step 4: the Window Procedure 
// 步骤 4: 窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;
    //Step 1: Registering the Window Class
    //步骤 1: 注册窗口类
    
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }
   
    // Step 2: Creating the Window
    // 步骤 2: 创建窗口
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);
    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
   
    // Step 3: The Message Loop
    // 步骤 3: 消息循环
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}
这是一个能创建真正具有窗口功能的最简单的windows程序,仅仅大概70左右行代码。如果第一个范例您正常编译过了,这个也应该没有什么问题。
 
步骤 1: 注册窗口类
窗口类保存着窗口类型相关的信息,包括控制窗口的窗口过程函数,窗口的最大化最小化图标,窗口的背景颜色等。通过这种方法,我们就能够基于创建的这个窗口类,创建多个窗口,并且不需要创建每一个窗口的时候都一次次的设置这些属性。我们设置的大多数的属性都可以在窗口创建之前进行更改。
窗口类和C++中的类没有任何关系。
 
const char g_szClassName[] = "myWindowClass";
上面的数组中存储着我们要创建的窗口类的名字,我们将用它向系统注册我们的窗口类。
 
    WNDCLASSEX wc;
    
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }
 
这是我们在WinMain()函数中注册窗口类的代码。我们为WNDCLASSEX结构的各个成员赋值然后调用RegisterClassEx()函数来注册这个窗口类。

结构的各个成员的含义如下:
cbSize
 结构的大小
style
 窗口类的风格(CS_*),不要同窗口风格(WS_*)弄混了,这个域经常被置为0。
lpfnWndProc
 指向这个窗口类对应的窗口过程函数的指针
cbClsExtra
 为这个类在内存中保留的扩展空间,通常置为0
cbWndExtra
 为这个类创建的窗口在内存中保留的扩展空间,通常置为0
hInstance
 应用程序实例化句柄(WinMain()函数中的第一个参数)
hIcon
 当用户按Alt+Tab键时显示的大图标(通常为32*32)
HCursor
 在窗口上显示的指针
hbrBackground
 设置窗口的背景色
lpszMenuName
 窗口类使用的菜单资源的名字
lpszClassName
 窗口类的名字
hIconSm
 最小化到任务栏和在程序左上角显示的小图标(通常为16*16)

别担心到现在为止,这些东西对您是否有意义,前面涉及到的内容不久就会解释的。另一点要提醒的是,不用把这些域都背下来,我很少见有人能完全背下来结构中的每一个域,或者函数的每一个参数,不要浪费这个精力,时间更重要。如果您知道你要调用的函数,接下来要做的也仅仅是花几秒钟去帮助文档里面找一下这个函数的详细说明。如果您还没有帮助文档,赶快弄一份。没有这个您就落伍了。通过查阅帮助文档,久而久之,最常用的函数的参数基本也就都记住了。
我们接着调用RegisterClassEx()函数并且检验调用是否成功,如说调用失败了,会弹出一个对话框提示出错,然后结束程序。
 

板凳

步骤 2: 创建窗口
一旦窗口类注册成功,我们就能够通过它创建一个窗口了。您可能迫不及待的想知道
CreateWindowEx()函数的参数了(当接触一个新的API函数的时候,您可能总是这样),接下来我会简要地介绍一下。
 
    HWND hwnd;
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);
 
第一个参数(WS_EX_CLIENTEDGE)是扩展的windows窗口风格,既然这样,那我就建立一个中
间全部是客户区的窗口。如果您想比较一下不同的参数有什么不同,就将它设置为0,也可以将它设置为其它的值。
接下来是窗口类的名字(g_szClassName),这个参数通知系统建立一个哪种类型的窗口。既然我们想要建立一个基于我们刚才注册的窗口类的窗口,我们就要将窗口类名作为参数。然后,我们要指定在创建的窗口标题栏上显示的内容(就是在窗口左上角显示的内容,也称为窗口的标题)。
WS_OVERLAPPEDWINDOW 这个参数是我们要创建的窗口的风格。关于这个参数有很多不同的值,您可以自己去查阅。在后面,我们会涉及到。
接下来的四个参数(CW_USEDEFAULT, CW_USEDEFAULT, 320, 240)分别是窗口左上角的x坐标,y坐标,窗口宽度和高度。我将x和y设置为CW_USEDEFAULT,让windows来设定在屏幕上的显示位置。需要注意的是,屏幕的x坐标是自左向右从0开始递增的,y坐标是自上向下递增的。递增的单位是像素,是屏幕所能显示的最基本单位。
接下来的参数(NULL, NULL, g_hInst, NULL)分别是,窗口的父窗口句柄,菜单句柄,程序的实例化句柄和指向窗口数据的指针。在Windows系统中,窗口是具有继承性的。当您见到在窗口上有一个按钮的时候,按钮就是窗口的子窗口,而窗口就是按钮的父窗口。在这个例子中,父窗口的句柄为NULL,因为我们这个窗口并没有父窗口,这个窗口就是我们的主窗口或者称为最顶级窗口。因为到目前为止,我们还没有创建菜单,所以菜单句柄为空。程序实例化句柄是WinMain()函数的第一个参数传递过来的。窗口数据的指针(我几乎从来没有用过)传递给窗口创建时候需要的一些附加数据。
如果您想知道奇特的NULL是什么,哈哈,它其实只是简单的被定义为0。在C中,因为经常被用于指针,它的定义是((void*)0)。所以,当你把它用在整形的位置的时候,很可能编译器会有警告信息,当然,这和你的编译器以及警告的设置有关。你可以选择忽略这些警告或者用0来代替NULL。
有时候,我们实在是不知道该死的bug是怎么出现的,那很可能是我们没有测试函数的返回值。即使是一个有经验的老手,CreateWindow()也经常会调用失败。在调用的过程中,确实有很多错误很容易就犯了。直到我们学会如何快速的找出这些错误,或者至少能够推断哪里可能有问题,并且总是检验函数的返回值的时候,这种情况才能够有所改善。
if(hwnd == NULL)
{
    MessageBox(NULL, "Window Creation Failed!", "Error!",
        MB_ICONEXCLAMATION | MB_OK);
    return 0;
}
当我们创建了窗口并且确定返回的指针有效的时候,我们就可以显示这个窗口了。传递WinMain()函数的最后一个参数,然后更新窗口,使窗口重画在显示器上。
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
nCmdShow参数是随意的。你可以一直简单的传递一个SW_SHOWNORMAL。通过这个参数,可以
指定你的程序启动的方式,任何人运行的时候都一样。启动方式包括启动的时候是否可见,最大化窗口,最小化窗口,等等。你可以在Windows的快捷方式里面找到这一项,这个参数就是指明程序如何启动的。
 
步骤 3: 消息循环
这是整个程序的核心部分,通过这里的控制合理的安排程序的执行。
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
GetMessage()从系统消息队列取得消息。无论何时,用户移动鼠标,敲击键盘,点击您的程序的菜单或者其余的一些操作,操作系统都会产生消息并且将它们发送到系统消息队列中。通过GetMessage()函数,就能将属于本窗口的消息取回送到本程序消息队列处理,然后删除这条消息。如果当前程序没有待处理的消息,GetMessage()函数就会被阻塞。如果您不理解什么是阻塞,可以
认为,阻塞的时候,这个函数会继续等待,直到有一个消息了,才会返回。
TranslateMessage()函数是当接收到键盘消息,像WM_CHAR和WM_KEYDOWN消息一起传送过来
的时候,做一些附加的处理。最后DispatchMessage()将消息发送给窗口处理。(译者注:实际上是发送给窗口的窗口过程函数处理)这个窗口也许是我们的主窗口,也有可能是别的窗口,或者仅仅是一个操作,在很多情况下,窗口的创建是不显示在屏幕上的,可能是由系统隐式的创建或者由另一个程序创建,而这些都不需要我们操心,因为我们通信的途径就是通过发送和接受消息,其余的事操作系统会为我们打理。
 
步骤 4: 窗口过程
如果说消息循环是程序的心脏,那么窗口过程就是程序的大脑。所有发送给程序的消息都在这里被处理。

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
 
每一个消息都会调用窗口过程函数。HWND参数是消息所属的窗口的句柄。当我们的程序有基于一个窗口类的两个或多个窗口的时候,这个参数就非常重要了。这些窗口共用窗口过程函数(WndProc())。区别这些窗口的方法就是靠hwnd的不同。举个例子,当我们接收到一个WM_CLOSE消息的时候,就会根据不同的hwnd参数,销毁相对应窗口,而其它的窗口不会受到任何影响。
当用户点击关闭按钮或者Alt+F4键的时候,触发WM_CLOSE消息,Windows会默认的关闭这个窗口。但我还是习惯自己来处理这一事件,因为有些事情可能需要在关闭窗口处理,像清除某些选项,或者询问用户是否要保存文件等等。
当我们调用了DestroyWindow()函数,系统会发送一个WM_DESTROY消息给窗口,然后销毁窗口的子窗口,释放窗口的资源。既然我们的程序只有一个窗口,我们可以让程序立即结束,所以我们继续调用PostQuitMessage()函数。这个函数会将WM_QUIT消息发送到消息循环中。我们永远也不会处理这个消息,因为它使GetMessage()函数的返回值为FALSE,您将会在我们的消息循环代码中了解到这一点。当接收到这条消息以后,窗口过程就停止处理任何消息了。WM_QUIT消息的wParam域包含着传递给PostQuitMessage()函数的值。只有您的程序是要启动别的程序的时候,这个函数的返回值才有用。(译者注: 实际上这个函数的范围值表示程序是否是正常退出)
 
步骤 5: 没有步骤5
嘿!就这样吧!如果我还有什么没解释清楚的,就先放着吧。希望随着我们不断深入学习更多的程序,所有的疑问都会渐渐的解开。

3 楼

好帖。顶了

4 楼

不错,还可以的啊!

5 楼

嗬嗬,谢谢两位兄弟!
不过小弟已经事先声明了,请大家多多提意见,不需要一些赞扬之类的话的啊。^_^

6 楼

好文章

7 楼

WinAPI编程指南(3)

译者注:API编程正在被人们渐渐的遗忘,现在还在用纯的API来编程的人已经是凤毛麟角了。在MFC,组件级编程泛滥的今天,还有多少人能体会到API的精髓呢?随着程序规模的不断增大,面向对象技术的不断普及,程序员的效率越来越高,但程序员的整体技术却在一步步的下降。封装技术的日臻完善,使编程也变成了普通的劳动。但是一流的程序员,还是在孜孜不倦的追求着背后的真相。
我只是个初学者,出于爱好和share的目的,准备陆续翻译这份Tutorial,如果文中有什么不恰当的地方或者翻译错误的地方,请您指正,我会很乐意接受您的建议!这是我的E-mail:casm@163.com
 
文档原文:http://www.winprog.org/tutorial/
 
消息处理
范例:window_click
我们已经能够创建一个窗口了,可是它除了默认处理函数DefWindowProc()能够执行的一些如改变窗口大小,最大化等操作之外,其余再什么也做不了了,这感觉好像没什么振奋人心的。
在下面这一节里,我将展示给您,如何修改已有的程序,使它能够做一些新的事情。这一节我只讲述如何处理消息。不用看完全部的例子,您就能明白我的意思。这也是我们的目标,好了,集中注意力 :P
一个好的开始就是我们能确定上一个程序能够正常的编译,运行。接下来,就可以在那个程序原有代码的基础上直接修改或者将那个程序的代码拷贝到一个新的工程中,然后修改。
我们要添加到程序中的功能是,当用户在窗口内单击鼠标以后,程序会显示本程序的名字。听上去并不怎么令人激动,但这是最基本的对消息的处理。让我们一起来看下窗口过程中的这段代码。

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
 
想处理鼠标单击消息,我们需要需要添加WM_LBUTTONDOWN消息(或者WM_RBUTTONDOWN,WM_MBUTTONDOWN,分别对应右键和中键)。如果我或者其他人提到处理消息,就是指将消息处理代码添加到WndProc()函数中。
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch(msg)
   {
      case WM_LBUTTONDOWN:    // <-
                              // <-     we just added this stuff
      break;                  // <-     我们就是添加在这里的
      case WM_CLOSE:
         DestroyWindow(hwnd);
      break;
      case WM_DESTROY:
         PostQuitMessage(0);
      break;
      default:
         return DefWindowProc(hwnd, msg, wParam, lParam);
   }
   return 0;
}
 
在窗口过程中,消息处理顺序的先后是没什么关系的,只要确定在每条消息处理结束的时候都添加了break语句就可以了。不难看出,我们在switch()中添加了一个case语句。现在,我们想要程序执行到这里的时候做点什么。
首先,我将要展示一下我们要添加进去的代码(这段代码会显示程序的名字),然后把他集成到我们的程序中。稍后,我会指导您将它添加到您自己的程序中。这对您,对我都是一件好事。我可以不用敲全部的代码,而您可以将它应用于任何程序,不仅仅局限于我展示的这个。如果您还不清楚要怎么做,请看看压缩包内包含着部分内容的代码。

GetModuleFileName(hInstance, szFileName, MAX_PATH);
MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);

这段代码并不是单独存在的,不能够随意的添加到我们程序的任何部分。我们想要当用户鼠标单击的时候它才被执行,所以我是这样将它添加到我们的基本程序中的。
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_LBUTTONDOWN:
// BEGIN NEW CODE
// 新代码开始
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);
            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);
        }
// END NEW CODE
// 新代码结束
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
 

8 楼

请注意{}括起来的部分。当在switch()语句中声明变量的时候,必须要加上{}。虽然这是C语言的基础知识,但我还是认为我应该提醒一下。
添加结束,编译一下。如果一切正常,在窗口内单击一下,就会弹出一个对话框显示程序的名字。
您可能注意到我们添加了两个变量,hInstance和szFileName。查看GetModuleFileName()您会发现,它的第一个参数是一个HINSTANCE变量,一个可执行模块的指针。(指向我们的程序,.exe文件)。为什么我们要得到它呢?GetModuleHandle()就是答案。GetModuleHandle()函数的说明指出,传递一个NULL参数给它,它就会返回一个指向调用它的过程的句柄(实际上就是返回这个程序的句柄),这正是我们所需要的,将这些语句结合起来,我们可以这样声明:
HINSTANCE hInstance = GetModuleHandle(NULL);
再看第二个参数,这是我们都熟悉的。这是一个指向 保存程序的路径和名字的字符串的指针。数据类型是LPTSTR(或者LPSTR)。既然LPSTR和char* 是等价的,我们也可以像这样声明一个数组:
char szFileName[MAX_PATH];
MAX_PATH是由<windows.h>定义的一个很方便的宏。它定义了在Win32系统下面存储一个文件名需要的最大的缓冲区。我们将MAX_PATH传递给GetModuleFileName(),这样它就能知道缓冲区的大小了。
调用GetModuleFileName()以后,szFileName就存储了包含.exe文件的名字的字符串,以NULL结束。我们简单的将它传递给MessageBox()函数显示给用户。
好了,如果您一切添加完了这段代码,现在就可以编译了。如果一切正常,在窗口区单击鼠标,您就能看见一个包含.exe名字的对话框弹出。
如果它不能正常运行,下面是这个程序全部的代码。和您的对比一下,有什么不一样,就是您的错误了。
 
#include <windows.h>
const char g_szClassName[] = "myWindowClass";
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_LBUTTONDOWN:
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);
            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);
        }
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);
    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

9 楼

接下来是跟在LP后面的一个表示常量的字符C,LPSTR表示一个指向字符串常量的长指针,它指向的类型是不能够修改的。

此處應該是樓住打錯了吧,是LPCSTR吧...

強烈支持樓住,希望能繼續看到下文...

10 楼

我说一句! 幸苦了, 楼主!

我来回复

您尚未登录,请登录后再回复。点此登录或注册