回 帖 发 新 帖 刷新版面

主题:[翻译]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个回复)

11 楼

不如下一个 Programmming windows 中文版来看看
买的话,可是要 160 大洋的

12 楼

首先,感谢您能够耐心的看我翻译的这份文档!

下面是MSDN中对这两种数据类型的定义:

LPCSTR 
Pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts. 


LPSTR 
Pointer to a null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts.

13 楼


楼主:
     太好了,呵呵``````````
     我是初学者,很有帮助的啊![em1]

14 楼

为什么我的TC2.0不能include<windows.h>

15 楼

To xgxa1:

<windows.h>头文件是微软编写的头文件,TC2.0里面不包含这个头文件。如果您觉得

安装VC6.0或者.NET太大了,可以安装这个编译器,Dev C++,它应该可以正常运行这

些程序的。

16 楼

译者注:API编程正在被人们渐渐的遗忘,现在还在用纯的API来编程的人已经是凤毛麟角了。在MFC,组件级编程泛滥的今天,还有多少人能体会到API的精髓呢?随着程序规模的不断增大,面向对象技术的不断普及,程序员的效率越来越高,但程序员的整体技术却在一步步的下降。封装技术的日臻完善,使编程也变成了普通的劳动。但是一流的程序员,还是在孜孜不倦的追求着背后的真相。
我只是个初学者,出于爱好和share的目的,准备陆续翻译这份Tutorial,如果文中有什么不恰当的地方或者翻译错误的地方,请您指正,我会很乐意接受您的建议!这是我的E-mail:casm@163.com
 
文档原文:http://www.winprog.org/tutorial/ 


理解消息循环
理解消息循环和windows程序的消息收发体系不仅是编写小程序,而且也是开发其它程序的基础。既然我们已经学习了一些消息处理的方法了,那接下来就稍微深入到整个消息处理过程之中吧,如果您不弄清楚这些,以后就可能会越来越糊涂。

消息是什么?
消息的本质就是一个整数。如果您查看过头文件(这是一种很通用很好的研究API运行原理的方法),您能发现类似这样的定义:

#define WM_INITDIALOG                    0x0110
#define WM_COMMAND                      0x0111
#define WM_LBUTTONDOWN              0x0201

等等。在Windows系统中,一切都是靠消息紧密地联系在一起的,至少在底层是这样。如果您想让一个窗口或者一个控件(实际上它也是一个特殊的窗口)执行一定的功能,您就要发送给它一个消息。如果另一个窗口想要您执行一定的操作,它也会给您发送一个消息。如果有某一事件被触发了,例如用户的键盘输入,鼠标移动,点击按钮等,系统就会将消息发送给对应的窗口。如果您的窗口属于其中之一,你就要接收并处理这个消息。
每个Windows消息都有两个参数,wParam和lParam。最初wParam是16bit,lParam是32bit,但是在Win32中,它们都是32bit。并不是每个消息都要用到这两个参数,每个消息应用它们的方法也不完全相同。例如WM_CLOSE消息,这两个参数都不用,你完全可以忽略它们。而WM_COMMAND消息这两个参数都用,wParam包含两个值,HIWORD(wParam)是通知码(如果可用),LOWORD(wParam)是控制码或者发送消息的菜单的ID值。lParam是发送消息的控件的HWND(窗口句柄),如果为空,说明消息不是来自于控件。
HIWORD()和LOWORD()是windows定义的两个宏,分别用来分离出32bit数据的高位字(0xFFFF0000)和低位字(0xFFFF0000)。在Win32系统中,字(WORD)是16bit的数据,双字(DWORD)是32bit。
您可以调用PostMessag()或者SendMessage()来发送一个消息。PostMessage()将消息放入消息队列后立即返回,也就是说,调用PostMessage()结束后,消息有可能已经被处理了,也有可能尚未被处理。SendMessage()直接将消息发送给窗口,直到窗口处理完这个消息,函数才会返回。如果想要关闭一个窗口,我们可以这样调用PostMessage()函数,发送一个WM_CLOSE消息,PostMessage(hwnd, WM_CLOSE, 0, 0);这样做同点击右上角的关闭按钮效果相同。注意此时wParam和lParam都是0。前面已经提到了,WM_CLOSE不用这两个参数。

对话框
如果您开始使用对话框了,为了同它通信,您就要给它发送消息。您或者可以先利用对话框的ID,调用GetDlgItem()函数得到对话框的句柄,然后调用SendMessage()发送这个消息;或者直接调用SendDlgItemMessage()函数,它将这些操作合并起来执行。您传递给它一个窗口的句柄和一个子窗口的ID,它就会返回子窗口的句柄,然后发送消息。SendDlgItemMessage()函数以及和它相似的GetDlgItemText()函数不仅仅可以应用于对话框,还可以应用于任何的windows程序。
消息循环是什么?

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
}

1. 消息循环调用GetMessage()函数,从消息队列中获取消息。如果消息队列为空,程序基本上会处于等待状态。(也就是所说的阻塞)
2. 当某一事件发生,触发了一个消息,这个消息就被添加到消息队列(例如系统检测到鼠标点击)。GetMessage()函数就会返回一个正值,通知有一个消息请求被处理。这个消息也会被添加到传递过来的MSG结构中。如果接收到WM_QUIT消息,就返回0。如果发生错误,就返回一个负值。
3. 我们接受这个消息(在MSG变量中),并将它传递给TranslateMessage()函数,在这里会做一点额外的处理。将虚拟键盘码转化成字符信息。这一步骤是可选的,但如果不写上,大部分的程序都不能正常运行。
4. 如果上面的步骤一切正常,接下来,我们将消息传递给DispatchMessage()函数。DispatchMessage()要做的是检查这个消息属于哪一个窗口,然后将它发送给窗口对应的窗口过程。然后它会调用窗口过程函数,将窗口的句柄,消息,wParam和lParam作为参数发送给窗口过程函数。
5. 在您的窗口过程中,您将检查消息和它的参数,并且可以随心所欲的处理这些消息。如果您不是在处理特殊的消息,您可以调用DefWindowsProc()函数,用默认的操作来处理这些消息(多数时候都是什么也不做)。
6. 如果您处理完了消息,窗口过程自动返回,DispatchMessage()函数也返回了,程序重新回到循环开始处继续执行。
这是windows编程的一个非常重要的概念。您的窗口过程函数并不是莫名其妙的被系统调用的,实际上,是您自己通过DispatchMessage()函数间接的调用了它。如果您喜欢,您可以在窗口句柄上调用GetWindowLong()将消息直接发送给窗口过程函数!

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
    fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}

我在早先的代码中曾经试验过,这样做虽然也可以正常运行,但是却有各种各样的不足,例如Unicode/ANSI的转换,调用计时器的回调等等,这种方法就不奏效了,并且很可能会影响整个程序。所以,可以尝试这样做,但请不要在真正的代码中这样写。
您可能注意到了,我们用WindowLong()将窗口过程和窗口联系起来了。为什么我们不直接调用窗口过程函数WndProc()呢?消息循环负责维护程序中所有窗口的消息,按钮和列表框也有他们自己的窗口过程,所以我们要确保调用的窗口过程函数是相对应的窗口的。因为多个窗口可以调用相同的窗口过程,所以第一个参数(窗口的句柄)就用于通知窗口过程这个消息是发送给哪个窗口的。
正如您所见到的,应用程序大部分时间都花费在处理消息循环中的消息上了。你可以尽情的将消息发送过去,Windows会为您处理。但是,当您想要结束程序时,要怎么做呢?既然我们用的是while()循环,如果GetMessage()返回了以一个FALSE,那么循环就停止了,程序也就执行到了WinMain()的末尾了,自然就会结束。这也正是PostQuitMeaasge()完成的工作。它将一个WM_QUIT消息添加到消息队列中,然后GetMessage()函数填充消息结构,并且返回0。这时,Msg结构的wParam域包含着您传递给PostQuitMessage()的值,您完全可以忽略它,或者将它返回给即将结束的WinMain()函数。

注意:如果遇到错误,GetMessage()会返回-1。切记这一点,否则很可能在某些地方就会遇到麻
烦。。。尽管GetMessage()被定义为返回一个BOOL型数据,而BOOL型被定义为UINT(unsigned int),所以它的返回值也可能不是TRUE或FALSE。下面是一些似乎可以正常运行的代码,但是在某些情况下并不能正常运行:
    
    while(GetMessage(&Msg, NULL, 0, 0))
    while(GetMessage(&Msg, NULL, 0, 0) != 0)
    while(GetMessage(&Msg, NULL, 0, 0) == TRUE)

以上都是错误的!值得一提的是,我过去在文档中一直用第一种写法,像我刚才说的,这样写也可以正常运行,而且GetMessage()也从来没失败过,而有的时候,您的代码没问题,它却有问题了。然而,我没有考虑您是否会读这里,也可能您的代码既有错误,GetMessage()又失败:)
我已经指出并且纠正这个错误了,如果还有什么遗漏,请见谅。

   while(GetMessage(&Msg, NULL, 0, 0) > 0)

这句代码总是执行同样的作用。(译者注:也就是说这是正确的写法,不会出现不确定行为)
我希望现在您对Windows消息循环能有一个更清晰的理解了,如果还没有,不用担心,当您实际应用了一段时间后,情况自然就会改变了。 

17 楼

老大,你可不以给我发一份过来?
我的邮箱:
yunzhou008@163.com

18 楼

老大辛苦了!谢谢!这世道像您这样的好人不多了!再次感谢!

19 楼

太好了  !!
一份很不错的材料!1
楼主  你太强了!!!
强烈支持!!

20 楼

赫赫,谢谢各位的支持,因为我都是翻译完了就贴上来,也没做归档。但是这些在我

的space上面都有。如果有什么疑问,请到我的space上面提问,我会第一时间解答。

这是url: http://spaces.msn.com/fetag

我来回复

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