﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-Jinglin's 国度-随笔分类-Win32汇编MASM基础</title><link>http://www.blogjava.net/cuiyuelei/category/19563.html</link><description>笑渐不闻声渐悄 多情却被无情恼 </description><language>zh-cn</language><lastBuildDate>Tue, 24 Jul 2007 15:07:40 GMT</lastBuildDate><pubDate>Tue, 24 Jul 2007 15:07:40 GMT</pubDate><ttl>60</ttl><item><title>处理鼠标消息</title><link>http://www.blogjava.net/cuiyuelei/archive/2007/07/24/132026.html</link><dc:creator>催月泪(Jaclick)</dc:creator><author>催月泪(Jaclick)</author><pubDate>Tue, 24 Jul 2007 05:11:00 GMT</pubDate><guid>http://www.blogjava.net/cuiyuelei/archive/2007/07/24/132026.html</guid><wfw:comment>http://www.blogjava.net/cuiyuelei/comments/132026.html</wfw:comment><comments>http://www.blogjava.net/cuiyuelei/archive/2007/07/24/132026.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cuiyuelei/comments/commentRss/132026.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cuiyuelei/services/trackbacks/132026.html</trackback:ping><description><![CDATA[
		<b>  .ELSEIF uMsg==</b>
		<b>
				<font color="#0000ff">WM_LBUTTONDOWN</font>
		</b>
		<br />
		<b>        mov eax,lParam</b>
		<br />
		<b>        and eax,0FFFFh</b>              ；高16位清0，保留低16位，即X坐标<br /><b>        mov hitpoint.x,eax</b>            <br /><b>        mov eax,lParam</b>  <br /><b>        shr eax,16</b>                        ；IParam逻辑右移16位，保留Y坐标<br /><b>        mov hitpoint.y,eax</b><br /><b>        mov MouseClick,TRUE</b><br /><b>      </b><b> </b><b> invoke InvalidateRect,hWnd,NULL,TRUE</b><br /><br /><pre class="syntax"><b>LRESULT CALLBACK WindowProc(
  HWND</b><i> hwnd</i><b>,       </b>// handle to window
<b>  UINT</b><i>uMsg</i><b>,       </b>// WM_LBUTTONDOWN
<b>  WPARAM</b><i><a class="synParam" onclick="showTip(this)" href=""><font color="#002c99">wParam</font></a></i><b>,   </b>// key indicator
<b>  LPARAM</b><i><a class="synParam" onclick="showTip(this)" href=""><font color="#002c99">lParam</font></a></i>    // 鼠标的X,Y坐标
<b>);<br /><br /><br /><br /><br /><br /><br /><br /><br /></b></pre><dt><i>lParam参数只能以32位使用</i></dt><dd><p>低16位存放X坐标<br />        高16位存放Y坐标</p><h4>Return Values</h4><p>If an application processes this message, it should return zero. <br /><br /><br />and  eax,ffff000 取高位  <strong>and eax,0FFFFh</strong>  取低位<br /></p></dd><br /><br /><img src ="http://www.blogjava.net/cuiyuelei/aggbug/132026.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cuiyuelei/" target="_blank">催月泪(Jaclick)</a> 2007-07-24 13:11 <a href="http://www.blogjava.net/cuiyuelei/archive/2007/07/24/132026.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Iczelion's Win32 Assembly Tutorial 4: 绘制文本</title><link>http://www.blogjava.net/cuiyuelei/archive/2007/04/14/110689.html</link><dc:creator>催月泪(Jaclick)</dc:creator><author>催月泪(Jaclick)</author><pubDate>Sat, 14 Apr 2007 12:34:00 GMT</pubDate><guid>http://www.blogjava.net/cuiyuelei/archive/2007/04/14/110689.html</guid><wfw:comment>http://www.blogjava.net/cuiyuelei/comments/110689.html</wfw:comment><comments>http://www.blogjava.net/cuiyuelei/archive/2007/04/14/110689.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cuiyuelei/comments/commentRss/110689.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cuiyuelei/services/trackbacks/110689.html</trackback:ping><description><![CDATA[
		<p align="left">翻译：Lxx，校对：LuoYunBin's Win32 ASM Page，<a href="http://asm.yeah.net">http://asm.yeah.net</a><br /><br /><a href="/Files/cuiyuelei/Iczelion_Win32ASM.zip">点击此处下载全教程</a><br /></p>
		<hr />
		<p>本课中，我们将学习如何在窗口的客户区“绘制”字符串。我们还将学习关于“<font color="#0000ff">设备环境</font>”的概念。点击这里下载实例here. </p>
		<p>
				<font color="#0000ff">理论：</font>
				<br />
				<br />Windows 中的文本是一个GUI（图形用户界面）对象。每一个字符实际上是由许多的像素点组成，这些点在有笔画的地方显示出来，这样就会出现字符。这也是为什么我说“绘制”字符，而不是写字符。通常您都是在您应用程序的客户区“绘制”字符串（尽管您也可以在客户区外“绘制”）。Windows 下的“绘制”字符串方法和DOS下的截然不同，在DOS下，您可以把屏幕想象成 85 x 25 的一个平面，而Windows下由于屏幕上同时有几个应用程序的画面，所以您必须严格遵从规范。Windows 通过把每一个应用程序限制在他的客户区来做到这一点。当然客户区的大小是可变的，您随时可以调整。</p>
		<p>在您在客户区“绘制”字符串前，您必须从Windows那里得到您客户区的大小，确实您无法像在DOS下那样随心所欲地在屏幕上任何地方“绘制”，绘制前您必须得到Windows的允许，然后Windows会告诉您客户区的大小，字体，颜色和其它GUI对象的属性。您可以用这些来在客户区“绘制”。</p>
		<p> 什么是“<font color="#0000ff">设备环境</font>”（DC）呢？它其实是由Windows内部维护的一个数据结构。一个“设备环境”和一个特定的设备相连。像打印机和显示器。对于显示器来说,“设备环境”和一个个特定的窗口相连。</p>
		<p> “设备环境”中的有些属性和绘图有关，比如：颜色，字体等。您可以随时改动那些缺省值，之所以保存缺省值是为了方便。您可以把“设备环境”想象成是Windows 为您准备的一个绘图环境，而您可以随时根据需要改变某些缺省属性。 </p>
		<p>当应用程序需要绘制时，您必须得到一个“设备环境”的句柄。通常有几种方法。<br /><br />call BeginPaint响应WM_PAINT消息<br />call GetDC       响应其他消息<br />call CreateDC  创建你自己的设备环境（DC）</p>
		<p> </p>
		<p>您必须牢记的是，<font color="#0000ff">在处理单个消息后你必须释放“设备环境”句柄</font>。不要在一个消息处理中获得“设备环境”句柄，而在另一个消息处理中在释放它。<br />        </p>
		<p>我们在Windows 发送 <font color="#0000ff">WM_PAINT</font> 消息时处理绘制客户区，Windows 不会保存客户区的内容，它用的是方法是“重绘”机制（比如当客户区刚被另一个应用程序的客户区覆盖），Windows 会把 WM_PAINT 消息放入该应用程序的消息队列。重绘窗口的客户区是各个窗口自己的责任，您要做的是在窗口过程处理WM_PAINT 的部分知道绘制什么和何如绘制。<br />        </p>
		<p>您必须了解的另一个概念是“<font color="#0000ff">无效区域</font>”。<font color="#0000ff">Windows 把一个最小的需要重绘的正方形区域叫做“无效区域”。</font>当 Windows 发现了一个”无效区域“后，它就会向该应用程序发送一个 WM_PAINT 消息，在 WM_PAINT 的处理过程中，窗口首先得到一个有关绘图的结构体，里面包括无效区的坐标位置等。您可以通过调用BeginPaint 让“无效区”有效，如果您不处理 WM_PAINT 消息，至少要调用缺省的窗口处理函数 DefWindowProc ，或者调用 ValidateRect 让“无效区”有效。否则您的应用程序将会收到无穷无尽的 WM_PAINT 消息。<br />        </p>
		<p>下面是响应WM_PAINT消息的步骤：</p>
		<p>1. <font color="#0000ff">使用<font color="#ff0000">BeginPaint</font>取得“设备环境”句柄</font><br /><br />2.  <font color="#0000ff">绘制客户区</font><br /><br />3.  <font color="#0000ff">使用<font color="#ff0000">EndPaint</font>释放“设备环境”句柄</font><br /><br />注意：您无须显式地让“无效区”有效，这个动作由 BeginPaint 自动完成。您可以在 BeginPaint 和 Endpaint 之间，调用所有的绘制函数。几乎所有的GDI 函数都需要“设备环境”的句柄作为参数。<br /><br /><font color="#0000ff">内容：<br /></font><br />我们将写一个应用程序，它会在客户区的中心显示一行 <font color="#ffa500">"Win32 assembly is great and easy!"  </font></p>
		<p>.386 <br />.model flat,stdcall <br />option casemap:none </p>
		<p>WinMain proto :DWORD,:DWORD,:DWORD,:DWORD <br /><br /><font color="#0000ff">include</font> \masm32\include\windows.inc <br /><font color="#0000ff">include</font> \masm32\include\user32.inc <br /><font color="#0000ff">includelib</font> \masm32\lib\user32.lib <br /><font color="#0000ff">include</font> \masm32\include\kernel32.inc <br /><font color="#0000ff">includelib</font> \masm32\lib\kernel32.lib </p>
		<p>
				<font color="#0000ff">.DATA</font>
				<br />
				<br />ClassName db "<font color="#ffa500">SimpleWinClass</font>",0 <br />AppName  db "<font color="#ffa500">Our First Window</font>",0 <br />OurText  db "<font color="#ffa500">Win32 assembly is great and easy</font><font color="#ffa500">!</font>",0 </p>
		<p>
				<font color="#0000ff">.DATA? <br /></font>
				<br />hInstance HINSTANCE ? <br />CommandLine LPSTR ? </p>
		<p>
				<font color="#0000ff">.CODE</font>
				<br />
				<br />start: <br />    invoke <font color="#ff0000">GetModuleHandle</font>, NULL <br />    mov    hInstance,eax <br />    invoke <font color="#ff0000">GetCommandLine</font><br />    mov CommandLine,eax<br />    invoke WinMain, hInstance, NULL, CommandLine,  SW_SHOWDEFAULT <br />    invoke <font color="#ff0000">ExitProcess</font>,eax </p>
		<p>WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD <br />    <br />    LOCAL wc:<font color="#0000ff">WNDCLASSEX</font><br />    LOCAL msg:<font color="#0000ff">MSG</font><br />    LOCAL hwnd:<font color="#0000ff">HWND</font> <br />    <br />    mov   wc.cbSize, SIZEOF WNDCLASSEX <br />    mov   wc.style, CS_HREDRAW or CS_VREDRAW <br />    mov   wc.lpfnWndProc, OFFSET WndProc <br />    mov   wc.cbClsExtra, NULL <br />    mov   wc.cbWndExtra, NULL <br />    push  hInst <br />    pop   wc.hInstance <br />    mov   wc.hbrBackground, COLOR_WINDOW+1 <br />    mov   wc.lpszMenuName,NULL <br />    mov   wc.lpszClassName,OFFSET ClassName <br />    <font color="#0000ff">invoke</font><font color="#ff0000">LoadIcon</font>, NULL, IDI_APPLICATION <br />    mov   wc.hIcon,eax <br />    mov   wc.hIconSm,eax <br />    <font color="#0000ff">invoke</font><font color="#ff0000">LoadCursor</font>, NULL, IDC_ARROW <br />    mov   wc.hCursor,eax <br />    <br />    <font color="#0000ff">invoke </font><font color="#ff0000">RegisterClassEx</font>, addr wc <br />    <font color="#0000ff">invoke</font><font color="#ff0000">CreateWindowEx</font>,NULL, ADDR ClassName,ADDR AppName,\ <br />           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ <br />           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ <br />           hInst,NULL <br />    mov   hwnd,eax <br />   <font color="#0000ff"> invoke</font><font color="#ff0000">ShowWindow</font>, hwnd, SW_SHOWNORMAL <br />    <font color="#0000ff">invoke</font><font color="#ff0000">UpdateWindow</font>, hwnd    </p>
		<p> <font color="#0000ff">.WHILE</font><font color="#ff0000">TRUE</font><br />                <font color="#0000ff">invoke </font><font color="#ff0000">GetMessage</font>, ADDR msg,NULL,0,0 <br />                <font color="#0000ff">.BREAK</font><font color="#0000ff">.IF</font> (!eax) <br />                <font color="#0000ff">invoke </font><font color="#ff0000">TranslateMessage</font>, ADDR msg <br />                <font color="#0000ff">invoke</font><font color="#ff0000">DispatchMessage</font>, ADDR msg <br />  <font color="#0000ff">.ENDW <br /></font>        mov     eax, msg.wParam <br />        ret <br />WinMain endp </p>
		<p>WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM <br />    <br />    LOCAL hdc:<font color="#0000ff">HDC</font><br />    LOCAL ps:<font color="#0000ff">PAINTSTRUCT</font><br />    LOCAL rect:<font color="#0000ff">RECT</font> <br />   <br />   .IF uMsg==<font color="#ff0000">WM_DESTROY</font><br />        <font color="#0000ff">invoke</font><font color="#ff0000">PostQuitMessage</font>, NULL <br />    <br />      .ELSEIF uMsg==<font color="#ff0000">WM_PAINT</font><br />        invoke <font color="#0000ff">BeginPaint</font>, hWnd, ADDR ps <br />        mov    hdc,eax <br />        invoke<font color="#ff0000"></font><font color="#0000ff">GetClientRect</font>, hWnd, ADDR rect <br />        invoke <font color="#0000ff">DrawText</font>, hdc, ADDR OurText,-1, ADDR rect, \ <br />                DT_SINGLELINE or DT_CENTER or DT_VCENTER <br />        invoke <font color="#0000ff">EndPaint</font>, hWnd, ADDR ps <br />   <br />     .ELSE <br />        invoke<font color="#0000ff"> DefWindowProc</font>, hWnd,uMsg, wParam,lParam <br />        ret <br />    .ENDIF <br />    xor   eax, eax <br />    ret <br />WndProc endp <br />end start<br /><br /></p>
		<p align="center">
				<img height="233" alt="tut4.gif" src="http://www.blogjava.net/images/blogjava_net/cuiyuelei/tut4.gif" width="330" border="0" />
		</p>
		<p>
				<br />
				<font color="#0000ff">分析：<br /></font>
				<br />这里的大多数代码和第三课中的一样。我只解释其中一些不相同的地方。</p>
		<p>    LOCAL hdc:<font color="#0000ff">HDC</font><br />    LOCAL ps:<font color="#0000ff">PAINTSTRUCT</font><br />    LOCAL rect:<font color="#0000ff">RECT</font></p>
		<p>这些局部变量由处理 WM_PAINT 消息中的 GDI 函数调用。hdc 用来存放调用 BeginPaint 返回的“设备环境”句柄。ps 是一个 PAINTSTRUCT 数据类型的变量。通常您不会用到其中的许多值，它由 Windows 传递给 BeginPaint，在结束绘制后再原封不动的传递给 EndPaint。rect 是一个 RECT 结构体类型参数，它的定义如下：</p>
		<p>
				<font color="#0000ff">RECT Struct <br />    left           LONG ? <br />    top           LONG ? <br />    right         LONG ? <br />    bottom     LONG ? <br />RECT ends</font>
		</p>
		<p>left 和 top 是正方形左上角的坐标。right 和 bottom 是正方形右下角的坐标。客户区的左上角的坐标是 x=0，y=0，这样对于 x=0，y=10 的坐标点就在它的下面。</p>
		<p>        invoke BeginPaint,hWnd, ADDR ps <br />        mov    hdc,eax <br />        invoke GetClientRect,hWnd, ADDR rect <br />        invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \ <br />                DT_SINGLELINE or DT_CENTER or DT_VCENTER <br />        invoke EndPaint,hWnd, ADDR ps </p>
		<p>在处理 <font color="#0000ff">WM_PAINT</font> 消息时，您调用BeginPaint函数，传给它一个窗口句柄和未初始化的 PAINTSTRUCT 型参数。调用成功后在 eax 中返回“设备环境”的句柄。下一次，调用 GetClientRect 以得到客户区的大小，大小放在 rect 中，然后把它传给 DrawText。DrawText 的语法如下： </p>
		<p>DrawText  proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD </p>
		<p>DrawText是一个高层的调用函数。它能自动处理像换行、把文本放到客户区中间等这些杂事。所以您只管集中精力“绘制”字符串就可以了。我们会在下一课中讲解低一层的函数 TextOut，该函数在一个正方形区域中格式化一个文本串。它用当前选择的字体、颜色和背景色。它处理换行以适应正方形区域。它会返回以设备逻辑单位度量的文本的高度，我们这里的度量单位是像素点。让我们来看一看该函数的参数： </p>
		<p>
				<font color="#0000ff">Hdc</font> : “设备环境”的句柄。<br /><font color="#0000ff"><br />lpString</font> : 要显示的文本串，该文本串要么以NULL结尾，要么在nCount中指出它的长短。</p>
		<p>
				<font color="#0000ff">nCount</font>:要输出的文本的长度。若以NULL结尾，该参数必须是-1。 </p>
		<p>
				<font color="#0000ff">lpRect</font> : 指向要输出文本串的正方形区域的指针，该方形必须是一个裁剪区，也就是说超过该区域的字符将不能显示。 </p>
		<p>
				<font color="#0000ff">uFormat </font>: 指定如何显示。我们可以用 OR把以下标志或到一块：</p>
		<p> </p>
		<p>
				<font color="#ff0000">DT_SINGLELINE</font>     单行显示。</p>
		<p>
				<font color="#ff0000">DT_CENTER</font>              水平居中。</p>
		<p>
				<font color="#ff0000">DT_VCENTER</font>           垂直居中。</p>
		<p>
				<font color="#0000ff">结束绘制后，必须调用 EndPaint 释放“设备环境”的句柄</font>。 好了，现在我们把“绘制”文本串的要点总结如下：<br /><br />1. <font color="#0000ff">必须在开始和结束处分别调用<font color="#ff0000">BeginPaint</font>和<font color="#ff0000">EndPaint</font>；</font></p>
		<p>2.<font color="#0000ff">在<font color="#ff0000">BeginPaint</font>和<font color="#ff0000">EndPaint</font>之间调用所有的绘制函数</font>； </p>
		<p>3. <font color="#0000ff">如果在其它的消息处理中重新绘制客户区</font>，您可以有两种选择：</p>
		<p>(1) 用GetDC和ReleaseDC代替BeginPaint和EndPaint；</p>
		<p>(2) 调用<font color="#0000ff">InvalidateRect</font>或<font color="#0000ff">UpdateWindow</font>让客户区无效，这将迫使WINDOWS把<font color="#ff0000">WM_PAINT</font>放入应用程序消息队列，从而使得客户区重绘<br /></p>
		<hr />
		<strong>[ <a href="http://win32asm.cjb.net Iczelion" temp_href="http://win32asm.cjb.net Iczelion"><font color="#4371a6">Iczelion's Win32 Assembly HomePage</font></a>] 需要代理服务器，但可通过以下镜像进行访问<br /></strong>
		<a href="http://win32assembly.online.fr/">
				<strong>
						<font color="#4371a6">http://win32assembly.online.fr/</font>
				</strong>
		</a>
		<strong>                 </strong>
		<a href="http://users.daex.ufsc.br/~iczelion/">
				<strong>
						<font color="#4371a6">http://users.daex.ufsc.br/~iczelion/</font>
				</strong>
		</a>
		<strong>  </strong>
		<a href="http://spiff.tripnet.se/~Iczelion">
				<strong>
						<font color="#4371a6">http://spiff.tripnet.se/~Iczelion</font>
				</strong>
		</a>
		<br />
<img src ="http://www.blogjava.net/cuiyuelei/aggbug/110689.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cuiyuelei/" target="_blank">催月泪(Jaclick)</a> 2007-04-14 20:34 <a href="http://www.blogjava.net/cuiyuelei/archive/2007/04/14/110689.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Iczelion's Win32 Assembly Tutorial 3: 一个简单的窗口</title><link>http://www.blogjava.net/cuiyuelei/archive/2007/03/13/103665.html</link><dc:creator>催月泪(Jaclick)</dc:creator><author>催月泪(Jaclick)</author><pubDate>Tue, 13 Mar 2007 15:37:00 GMT</pubDate><guid>http://www.blogjava.net/cuiyuelei/archive/2007/03/13/103665.html</guid><wfw:comment>http://www.blogjava.net/cuiyuelei/comments/103665.html</wfw:comment><comments>http://www.blogjava.net/cuiyuelei/archive/2007/03/13/103665.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cuiyuelei/comments/commentRss/103665.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cuiyuelei/services/trackbacks/103665.html</trackback:ping><description><![CDATA[
		<p>翻译：Lxx，校对：LuoYunBin's Win32 ASM Page，<a href="http://asm.yeah.net"><font size="4">http://asm.yeah.net</font></a><br /><br /><a href="/Files/cuiyuelei/Iczelion_Win32ASM.zip">点击此处下载全教程</a></p>
		<hr />
在本课中我们将写一个Windows 程序,它会在桌面显示一个标准的窗口.点击这里下载例程<a href="/Files/cuiyuelei/Tutorial3.zip"> here</a>.<br /><br /><font color="#0000ff">理论：</font><br /><br />Windows程序中,在写图形用户界面时需要调用大量的标准Windows GUI函数。其实这对用户和程序员来说都有好处,对于用户,面对的是同一套标准的窗口,对这些窗口的操作都是一样的,所以使用不同的应用程序时无须重新学习操作.对程序员来说,这些GUI源代码都是经过了微软的严格测试,随时拿来就可以用的.当然至于具体地写程序对于程序员来说还是有难度的.为了创建基于窗口的应用程序,必须严格遵守规范.作到这一点并不难,只要用模块化或OOP编程方法即可.
<p>下面我就列出在桌面显示一个窗口的几个步骤：</p><p>1.  <font color="#0000ff">得到您应用程序的实例句柄(必需)；</font></p><p>2.  <font color="#0000ff">得到命令行参数(<font color="#000000">如果您想从命令行得到参数，可选</font>)；</font></p><p>3.  <font color="#0000ff">注册窗口类</font>(必需,除非您使用Windows预定义的窗口类,如 MessageBox,Dialogbox)</p><p>4. <font color="#0000ff"> 创建窗口</font>(必需)；</p><p>5.  <font color="#0000ff">在桌面显示窗口</font>(必需，除非您不想立即显示它)；</p><p>6.  <font color="#0000ff">刷新窗口客户区</font>；</p><p>7.  <font color="#0000ff">进入无限的获取窗口消息的循环</font>；</p><p>8.  <font color="#0000ff">如果有消息到达，由负责该窗口的窗口回调函数处理</font>；</p><p>9.  <font color="#0000ff">如果用户关闭窗口，进行退出处理</font>。</p><p>可以看的出,相对于单用户的DOS下的编程来说,Windows下的程序框架结构是相当复杂的.但是Windows和DOS在系统架构上是截然不同的.Windows是一个多任务的操作系统故系统中同时有多个应用程序彼此协同运行.这就要求Windows程序员必须严格遵守编程规范,并养成良好的编程风格。<br /><br /><font color="#0000ff">内容：</font><br /><br />下面是我们简单的窗口程序的源代码。在进入复杂的细节前，我将指出几点要点以简化你的设计：</p><p>您应当把程序中要用到的<font color="#000000">所有常量和结构体的声明放到一个头文件中</font>，并且在源程序的开始处包含这个头文件。这么做将会节省您大量的时间，也免得一次又一次的敲键盘。目前，最完善的头文件是hutch写的，您可以到hutch或我的网站下载。您也可以定义您自己的常量和结构体，但最好把它们放到独立的头文件中用<font color="#0000ff">includelib</font>指令，包含您的程序要引用的库文件，譬如：若您的程序要调用 "MessageBox"， 您就应当在源文件中加入如下一行： includelib user32.lib 这条语句告诉 MASM 您的程序将要用到一些引入库。如果您不止引用一个库，只要简单地加入includelib语句，不要担心链接器如何处理这么多的库，只要在链接时用链接开关 /LIBPATH 指明库所在的路径即可。 <br /><br />在其它地方运用头文件中定义函数原型，常数和结构体时，要严格保持和头文件中的定义一致，包括大小写。在查询函数定义时，这将节约您大量的时间； <br />在编译，链接时用makefile文件，免去重复敲键。 <br /><br />.386 <br />.model flat,stdcall <br />option casemap:none <br />include \masm32\include\windows.inc <br />include \masm32\include\user32.inc <br />includelib \masm32\lib\user32.lib            ; 指明要调用user32.lib和kernel32.lib 两个库中的函数<br />include \masm32\include\kernel32.inc <br />includelib \masm32\lib\kernel32.lib </p><p>WinMain proto :DWORD,:DWORD,:DWORD,:DWORD </p><p><font color="#0000ff">.DATA</font>                     ;初始化数据<br />ClassName db "<font color="#ffa500">SimpleWinClass</font>",0        ; Windows窗口类的名字 <br />AppName db "<font color="#ffa500">Our First Window</font>",0        ; 窗口的名字 </p><p><font color="#0000ff">.DATA?</font>                ; 未初始化数据<br />hInstance HINSTANCE ?        ;本程序的实例句柄 <br />CommandLine LPSTR ? <br /><br /><font color="#0000ff">.CODE</font>                ; 从这里开始我们的代码 <br />start: <br /><br />invoke <font color="#0000ff">GetModuleHandle</font>, NULL    ;<font color="#0000ff">获得本程序实例句柄</font><font color="#0000ff">,在WIN32下hmodule==hinstance</font><br />mov hInstance,eax <br /><br />invoke GetCommandLine ; <font color="#0000ff">得到命令行参数.如果你的程序不处理命令行就无需调用此函数<br /></font>mov CommandLine,eax <br /><br />invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT    ; 调用主函数 <br />invoke <font color="#0000ff">ExitProcess</font>, eax                           ; 退出程序，退出码由WinMain函数返回到了eax </p><p>WinMain proc <font color="#0000ff">hInst</font>:HINSTANCE,<font color="#0000ff">hPrevInst</font>:HINSTANCE,<font color="#0000ff">CmdLine</font>:LPSTR,<font color="#0000ff">CmdShow</font>:DWORD <br />    LOCAL wc:WNDCLASSEX                                            ; 创建局部变量<br />    LOCAL msg:MSG <br />    LOCAL hwnd:HWND </p><p>    mov   wc.cbSize,SIZEOF WNDCLASSEX                   ; 填入wc结构的大小 <br />    mov   wc.style, CS_HREDRAW or CS_VREDRAW <br />    mov   wc.lpfnWndProc, OFFSET WndProc <br />    mov   wc.cbClsExtra,NULL <br />    mov   wc.cbWndExtra,NULL <br />    push  hInstance <br />    pop   wc.hInstance <br />    mov   wc.hbrBackground,COLOR_WINDOW+1 <br />    mov   wc.lpszMenuName,NULL <br />    mov   wc.lpszClassName,OFFSET ClassName <br />    invoke LoadIcon,NULL,IDI_APPLICATION <br />    mov   wc.hIcon,eax <br />    mov   wc.hIconSm,eax <br />    invoke LoadCursor,NULL,IDC_ARROW <br />    mov   wc.hCursor,eax <br />    invoke <font color="#0000ff">RegisterClassEx</font>, addr wc                       ; <font color="#0000ff">注册窗口类</font><br />    invoke <font color="#0000ff">CreateWindowEx</font>,NULL,\                       ；<font color="#0000ff">创建窗口</font><br />                ADDR ClassName,\ <br />                ADDR AppName,\ <br />                WS_OVERLAPPEDWINDOW,\ <br />                CW_USEDEFAULT,\ <br />                CW_USEDEFAULT,\ <br />                CW_USEDEFAULT,\ <br />                CW_USEDEFAULT,\ <br />                NULL,\ <br />                NULL,\ <br />                hInst,\ <br />                NULL <br />    <font color="#0000ff">mov   hwnd,eax</font>   ;将CreatwindowEx成功调用后返回到eax中的窗口句柄放入到hwnd保存<br />    invoke <font color="#0000ff">ShowWindow</font>, hwnd,CmdShow               ; 在桌边显示窗口 <br />    invoke <font color="#0000ff">UpdateWindow</font>, hwnd                                 ; 更新客户区 </p><p>    <font color="#0000ff">.WHILE </font><font color="#ff0000">TRUE</font>                                                         ; 进入消息循环 <br />                invoke <font color="#0000ff">GetMessage</font>, ADDR msg,NULL,0,0  ;从消息队列获取消息,放入eax<br />                .BREAK .IF (!eax)                                       ;有消息继续执行,无消息则退出循环<br />                invoke <font color="#0000ff">TranslateMessage</font>, ADDR msg <br />                invoke <font color="#0000ff">DispatchMessage</font>, ADDR msg <br />   .ENDW <br />    mov     eax,msg.wParam                                            ; 将退出码返回到eax中 <br />    ret <br />WinMain endp </p><p>WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM <br />    .IF uMsg==<font color="#ff1493">WM_DESTROY</font>                           ; 如果用户点击了关闭按钮<br />        invoke <font color="#0000ff">PostQuitMessage</font>,NULL             ; 退出应用程序<br />    .ELSE <br />        invoke <font color="#0000ff">DefWindowProc</font>,hWnd,uMsg,wParam,lParam     ; 缺省的窗口消息处理函数。<br />        ret <br />    .ENDIF <br />    xor eax,eax <br />    ret <br />WndProc endp </p><p>end start <br /></p><p><font color="#0000ff"><img height="254" alt="window.png" src="http://www.blogjava.net/images/blogjava_net/cuiyuelei/window.png" width="401" border="0" /><br /><br />分析：</font>                                                        <br /><br />你一定会吃惊一个简单的窗口程序竟然会有如此之多的代码，但是您必须要知道上面的大多数代码都是模板而已，模板的意思是指这些代码对差不多所有标准 Windows 程序来说都是相同的。在写 Windows 程序时您可以把这些代码拷来拷去，当然把这些重复的代码写到一个库中也挺好。其实真正要写的代码集中在 WinMain 中。这和一些 C 编译器一样，无须要关心其它杂务，集中精力于 WinMain 函数。唯一不同的是 C 编译器要求您的源代码有必须有一个函数叫 WinMain。否则 ，C 无法知道将哪个函数和有关的前后代码链接。相对C，汇编语言提供了较大的灵活性，它不强行要求一个叫 WinMain 的函数。</p><p> 下面我们开始分析，您可得做好思想准备，这可不是一件太轻松的活。 </p><p><font color="#0000ff"> .386 <br />.model flat,stdcall <br />option casemap:none</font></p><p>WinMain proto :DWORD,:DWORD,:DWORD,:DWORD </p><p>include \masm32\include\windows.inc <br />include \masm32\include\user32.inc <br />include \masm32\include\kernel32.inc <br />includelib \masm32\lib\user32.lib <br />includelib \masm32\lib\kernel32.lib</p><p>您可以把前三行看成是"<font color="#0000ff">必须</font>"的。<br /><font color="#0000ff">.386</font>告诉MASN我们要用80386指令集。<br /><font color="#0000ff">. model flat,stdcall</font>告诉MASM 我们用的内存寻址模式，此处也可以加入stdcall告诉MASM我们所用的参数传递约定。<br />        <br />接下来是函数 WinMain 的原型申明，因为我们稍后要用到该函数，故必须先声明。 <br />       <br />我们必须包含<font color="#0000ff">window.inc</font>文件，因为其中包含大量要用到的常量和结构的定义，该文件是一个文本文件，您可以用任何文本编辑器打开它，window.inc还没有包含所有的常量和结构定义，不过 hutch 和我一直在不断加入新的内容。如果暂时在 window.inc 找不到，您也可以自行加入。<br />       <br />我们的程序调用驻扎在<font color="#0000ff">user32.dll</font> (譬如：CreateWindowEx， RegisterWindowClassEx) 和 kernel32.dll (ExitProcess)中的函数，所以必须链接这两个库。接下来我如果问：您需要把什么库链入您的程序呢 ?  答案是：先查到您要调用的函数在什么库中，然后包含进来。譬如：若您要调用的函数在 gdi32.dll 中，您就要包含gdi32.inc头文件。</p><p>和MASM 相比，TASM 则要简单得多，您只要引入一个库，即：import32.lib。</p><p> .DATA <br />    ClassName db "SimpleWinClass",0 <br />    AppName  db "Our First Window",0 </p><p>.DATA? <br />hInstance HINSTANCE ? <br />CommandLine LPSTR ?</p><p>接下来是"DATA"分段"。<br /><br />在 .DATA 中我们定义了两个以 NULL 结尾的字符串 (ASCIIZ)：其中 <font color="#0000ff">ClassName </font>是 Windows 类名，<font color="#0000ff">AppName</font> 是我们窗口的名字。这两个变量都是初始化了的。</p><p>在 .DATA? 中放了两个未进行初始化的变量，其中 hInstance 代表应用程序的句柄，CommandLine 保存从命令行传入的参数。</p><p>HINSTACE 和 LPSTR 是两个数据类型名，它们在头文件中定义，可以看做是 DWORD 的别名，之所以要这么重新定义仅是为了易记。您可以查看 windows.inc 文件，在 .DATA? 中的变量都是未经初始化的，这也就是说在程序刚启动时它们的值是什么无关紧要，只不过占有了一块内存，以后可以再利用而已。</p><p> .CODE <br /> start: <br />     invoke GetModuleHandle, NULL <br />     mov    hInstance,eax <br />     invoke GetCommandLine <br />     mov    CommandLine,eax <br />     invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT <br />     invoke ExitProcess,eax <br />     ..... <br />end start</p><p> .CODE "分段"包含了您应用程序的所有代码，这些代码必须都在 .code 和 end 之间。至于 label 的命名只要遵从MASM规范而且保证唯一则具体叫什么倒是无所谓。</p><p> 我们程序的第一条语句是调用 GetModuleHandle 去查找我们应用程序的句柄。在Win32下，应用程序的句柄和模块的句柄是一样的。您可以把实例句柄看成是您的应用程序的 ID 号。我们在调用几个函数时都把它作为参数来进行传递，所以在一开始便得到并保存它就可以省许多的事。</p><p> 特别注意：WIN32下的实例句柄实际上是您应用程序在内存中的线性地址。</p><p> WIN32 中函数的函数如果有返回值，那它是通过 eax 寄存器来传递的。其他的值可以通过传递进来的参数地址进行返回。</p><p> 一个 WIN32 函数被调用时总会保存好段寄存器和 ebx，edi，esi和ebp 寄存器，而 ecx和edx 中的值总是不定的，不能在返回时应用。</p><p>特别注意：从Windows API 函数中返回后，eax，ecx，edx 中的值和调用前不一定相同。</p><p>当函数返回时，返回值放在eax中。如果您应用程序中的函数提供给 Windows 调用时，也必须遵守这一点，即在函数入口处保存段寄存器和 ebx，esp，esi，edi 的值并在函数返回时恢复。如果不这样一来的话，您的应用程序很快会崩溃。从您的程序中提供给 Windows 调用的函数大体上有两种：Windows 窗口过程和 Callback 函数。如果您的应用程序不处理命令行那么就无须调用 GetCommandLine，这里只是告诉您如果要调用应该怎么做。 </p><p>下面则是调用WinMain了。该函数共有4个参数：应用程序的实例句柄，该应用程序的前一实例句柄，命令行参数串指针和窗口如何显示。Win32 没有前一实例句柄的概念，所以第二个参数总为0。之所以保留它是为了和 Win16 兼容的考虑，在 Win16下，如果 hPrevInst 是 NULL，则该函数是第一次运行。</p><p>特别注意：您不用必须申明一个名为 WinMain 函数，事实上在这方面您可以完全作主，您甚至无须有一个和 WinMain 等同的函数。您只要把 WinMain 中的代码拷到GetCommandLine 之后，其所实现的功能完全相同。在 WinMain 返回时，把返回码放到 eax 中。然后在应用程序结束时通过 ExitProcess 函数把该返回码传递给 Windows 。</p><p>WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD </p><p>上面是WinMain的定义。注意跟在 proc 指令后的parameter：type形式的参数，它们是由调用者传给 WinMain 的，我们引用是直接用参数名即可。至于压栈和退栈时的平衡堆栈工作由 MASM 在编译时加入相关的前序和后序汇编指令来进行。</p><p>    LOCAL wc:WNDCLASSEX <br />    LOCAL msg:MSG <br />    LOCAL hwnd:HWND </p><p>LOCAL 伪指令为局部变量在栈中分配内存空间，所有的 LOCAL 指令必须紧跟在 PROC 之后。LOCAL 后跟声明的变量，其形式是 <font color="#0000ff">变量名:变量类型</font>。譬如 LOCAL wc：WNDCLASSEX 即是告诉 MASM 为名字叫 wc 的局部边量在栈中分配长度为 WNDCLASSEX 结构体长度的内存空间，然后我们在用该局部变量是无须考虑堆栈的问题，考虑到 DOS 下的汇编，这不能不说是一种恩赐。不过这就要求这样申明的局部变量在函数结束时释放栈空间，(也即不能在函数体外被引用)，另一个缺点是您因不能初始化您的局部变量，不得不在稍后另外再对其赋值。</p><p>    mov   wc.cbSize,SIZEOF WNDCLASSEX <br />    mov   wc.style, CS_HREDRAW or CS_VREDRAW <br />    mov   wc.lpfnWndProc, OFFSET WndProc <br />    mov   wc.cbClsExtra,NULL <br />    mov   wc.cbWndExtra,NULL <br />    push  hInstance <br />    pop   wc.hInstance <br />    mov   wc.hbrBackground,COLOR_WINDOW+1 <br />    mov   wc.lpszMenuName,NULL <br />    mov   wc.lpszClassName,OFFSET ClassName <br />    invoke LoadIcon,NULL,IDI_APPLICATION <br />    mov   wc.hIcon,eax <br />    mov   wc.hIconSm,eax <br />    invoke LoadCursor,NULL,IDC_ARROW <br />    mov   wc.hCursor,eax <br />    invoke RegisterClassEx, addr wc </p><p>上面几行从概念上说确实是非常地简单。只要几行指令就可以实现。其中的主要概念就是窗口类（window class），一个窗口类就是一个有关窗口的规范，这个规范定义了几个主要的窗口的元素，如：图标、光标、背景色、和负责处理该窗口的函数。您产生一个窗口时就必须要有这样的一个窗口类。如果您要产生不止一个同种类型的窗口时，最好的方法就是把这个窗口类存储起来，这种方法可以节约许多的内存空间。也许今天您不会太感觉到，可是想想以前 PC 大多数只有 1M 内存时，这么做是非常有必要的。如果您要定义自己的创建窗口类就必须：在一个 WINDCLASS 或 WINDOWCLASSEXE 结构体中指明您窗口的组成元素，然后调用 RegisterClass 或 RegisterClassEx ，再根据该窗口类产生窗口。对不同特色的窗口必须定义不同的窗口类。 WINDOWS有几个预定义的窗口类，譬如：按钮、编辑框等。要产生该种风格的窗口无须预先再定义窗口类了，只要包预定义类的类名作为参数调用 CreateWindowEx 即可。</p><p>WNDCLASSEX 中最重要的成员莫过于lpfnWndProc了。前缀 lpfn 表示该成员是一个指向函数的长指针。在 Win32中由于内存模式是 FLAT 型，所以没有 near 或 far 的区别。每一个窗口类必须有一个窗口过程，当 Windows 把属于特定窗口的消息发送给该窗口时，该窗口的窗口类负责处理所有的消息，如键盘消息或鼠标消息。由于窗口过程差不多智能地处理了所有的窗口消息循环，所以您只要在其中加入消息处理过程即可。下面我将要讲解 WNDCLASSEX 的每一个成员</p><p>WNDCLASSEX STRUCT DWORD <br />  cbSize            DWORD      ? <br />  style             DWORD      ? <br />  lpfnWndProc       DWORD      ? <br />  cbClsExtra        DWORD      ? <br />  cbWndExtra        DWORD      ? <br />  hInstance         DWORD      ? <br />  hIcon             DWORD      ? <br />  hCursor           DWORD      ? <br />  hbrBackground     DWORD      ? <br />  lpszMenuName      DWORD      ? <br />  lpszClassName     DWORD      ? <br />  hIconSm           DWORD      ? <br />WNDCLASSEX ENDS </p><p><font color="#0000ff">cbSize：</font>WNDCLASSEX 的大小。我们可以用sizeof(WNDCLASSEX)来获得准确的值。</p><p><font color="#0000ff">style</font>：从这个窗口类派生的窗口具有的风格.您可以用"or"操作符来把几个风格或到一起。 </p><p><font color="#0000ff">lpfnWndProc：</font>窗口处理函数的指针</p><p><font color="#0000ff">cbClsExtra：</font>指定紧跟在窗口类结构后的附加字节数。不使用的话其值就为0；</p><p><font color="#0000ff">cbWndExtra：</font>指定紧跟在窗口实例后的附加字节数。如果一个应用程序在资源中用CLASS伪指令注册一个对话框类时,则必须把这个成员设成DLGWINDOWEXTRA,不使用的话其值就为0；</p><p><font color="#0000ff">hInstance：</font>本模块的实例句柄。</p><p><font color="#0000ff">hIcon：</font>图标的句柄。</p><p><font color="#0000ff">hCursor：</font>光标的句柄。</p><p><font color="#0000ff">hbrBackground：</font>背景画刷的句柄。</p><p><font color="#0000ff">lpszMenuName：</font>指向菜单的指针。</p><p><font color="#0000ff">lpszClassName：</font>指向类名称的指针。</p><p><font color="#0000ff">hIconSm：</font>和窗口类关联的小图标。如果该值为NULL。则把hCursor中的图标转换成大小合适的小图标。</p><p>    invoke CreateWindowEx, NULL,\ <br />                                                ADDR ClassName,\ <br />                                                ADDR AppName,\ <br />                                                WS_OVERLAPPEDWINDOW,\ <br />                                                CW_USEDEFAULT,\ <br />                                                CW_USEDEFAULT,\ <br />                                                CW_USEDEFAULT,\ <br />                                                CW_USEDEFAULT,\ <br />                                                NULL,\ <br />                                                NULL,\ <br />                                                hInst,\ <br />                                                NULL </p><p>注册窗口类后，我们将调用CreateWindowEx来产生实际的窗口。请注意该函数有12个参数。</p><p>CreateWindowExA proto dwExStyle:DWORD,\ <br />   lpClassName:DWORD,\ <br />   lpWindowName:DWORD,\ <br />   dwStyle:DWORD,\ <br />   X:DWORD,\ <br />   Y:DWORD,\ <br />   nWidth:DWORD,\ <br />   nHeight:DWORD,\ <br />   hWndParent:DWORD ,\ <br />   hMenu:DWORD,\ <br />   hInstance:DWORD,\ <br />   lpParam:DWORD </p><p>我们来仔细看一看这些的参数：</p><p><font color="#0000ff">dwExStyle：</font>附加的窗口风格。相对于旧的CreateWindow这是一个新的参数。在9X/NT中您可以使用新的窗口风格。您可以在Style中指定一般的窗口风格，但是一些特殊的窗口风格，如顶层窗口则必须在此参数中指定。如果您不想指定任何特别的风格，则把此参数设为NULL。 </p><p><font color="#0000ff">lpClassName:（</font>必须）。ASCIIZ形式的窗口类名称的地址。可以是您自定义的类，也可以是预定义的类名。像上面所说，每一个应用程序必须有一个窗口类。 </p><p><font color="#0000ff">lpWindowName：</font>ASCIIZ形式的窗口名称的地址。该名称会显示在标题条上。如果该参数空白，则标题条上什么都没有。</p><p><font color="#0000ff">dwStyle：</font>窗口的风格。在此您可以指定窗口的外观。可以指定该参数为零，但那样该窗口就没有系统菜单，也没有最大化和最小化按钮，也没有关闭按钮，那样您不得不按Alt+F4 来关闭它。最为普遍的窗口类风格是 WS_OVERLAPPEDWINDOW。 一种窗口风格是一种按位的掩码，这样您可以用“or”把您希望的窗口风格或起来。像 WS_OVERLAPPEDWINDOW 就是由几种最为不便普遍的风格或起来的。 </p><p><font color="#0000ff">X，Y：</font>指定窗口左上角的以像素为单位的屏幕坐标位置。缺省地可指定为 CW_USEDEFAULT，这样 Windows 会自动为窗口指定最合适的位置。 </p><p><font color="#0000ff">nWidth， nHeight：</font>以像素为单位的窗口大小。缺省地可指定为 CW_USEDEFAULT，这样 Windows 会自动为窗口指定最合  适的大小。 </p><p><font color="#0000ff">hWndParent：</font>父窗口的句柄（如果有的话）。这个参数告诉 Windows 这是一个子窗口和他的父窗口是谁。这和 MDI（多文档结构）不同，此处的子窗口并不会局限在父窗口的客户区内。他只是用来告诉 Windows 各个窗口之间的父子关系，以便在父窗口销毁是一同把其子窗口销毁。在我们的例子程序中因为只有一个窗口，故把该参数设为 NULL。 </p><p><font color="#0000ff">hMenu：</font>WINDOWS菜单的句柄。如果只用系统菜单则指定该参数为NULL。回头看一看WNDCLASSEX 结构中的 lpszMenuName 参数，它也指定一个菜单，这是一个缺省菜单，任何从该窗口类派生的窗口若想用其他的菜单需在该参数中重新指定。其实该参数有双重意义：一方面若这是一个自定义窗口时该参数代表菜单句柄，另一方面，若这是一个预定义窗口时，该参数代表是该窗口的 ID 号。Windows 是根据lpClassName 参数来区分是自定义窗口还是预定义窗口的。 </p><p><font color="#0000ff">hInstance：</font> 产生该窗口的应用程序的实例句柄。</p><p><font color="#0000ff">lpParam：（</font>可选）指向欲传给窗口的结构体数据类型参数的指针。如在MDI中在产生窗口时传递 CLIENTCREATESTRUCT 结构的参数。一般情况下，该值总为零，这表示没有参数传递给窗口。可以通过GetWindowLong 函数检索该值。 </p><p>    mov   hwnd,eax <br />    invoke ShowWindow, hwnd,CmdShow <br />    invoke UpdateWindow, hwnd </p><p>调用CreateWindowEx成功后，将会返回窗口句柄于eax中。我们必须保存该值以备后用。我们刚刚产生的窗口不会自动显示，所以必须调用 ShowWindow 来按照我们希望的方式来显示该窗口。接下来调用 UpdateWindow 来更新客户区。</p><p>    .WHILE TRUE <br />                invoke GetMessage, ADDR msg,NULL,0,0 <br />                .BREAK .IF (!eax) <br />                invoke TranslateMessage, ADDR msg <br />                invoke DispatchMessage, ADDR msg <br />   .ENDW </p><p>这时候我们的窗口已显示在屏幕上了。但是它还不能从外界接收消息。所以我们必须给它提供相关的消息。我们是通过一个消息循环来完成该项工作的。每一个模块仅有一个消息循环，我们不断地调用 GetMessage 从 Windows 中获得消息。GetMessage 传递一个 MSG 结构体给 Windows ，然后 Windows 在该函数中填充有关的消息，一直到 Windows 找到并填充好消息后 GetMessage 才会返回。在这段时间内系统控制权可能会转移给其他的应用程序。这样就构成了Win16下的多任务结构。</p><p>如果 GetMessage 接收到 WM_QUIT 消息后就会返回 FALSE，使循环结束并退出应用程序。</p><p>TranslateMessage 函数是一个是实用函数，它从键盘接受原始按键消息，然后解释成 WM_CHAR，在把 WM_CHAR 放入消息队列，由于经过解释后的消息中含有按键的 ASCII 码，这比原始的扫描码好理解得多。如果您的应用程序不处理按键消息的话，可以不调用该函数。     </p><p>DispatchMessage 会把消息发送给负责该窗口过程的函数。</p><p>mov   eax,msg.wParam <br />    ret <br />WinMain endp </p><p>如果消息循环结束了，退出码存放在 MSG 中的 wParam中，您可以通过把它放到 eax 寄存器中传给 Windows目前 Windows 没有利用到这个结束码，但我们最好还是遵从Windows规范已防意外。</p><p><font color="#0000ff">WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM</font></p><p>是我们的窗口处理函数。您可以随便给该函数命名。其中第一个参数 hWnd 是接收消息的窗口的句柄。uMsg 是接收的消息。注意 uMsg 不是一个 MSG 结构，其实上只是一个 DWORD 类型数。Windows 定义了成百上千个消息，大多数您的应用程序不会处理到。当有该窗口的消息发生时，Windows 会发送一个相关消息给该窗口。其窗口过程处理函数会智能的处理这些消息。wParam 和 lParam 只是附加参数，以方便传递更多的和该消息有关的数据。</p><p>    .IF uMsg==WM_DESTROY <br />        invoke PostQuitMessage,NULL <br />    .ELSE <br />        invoke DefWindowProc,hWnd,uMsg,wParam,lParam <br />        ret <br />    .ENDIF <br />    xor eax,eax <br />    ret <br />WndProc endp </p><p>上面可以说是关键部分。这也是我们写 Windows 程序时需要改写的主要部分。此处您的程序检查 Windows 传递过来的消息，如果是我们感兴趣的消息则加以处理，处理完后，在 eax 寄存器中传递 0，否则必须调用 DefWindowProc，把该窗口过程接收到的参数传递给缺省的窗口处理函数。</p><p> 所有消息中您必须处理的是 WM_DESTROY，当您的应用程序结束时 Windows 把这个消息传递进来，当您的应用程序解说到该消息时它已经在屏幕上消失了，这仅是通知您的应用程序窗口已销毁，您必须自己准备返回 Windows 。在此消息中您可以做一些清理工作，但无法阻止退出应用程序。如果您要那样做的话，可以处理 WM_CLOSE 消息。在处理完清理工作后，您必须调用 PostQuitMessage，该函数会把 WM_QUIT 消息传回您的应用程序，而该消息会使得 GetMessage 返回，并在 eax 寄存器中放入 0，然后会结束消息循环并退回 WINDOWS。您可以在您的程序中调用 DestroyWindow 函数，它会发送一个 WM_DESTROY 消息给您自己的应用程序，从而迫使它退出。<br /></p><hr /><p><strong>[ <a href="http://win32asm.cjb.net Iczelion" temp_href="http://win32asm.cjb.net Iczelion"><font color="#4371a6">Iczelion's Win32 Assembly HomePage</font></a>] 需要代理服务器，但可通过以下镜像进行访问<br /></strong><a href="http://win32assembly.online.fr/"><strong><font color="#4371a6">http://win32assembly.online.fr/</font></strong></a><strong>                 </strong><a href="http://users.daex.ufsc.br/~iczelion/"><strong><font color="#4371a6">http://users.daex.ufsc.br/~iczelion/</font></strong></a><strong>  </strong><a href="http://spiff.tripnet.se/~Iczelion"><strong><font color="#4371a6">http://spiff.tripnet.se/~Iczelion</font></strong></a></p><img src ="http://www.blogjava.net/cuiyuelei/aggbug/103665.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cuiyuelei/" target="_blank">催月泪(Jaclick)</a> 2007-03-13 23:37 <a href="http://www.blogjava.net/cuiyuelei/archive/2007/03/13/103665.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Iczelion's Win32 Assembly Tutorial 2: 消息框</title><link>http://www.blogjava.net/cuiyuelei/archive/2007/02/27/100883.html</link><dc:creator>催月泪(Jaclick)</dc:creator><author>催月泪(Jaclick)</author><pubDate>Tue, 27 Feb 2007 02:45:00 GMT</pubDate><guid>http://www.blogjava.net/cuiyuelei/archive/2007/02/27/100883.html</guid><wfw:comment>http://www.blogjava.net/cuiyuelei/comments/100883.html</wfw:comment><comments>http://www.blogjava.net/cuiyuelei/archive/2007/02/27/100883.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cuiyuelei/comments/commentRss/100883.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cuiyuelei/services/trackbacks/100883.html</trackback:ping><description><![CDATA[翻译：Lxx，校对：LuoYunBin's Win32 ASM Page，<font color="#0000ff"><a href="http://asm.yeah.net" target="_blank"><font face="Arial">http://asm.yeah.net<br /></font><br /></a><a href="/Files/cuiyuelei/Iczelion_Win32ASM.zip">点击此处下载全教程</a><a href="http://asm.yeah.net" target="_blank"><br /></a></font><hr /><br />在本课中，我们将用汇编语言写一个Windows程序，程序运行时将弹出一个消息框并显示"Win32 assembly is great!"。点击这里下载例程<a href="/Files/cuiyuelei/Tutorial2.zip"><font color="#0000ff">here</font></a>. <br /><br /><font color="#0000ff">理论：</font><br /><br />Windows 为编写应用程序提供了大量的资源。其中最重要的是<font color="#0000ff">Windows API</font> (Application Programming Interface)。<font color="#0000ff">Windows API</font>是一大组功能强大的函数，它们本身驻扎在Windows 中供人们随时调用。这些函数的大部分被包含在几个动态链接库(DLL)中，譬如：kernel32.dll、user32.dll 和 gdi32.dll。 <font color="#0000ff">Kernel32.dll</font>中的函数主要处理内存管理和进程调度；<font color="#0000ff">user32.dll </font>中的函数主要控制用户界面；<font color="#0000ff">gdi32.dll </font>中的函数则负责图形方面的操作。除了上面主要的三个动态链接库，您还可以调用包含在其他动态链接库中的函数，当然您必须要有关于这些函数的足够的资料。<br /><br /><font color="#0000ff">动态链接库</font>，顾名思义，这些API的代码本身并不包含在Windows可执行文件中，而是当要使用时才被加载。为了让应用程序在运行时能找到这些函数，就必须事先把有关的重定位信息嵌入到应用程序的可执行文件中。这些信息存在于引入库中，由链接器把相关信息从引入库中找出插入到可执行文件中。您必须指定正确的引入库，因为只有正确的引入库才会有正确的重定位信息。<br /><br />当应用程序被加载时Windows会检查这些信息，这些信息包括动态链接库的名字和其中被调用的函数的名字。若检查到这样的信息，Windows就会加载相应的动态链接库，并且重定位调用的函数语句的入口地址，以便在调用函数时控制权能转移到函数内部。<br /><br />如果从与字符集的相关性来分，API 共有两类：一类是处理 ANSI 字符集的，另一类是处理 UNICODE 字符集的。前一类函数名字的尾部带有一个"A"字符的后缀，处理UNICODE的则带有一个"W"字符的后缀(我想"W"也许是代表宽字符的意思吧)。<br /><br />我们比较熟悉的ANSI字符串是以 NULL 结尾的一串字符数组，每一个ANSI字符是一个BYTE 宽。对于欧洲语言体系，ANSI 字符集已足够了，但对于有成千上万个唯一字符的几种东方语言体系来说就只有用 UNICODE 字符集了。每一个 UNICODE 字符占有两个BYTE 宽，这样一来就可以在一个字符串中使用65336个不同字符了。<br /><br />Windows95仅支持ANSI，Windows98/ME只有极少的函数支持UNICODE，Windows NT则从底层支持UNICODE。但在更多的时候，你仅仅只需提供API函数名字而不需要后缀，即可因你的平台来决定和选择适当的函数。<br /><br /><font color="#0000ff">例子：</font><br /><br />我先把框架程序放在下面，然后我们再向里面加东西。<br /><br /><b>.386<br />.model flat, stdcall</b><br /><b>.data</b><br /><b>.code</b><br /><b><font color="#0000ff">start:</font></b><br /><b>end </b><b><font color="#0000ff">start</font></b><br /><br />应用程序的执行是从 END <label><font color="#0000ff">定义的</font><label><font color="#0000ff">标识符后的第一条语句开始的</font>。在上面的框架程序中就是从START开始。程序逐条语句执行一直到遇到 JMP，JNE，JE，RET 等跳转指令。这些跳转指令将把执行权转移到其他语句上，若程序要退出Windows，则必须调用函数<font face="Georgia" color="#ff0000" size="4">ExitProcess</font>。 <br /><br /><font color="#0000ff"><b>ExitProcess</b><b> proto uExitCode:DWORD</b></font><br /><br />上面一行是函数原型。函数原型会告诉编译器和链接器该函数的属性，这样在编译和链接时，编译器和链接器就会作相关的类型检查。 函数的原型定义如下：<br /><br /><font color="#0000ff"><b>FunctionName</b><b> PROTO </b><b>[ParameterName]</b><b>:DataType</b><b>,</b><b>[ParameterName]</b><b>:DataType, ...</b></font><br /><br />简言之，就是在函数名后加伪指令PROTO，再跟一串由逗号相隔的数据类型链表。在前面的ExitProcess 定义中，该函数有一个DWORD 类型的参数。当您使用高层调用语句INVOKE时，使用函数原型定义特别有用，您可以简单地认为 INVOKE 是一个有参数类型检查的调用语句。譬如，假设您这样写： <br /><b><br />call</b><b> ExitProcess</b><b></b><br /><br />若您事先没把一个DWORD类型参数压入堆栈，编译器和链接器都不会报错，但毫无疑问，在您的程序运行时将引起崩溃。但是，当您这样写： <br /><b><br /><font color="#0000ff" size="4">invoke</font></b><font size="4"><b></b><b>ExitProcess</b><b></b><br /></font><br />连接器将报错提醒您忘记压入一个 DWORD 类型参数。所以我建议您用 INVOKE 指令而不是CALL去调用一个函数。INVOKE 的语法如下： <br /><b><br />INVOKE  <font size="4">expression</font></b><font size="4"><b> [</b><b>,arguments</b><b>]</b><br /></font><br /><font color="#0000ff"><font size="4">expression</font></font>既可以是一个函数名也可以是一个函数指针。参数由逗号隔开。大多数API函数的原型放在头文件中。 如果您用的是<font size="4">hutch（人名）</font>的MASM32，这些头文件在文件夹MASM32/include 下， 这些头文件的扩展名为 INC，函数名和DLL 中的函数名相同，譬如：KERNEL32.LIB 引出的函数 ExitProcess 的函数原形声明于KERNEL32.INC中。 <br /><br />您也可以自己声明函数原型。在我的教学课程中都使用<font size="4">hutch</font>的<font size="4">windows.inc</font>，这些头文件您可以从<a href="http://win32asm.cjb.net">http://win32asm.cjb.net</a>（需要代理）下载。 <br /><br />好，我们现在回到ExitProcess 函数，参数uExitCode 是您希望当您的应用程序结束时传递给Windows的。您可以这样写： <br /><b><br /><font color="#0000ff">invoke</font></b><font color="#0000ff"><b> ExitProcess</b><b>, 0</b></font><br /><br />把这一行放到开始标识符下，这个应用程序就会立即退出Windows，当然毫无疑问这个应用程序本身是一个完整的Windows程序。 <br /><b><br />.386</b><br /><b>.model </b><b>flat, stdcall</b><br /><font color="#0000ff"><b>option </b><b>casemap:none</b></font><br /><b><font color="#0000ff">include</font></b><b>\masm32\</b><b>include</b><b>\windows.inc</b><br /><b><font color="#0000ff">include</font></b><b>\masm32\</b><b>include</b><b>\kernel32.inc</b><br /><b><font color="#0000ff">includelib</font></b><b> \masm32\lib\kernel32.lib</b><br /><b>.data</b><br /><b>.code</b><br /><b>start:</b><br /><b>        <font color="#0000ff">invoke</font></b><b> ExitProcess</b><b>,0</b><br /><b>end </b><b>start</b><br /><b><br /><font color="#0000ff">option casemap</font></b><font color="#0000ff"><b>：</b><b>none</b><b></b></font>一句的意思是告诉 MASM 要区分标号的大小写，譬如：start 和 START 是不同的。请注意新的伪指令 include，跟在其后的文件名所指定的文件在编译时将“插”在该处。在我们上面的程序段中，当MASM处理到语句 include \masm\include\windows.inc 时，它就会打开文件夹\MASM32\include 中的文件windows.inc，这和您把整个文件都粘贴到您的源程序中的效果是一样的。hutch 的 windows.inc 包含了 WIN32 编程所需要的常量和结构体的定义。 但是它不包含函数原型的定义。尽管hutch和我尽力包含所有的常量和结构体的定义，但仍会有不少遗漏，为此我们将不断加入新的内容。请随时注意我们主页，下载最新的头文件。 <br /><br />您的应用程序除了从windows.inc中得到相关变量结构体的定义外，还需要从其他的头文件中得到函数原型的声明，这些头文件都放在 \masm32\include 文件夹中。 <br /><br />在我们上面的例子中调用了驻扎在 kernel32.dll 中的函数，所以需要包含有这个函数原型声明的头文件 kernel32.inc。如果用文本编辑器打开该文件您会发现里面全是从 kernel32.dll中引出的函数的声明。如果您不包含kernel32.inc，您仍然可以调用（call）ExitProcess，但不能够调用（invoke）ExitProcess（这会无法通过编译器和连接器的参数合法性检查）。所以若用 invoke 去调用一个函数，您就必须事先声明，当然不一定要包含我们的头文件，您完全可以在调用该函数前在源代码的适当位置进行声名。包含头文件主要是为了节省时间（译者：当然还有正确性）<br />      <br />接下来我们来看看<b>includelib </b>伪指令，和 include 不同，它仅仅是告诉编译器您的程序引用了哪个库。当编译器处理到该指令时会在生成的目标文件中插入链接命令告诉链接器链入什么库。当然您还可以通过在链接器的命令行指定引入库名称的方法来达到和用includelib指令相同的目的，但考虑到命令行仅能够传递128个字符而且要不厌其烦地在命令行敲字符，所以这种方法是非常不可取的。 <br /><br />好了，现在保存例子，取名为msgbox.asm。把 ml.exe 的路径放到 PATH 环境变量中，键入下面一行 进行编译： <br /><b><br /><font color="#0000ff">ml  /c  /coff  /Cp msgbox.asm</font></b><font color="#0000ff"><b></b><br /></font><b><br /><font color="#0000ff" size="4">/c</font></b>是告诉MASM只编译不链接。这主要是考虑到在链接前您可能还有其他工作要做。 <br /><b><br /><font color="#0000ff" size="4">/coff</font></b> 告诉MASM产生的目标文件用coff格式。MASM的coff格式是COFF（Common Object File Format：通用目标文件格式）格式的一种变体。在UNIX下的COFF格式又有不同。 <br /><b><br /><font color="#0000ff">/Cp</font></b> 告诉 MASM 不要更改用户定义的标识符的大小写。若您用的是hutch的包含文件的话，在.model 指令下加入 "option casemap:none" 句，可达到同样的效果。 <br /><br />当您成功的编译了msgbox.asm 后，编译器会产生msgbox.obj 目标文件，目标文件和可执行文件只一步之遥，目标文件中包含了以二进制形式存在的指令和数据，比可执行文件相差的只是链接器加入的重定位信息。 <br /><br />好，我们来链接目标文件： <br /><b> </b><br /><font color="#0000ff"><b>link</b><b> /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj</b><br /></font><br /><font color="#0000ff"><b>/SUBSYSTEM:</b><b>WINDOWS</b></font>  告诉链接器可执行文件的运行平台 <br /><b><br /><font color="#0000ff">/LIBPATH:</font></b><b><font color="#0000ff">&lt;path to import library&gt;</font></b> 告诉链接器引入库的路径。<br /><br />链接器做的工作就是根据引入库往目标文件中加入重定位信息，最后产生msgbox.exe可执行文件。 既然得到了可执行文件，我们来运行一下。好，一、二、三，GO！屏幕上什么都没有。哦，对了，我们除了调用了 ExitProcess 函数外，什么都还没做呢！但是别一点成就感都没有，因为我们用汇编所写的是一个真正的Windows 程序，不信的话，查查您磁盘上的 msgbox.exe文件，在我的机器上它的大小足有1,536字节呢。 <br /><br />下面我们来做一点可以看的见摸的着的，我们在程序中加入一个对话框。该函数的原型如下： <br /><b><br /><font color="#0000ff">MessageBox</font></b><font color="#0000ff"><b> PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD</b><br /></font><b><br /><font color="#0000ff">hwnd</font></b><b></b>是父窗口的句柄。句柄代表您引用的窗口的一个地址指针。它的值对您编Windows 程序并不重要   <br />      （译者注：如果您想成为高手则是必须的），您只要知道它代表一个窗口。当您要对窗口做任何操作时，必须要引用该窗口的指针。 <br /><br /><b><font color="#0000ff">lpText</font></b><b></b>是指向您要显示的文本的指针。指向文本串的指针事实上就是文本串的首地址。 <br />  <br /><b><font color="#0000ff">lpCaption</font></b><b></b>是指向您要显示的对话框的标题文本串指针。 <br />  <br /><b><font color="#0000ff">uType</font></b><b></b>是显示在对话框窗口上的小图标的类型。 <br />  <br />我们修改 msgbox.asm 在其中加入MessageBox函数。   <br /><b> <br />.386</b><b><br /></b><b>.model </b><b>flat,stdcall</b><b><br /></b><font color="#0000ff"><b>option</b><b> casemap:none</b></font><b><br /></b><b><font color="#0000ff">include </font></b><b>\masm32\include\windows.inc</b><b><br /></b><b><font color="#0000ff">include </font></b><b>\masm32\include\kernel32.inc</b><b><br /></b><b><font color="#0000ff">includelib</font></b><b> \masm32\lib\kernel32.lib</b><b><br /></b><b><font color="#0000ff">include </font></b><b>\masm32\include\user32.inc</b><b><br /></b><b><font color="#0000ff">includelib</font></b><b> \masm32\lib\user32.lib</b><b></b><br /><b>.data</b><b></b><b><br /></b><b>MsgBoxCaption</b><b>  db </b><b></b><b>"<font color="#ffa500">Iczelion Tutorial No.2</font>"</b><b>, 0</b><b></b><b><br /></b><b>MsgBoxText</b><b>       db </b><b>"<font color="#ffa500">Win32 Assembly is Great</font><font color="#ffa500">!</font>"</b><b>, 0</b><b></b><b></b><br /><b>.code</b><b></b><b><br /></b><b><font color="#ff0000">start:</font></b><b></b><b><br /></b><b>invoke </b><b><font color="#0000ff">MessageBox</font></b><b>, NULL,</b><b><font color="#0000ff">addr </font></b><b>MsgBoxText</b><b>,</b><b><font color="#0000ff">addr </font></b><b>MsgBoxCaption</b><b>,</b><b><font color="#0000ff"> MB_OK</font></b><b><br /></b><b>invoke </b><b><font color="#0000ff">ExitProcess</font></b><b>, NULL</b><b><br /></b><b>end </b><b><font color="#ff0000">start</font></b><font color="#ff0000"><b></b><br /></font><br />编译、链接上面的程序段，得到可执行文件。运行，窗口上弹出了一个对话框，上面有一行字：Win32 Assembly is Great！。想一想，我们是用汇编写出来的，所以我们有理由为编写了一个最简单的WIN32程序感到高兴。（译者注：如果明天我们能够像在 DOS 下那样每一行都用汇编写，那我们有理由为自己感到自豪。）<br /><br /><img height="103" alt="msgbox.gif" src="http://www.blogjava.net/images/blogjava_net/cuiyuelei/msgbox.gif" width="266" border="0" /><br /><br />好，我们回过头来看看上面的源代码。我们在<font color="#0000ff">【.DATA】</font>分段定义了两个NULL结尾的字符串。我们用了两个常量：NULL 和 MB_OK。这些常量在<font size="4">windows.inc</font> 文件中有定义，使用常量使得您的程序有较好的可读性。 <br /><br />addr操作符用来把标号的地址传递给被调用的函数，它只能用在 invoke 语句中，譬如您不能用它来把标号的地址赋给寄存器或变量，如果想这样做则要用offset 操作符。在 offset 和 addr 之间有如下区别： <br /><br />1. addr不可以处理向前引用，offset则能。所谓向前引用是指：标号的定义是在invoke 语句之后，譬如在如下的例子： <br /><br />invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK <br />...... <br />MsgBoxCaption  db "Iczelion Tutorial No.2",0 <br />MsgBoxText       db "Win32 Assembly is Great!",0<br /><br />如果您是用addr而不是offset的话，那MASM就会报错。 <br /><br />2. <font size="4"> addr</font>可以处理局部变量而<font size="4">offset</font> 则不能。局部变量只是在运行时在堆栈中分配内存空间。而<font color="#000000" size="4">offset</font> 则是在编译时由编译器解释，这显然不能用<font size="4">offset</font>在运行时来分配内存空间。编译器对<font size="4">addr</font> 的处理是先检查处理的是全局还是局部变量，若是全局变量则把其地址放到目标文件中，这一点和<font size="4">offset</font>相同，若是局部变量，就在执行<font size="4">invoke</font> 语句前产生如下指令序列： <br /><br /><font color="#0000ff"><b>lea</b><b> eax, LocalVar</b></font><font color="#0000ff"><b><br /></b><b>push eax</b></font><br /><b> </b><br />因为<font size="4">lea</font>指令能够在运行时决定标号的有效地址，所以有了上述指令序列，就可以保证 <font size="4">invoke</font> 的正确执行了。<br /><b><br /><hr /><br />[ <b><a href="http://win32asm.cjb.net Iczelion" temp_href="http://win32asm.cjb.net Iczelion"><font color="#000080">Iczelion's Win32 Assembly HomePage</font></a></b><b>] 需要代理服务器，但可通过以下镜像进行访问<br /><a href="http://win32assembly.online.fr/"><font color="#000080">http://win32assembly.online.fr/</font></a>  <a href="http://users.daex.ufsc.br/~iczelion/"><font color="#000080">http://users.daex.ufsc.br/~iczelion/</font></a>  <a href="http://spiff.tripnet.se/~Iczelion"><font color="#000080">http://spiff.tripnet.se/~Iczelion</font></a></b></b></label></label><img src ="http://www.blogjava.net/cuiyuelei/aggbug/100883.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cuiyuelei/" target="_blank">催月泪(Jaclick)</a> 2007-02-27 10:45 <a href="http://www.blogjava.net/cuiyuelei/archive/2007/02/27/100883.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Iczelion's Win32 Assembly Tutorial 1:基础知识</title><link>http://www.blogjava.net/cuiyuelei/archive/2007/01/28/96397.html</link><dc:creator>催月泪(Jaclick)</dc:creator><author>催月泪(Jaclick)</author><pubDate>Sun, 28 Jan 2007 12:34:00 GMT</pubDate><guid>http://www.blogjava.net/cuiyuelei/archive/2007/01/28/96397.html</guid><wfw:comment>http://www.blogjava.net/cuiyuelei/comments/96397.html</wfw:comment><comments>http://www.blogjava.net/cuiyuelei/archive/2007/01/28/96397.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cuiyuelei/comments/commentRss/96397.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cuiyuelei/services/trackbacks/96397.html</trackback:ping><description><![CDATA[
		<div class="Section1">
				<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
						<font color="#000000">
								<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">翻译：</span>
								<span lang="EN-US">Lxx   </span>
								<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">校对：</span>
								<span lang="EN-US">LuoYunBin's Win32 ASM Page</span>
								<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，</span>
								<span lang="EN-US" style="COLOR: yellow; FONT-FAMILY: 'Arial Black'">
										<a href="http://asm.yeah.net/">
												<span style="COLOR: yellow">
														<font color="#000080">http://asm.yeah.net</font>
												</span>
										</a>
										<br />
										<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /?>
										<o:p>
										</o:p>
								</span>
						</font>
				</p>
				<br />
				<a href="/Files/cuiyuelei/Iczelion_Win32ASM.zip">点击此处下载全教程</a>
				<br />
				<hr />
				<br />我们先假设您已知道了如何使用MASM 。如果您还不知道的话，请下载<a href="http://www.movsd.com/"><font color="#000080">MASM32</font></a> ， 并请仔细研读其中所附带的文档资料。好,如果您已准备就绪，我们这就开始吧 ! <br /><br /><b>原理： </b><b></b><br /><br />WIN32 程序运行在保护模式下的，保护模式的历史可以追溯到 80286 。而今 80286 已成为了历史。所以我们将只把精力集中于 80386 及后续的 X86 系列 CPU 。 Windows 把每一个 Win32 应用程序放到分开的虚拟地址空间中去运行，也就是说每一个应用程序都拥有其相互独立的 4GB 地址空间，当然这倒不是说它们都拥有 4GB 的物理地址空间，而只是说能够在 4GB 的范围内寻址。操作系统将会在应用程序运行时完成 4GB 的虚拟地址和物理内存地址间的转换。这就要求编写应用程序时必须格守 Windows 的规范，否则极易引起内存的保护模式错误。而过去的 Win16 内存模式下，所有的应用程序都运行于同一个 4GB 地址空间，它们可以彼此 " 看 " 到别的程序的内容，这极易导致一个应用程序破坏另一个应用程序甚至是操作系统的数据或代码。  <br />       <br />和16 位Windows下的把代码分成 DATA ， CODE 等段的内存模式不同，WIN32 只有一种<font color="#0000ff">内存模式</font>，即 <font color="#0000ff">FLAT</font> 模式，意思是 " 平坦 " 的内存模式，再没有 64K 的段大小限制，所有的 WIN32 的应用程序运行在一个连续、平坦、巨大的 4GB 的空间中。这同时也意味着您无须和段寄存器打交道，您可以用任意的段寄存器寻址任意的地址空间，这对于程序员来说是非常方便的，这也使得用 32 位汇编语言和用 C 语言一样方便。  <br />       <br />在Win32 下编程，有许多重要的规则需要遵守。有一条很重要的是：Windows 在内部频繁使用 ESI ，EDI ，EBP ，EBX 寄存器，而且并不去检测这些寄存器的值是否被更改，这样当您要使用这些寄存器时必须先保存它们的值，待用完后再恢复它们，一个最显著的应用例子就是Windows 的CallBack 函数中。<br /><br />下面的程序段是一个框架，若您现在还不知道这些指令的确切意义的话，没关系，随后我就会给大家详细解释。 <br /><b><br /><font color="#000080">.386 <br />.MODEL </font></b><b><font color="#000080">Flat, STDCALL </font></b><b><br /><br /><font color="#000080">.DATA <br />    </font><font color="#ff0000">&lt; </font></b><font color="#ff0000">被初始化的数据 </font><b><font color="#ff0000">&gt;</font><font color="#000080"><br />    ...... <br /><br />.DATA?<br />   &lt; </font></b>未初始化的数据 <font color="#000080"><b>&gt; <br />   ...... <br /><br />.CONST <br />   <font color="#ff0000">&lt; </font></b></font><font color="#ff0000">常量 </font><b><font color="#ff0000">&gt; <br /></font><font color="#000080">   ...... <br /><br />.CODE <br />   &lt;label&gt; <br />    <font color="#ff0000">&lt; </font></font></b><font color="#ff0000">你的代码 </font><b><font color="#ff0000">&gt;</font><font color="#000080"><br />   ..... <br />    end &lt;label&gt; </font></b><b></b><br /><br />框架就这么简单，好，我现在就给您解释： <br /><b><br /><font color="#000080">.386 </font></b><br /><br />这是一个汇编语言伪指令，他告诉编译器我们的程序是使用<b>80386 </b>指令集编写的。您还可以使用 <b>.486、 </b><b>.586 </b>，但最安全的还是用 <b>.386 </b>。对于每一种 CPU 有两套几乎功能相同伪指令: <b>.386/.386P </b><b>、 </b><b>486/.486P </b><b>、 </b><b>586/.586P </b>。 带 P 的指令标明您的程序中可以用 特权级指令 。特权级指令是保留给操作系统的，如虚拟设备驱动程序。在大多数时间，您的程序都 无须运行 在 RING0 层，故用不带后缀 P 的伪指令已足够了。 <br /><b><br /><font color="#000080">.MODEL </font></b><font color="#000080"><b>FLAT, STDCALL </b><br /></font><br /><b>.MODEL </b><b>   </b>是用来指定内存模式的伪指令，在Win32 下，只有一种内存模型，那就是FLAT 平坦模式。 <br /><b><br />STDCALL </b>告诉编译器参数的传递约定。参数的传递约定是指参数传达时的顺序 ( 从左到右或从右到  左 )  和由谁恢复堆栈指针 ( 调用者或被调者 ) 。在Win16 下有两种约定：C 和 PASCAL 。 <br /><b><br />C </b>              约定规定参数传递顺序是 从右到左 ，即最右边的参数最先压栈，由调用者恢复堆栈指针。例 如：为调用函数 <b>foo</b><b>(int first param, int second param, int third param)</b>，按C约定的汇编代码应该是这样的： <br /><b><br /><font color="#000080">push  [third_param]               </font></b><font color="#000080"><b>; </b>把第三个参数压入栈 <b></b></font><b><br /><font color="#000080">push  [second_param]           </font></b><font color="#000080"><b>; </b>接着是第二个参数入栈 </font><b><br /><font color="#000080">push  [first_param]                </font></b><font color="#000080"><b>; </b>然后是第一个参数入栈 </font><b><br /><font color="#000080">call   foo<br />add    sp, 12                            </font></b><font color="#000080"><b>; </b>由调用者恢复堆栈指针</font> <b></b><br /><b>       <br />PASCAL </b><b></b>约定和 <b>C </b>约定正好相反，它规定参数是从左向右传递，由被调用者恢复堆栈。 <br /><br />        <br />Win16 采用了 PASCAL 约定， 因为 PASCAL 约定产生的代码量要小。当不知道参数的个数时，C 约定特别有用。如在函数<b>wsprintf </b><b>()</b>中，<b>wsprintf </b><b>() </b>预先并不知道要传递几个参数，所以它不知道如何恢复堆栈。 <br /><br /><b>STDCALL </b>是 C 约定和 PASCAL 约定的混合体，它规定参数的传递是从右到左，恢复堆栈的工作交由被调用者。 Win32 只用 <b>STDCALL </b>约定，但除了一个特例，即： <b>wsprintf </b><b>() </b><br /><br /><b><font color="#000080">.DATA <br />.DATA? </font></b><b><br /><font color="#000080">.CONST <br />.CODE</font>  </b><br /><br />上面的四个伪指令是 " 分段 "(SECTION) 伪指令。我们上面刚讲过 Win32 下没有 " 段 "(SEGMENT) 的概念，但是您可以把您的程序分成不同的 " 分段 " ， 一个 " 分段 " 的开始即是上一个 " 分段 " 的结束。 WIN32 中只有两种性质的 " 分段 " ： DATA 和 CODE 。 <br />其中 DATA" 分段 " 又分为三种： <br /><br /><b>.DATA   </b>其中包括已初始化的数据。 <br /><br /><b>.DATA? </b><b>  </b>其中包括未初始化的数据。比如有时您仅想预先分配一些内存但并不想指定初始值。使用未初始化的数据的优点是它不占据可执行文件的大小，如：若您要在 <b>.DATA? </b>段中分配 10,000 字节的空间，您的可执行文件的大小无须增加 10,000 字节，而仅仅是要告诉编译器在装载可执行文件时分配所需字节。 <br /><b><br />.CONST </b><b>  </b>其中包括常量定义。这些常量在程序运行过程中是不能更改的。 <br /><b><br />.CODE </b>这是代码 " 分段 " 。 <br /><br />应用程序并不需要以上所有的三个 " 分段 " ，可以根据需要进行定义。 &lt; 译者注：实际上，分段并不是象在 <b></b><b>Dos </b><b></b>下一样 ，为不同的段分别指出不同的段寄存器，因为 Windows 下只有一个 4GB 的段， Windows 程序中的分段表现在当程序装载时，赋予不同的分段不同的属性，比如说当你的程序加载时，对于 Ring3 程序来说，<b>.code </b>段是不可写的，而<b>.data </b>段是可写的，如果你尝试象在 <b>Dos </b>下一样写自己的代码部分，你会得到一个蓝屏错误 &gt; <br />  <br /><b><font color="#000080">&lt;label&gt; <br />end &lt;label&gt;</font> </b><b><br /></b><br />是用来唯一标识您的代码范围的标签，两个标签必须相同，应用程序的所有可执行代码必修在两个标签之间。<br /><hr /><br /><b>[ </b><b><font color="#800080"><a href="http://win32asm.cjb.net Iczelion" temp_href="http://win32asm.cjb.net Iczelion">Iczelion's Win32 Assembly HomePage</a></font></b><b>] 需要代理服务器，但可通过以下镜像进行访问<br /><a href="http://win32assembly.online.fr/">http://win32assembly.online.fr/</a>  <a href="http://users.daex.ufsc.br/~iczelion/">http://users.daex.ufsc.br/~iczelion/</a>  <a href="http://spiff.tripnet.se/~Iczelion">http://spiff.tripnet.se/~Iczelion</a></b></div>
<img src ="http://www.blogjava.net/cuiyuelei/aggbug/96397.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cuiyuelei/" target="_blank">催月泪(Jaclick)</a> 2007-01-28 20:34 <a href="http://www.blogjava.net/cuiyuelei/archive/2007/01/28/96397.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>