Tutorial 13: Memory Mapped Files
第十三课:内存映射文件
________________________________________
I'll show you what memory mapped files are and how to use them to your advantages. Using a memory mapped file is quite easy as you'll see in this tutorial.
我将向你说明什么是内存映射文件以及你们如何使用它们来获得优势。在这一课中,你将发现使用内存映象文件是非常简单的。
Download the example here.
Theory:
原理:
If you examine the example in the previous tutorial closely, you'll find that it has a serious shortcoming: what if the file you want to read is larger than the allocated memory block? or what if the string you want to search for is cut off in half at the end of the memory block? The traditional answer for the first question is that you should repeatedly read in the data from the file until the end of file is encountered. The answer to the second question is that you should prepare for the special case at the end of the memory block. This is called a boundary value problem. It presents headaches to programmers and causes innumerable bugs.
如果你仔细分析前一课中的例子,你将发现它有一个严重的缺点:如果你想读的文件比已分配的内存块大得多,怎么办?或者是你想查找的字串在内存块的结尾处一分为二,又如何解决。对于第一个问题,传统的答案是:你应该重复的从文件中读进数据直到文件结束为止。而对于第二个问题的答案是:你应该在内存块的边界处做一些处理。这是调用一个边界值的问题。它对程序员来说是非常头痛的并且导致程序中存在无数缺陷(bug臭虫 ,有软件臭虫和硬件臭虫之分)的原因。
It would be nice if we can allocate a very large block of memory, enough to store the whole file but our program would be a resource hog. File mapping to the rescue. By using file mapping, you can think of the whole file as being already loaded into memory and you can use a memory pointer to read or write data from the file. As easy as that. No need to use memory API functions and separate File I/O API functions anymore, they are one and the same under file mapping. File mapping is also used as a means to share data between processes. Using file mapping in this way, there's no actual file involved. It's more like a reserved memory block that every process can *see*. But sharing data between processes is a delicate subject, not to be treated lightly. You have to implement process and thread synchronization else your applications will crash in very short order.
如果我们能分配一块非常大的内存,大得足够存储整个文件,那是多么美好的事。但是那样我们的程序就成了一头贪吃资源的肥猪。文件映射拯救了它。(gog?)
通过使用文件映射,你可以认为整个文件都已经装进内存并且你能用一个内存指针从文件中读写数据。这样您甚至不需要调用那些分配、释放内存块和文件输入/输出的API函数,它们在文件映射中是一样的。文件映射也同样作为进程之间共享数据的一种方法。使用文件映射这种方法是非常复杂的。文件映象并没有包括实际的文件,它更像是为每个进程保留一个能见(* see*!!) 的内存块。但是在进程之间共享数据是一个棘手的主题,并不是轻轻松松就能够处理的。你必须实现进程和线程的同步, 否则 你的应用程序将在很短的时间内崩溃。(进程的不同步执行导致程序的不可再现性,即每次运行程序都将得到一个不同的结果)
We will not touch the subject of file mapping as a means to create a shared memory region in this tutorial. We'll concentrate on how to use file mapping as a means to "map" a file into memory. In fact, the PE loader uses file mapping to load executable files into memory. It is very convenient since only the necessary portions can be selectively read from the file on the disk. Under Win32, you should use file mapping as much as possible.
在这课中,我们讲述的内容将不涉及这种为进程同步而创建一内存共享区的方法。我们将把精力集中在 如何把一个文件映射在内存中 的 这种文件映射方法。
实际上,PE文件的装入程序就是用文件映射的方法来把可执行文件装入内存中。
它是非常方便的 因为 仅仅是必须的那一部分(保证程序能执行的数据和代码) 能被从磁盘上的文件中读进内存。(而文件的其它的部分只有在缺页的时候才被调入。)在win32中,你应该尽可能的用文件映射方法。
There are some limitation to file mapping though. Once you create a memory mapped file, its size cannot be changed during that session. So file mapping is great for read-only files or file operations that don't affect the file size. That doesn't mean that you cannot use file mapping if you want to increase the file size. You can estimate the new size and create the memory mapped file based on the new size and the file will grow to that size. It's just inconvenient, that's all.
尽管文件映射方法是有一些限制的。一旦你创建了一个内存映射文件,它的大小在会话区间就不能被够改变的。所以文件映射方法最大的用处就是对于只读文件或是文件操作并不会改变文件大小的文件中。但是这并不意味着你不能用文件映射方法在那些你想增加大小的文件中。你能估计新的文件大小并且基于这个新的文件大小创建内存映射文件。这样文件的大小仍然在你的控制中。它仅仅是不方便的而已。
Enough for the explanation. Let's dive into implementation of file mapping. In order to use file mapping, these steps must be performed:
我已经解释的足够多了。让我们来钻研文件映射方法的实现。
为了用文件映射方法,下面这些步骤必须完成:
1. call CreateFile to open the file you want to map.
调用CreateFile 打开你想映射的文件。
2. call CreateFileMapping with the file handle returned by CreateFile as one of its parameter. This function creates a file mapping object from the file opened by CreateFile.
传递CreateFile返回的文件句柄 作为CreateFileMapping函数 的一个参数并调用它。这个函数从被CreateFile函数打开的文件中创建一个文件映射对象。
3. call MapViewOfFile to map a selected file region or the whole file to memory. This function returns a pointer to the first byte of the mapped file region.
调用MapViewOfFile来映射一个被选定的文件区域或是整个文件到内存中。这个函数的返回值是一个指针,这个指针指向 映射文件区域的第一个字节。
4. Use the pointer to read or write the file
用这个指针读或写这个文件
5. call UnmapViewOfFile to unmap the file.
调用UnmapViewOfFile来解除文件映射。
6. call CloseHandle with the handle to the mapped file as the parameter to close the mapped file.
将映射文件的句柄作为CloseHandle函数的参数并调用它来关闭内存映射文件。
7. call CloseHandle again this time with the file handle returned by CreateFile to close the actual file.
再一次传递CreateFile返回的文件句柄给CloseHandle函数并调用它来关闭实际的文件。
Example:
例子:
The program listed below lets you open a file via an open file dialog box. It opens the file using file mapping, if it's successful, the window caption is changed to the name of the opened file. You can save the file in another name by select File/Save as menuitem. The program will copy the whole content of the opened file to the new file. Note that you don't have to call GlobalAlloc to allocate a memory block in this program.
下面的程序列表让你通过一个打开文件对话框打开一个文件。它用文件映射方法打开这个文件,如果它是成功的,窗口的标题将变成被打开文件的名称。你能选择文件/保存这个菜单项来把文件用另一个名字保存。这个程序将复制被打开文件的所有内容到新文件中。注意:在这个程序中,你不能用GlobalAlloc来分配一个内存块。
.386
.model flat,stdcall
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0 ; Handle to the memory mapped file, must be
;initialized with 0 because we also use it as
;a flag in WM_DESTROY section too
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Handle to the source file
hFileWrite HANDLE ? ; Handle to the output file
hMenu HANDLE ?
pMemory DWORD ? ; pointer to the data in the source file
SizeWritten DWORD ? ; number of bytes actually written by WriteFile
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ;Obtain the menu handle
mov hMenu,eax
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileRead,eax
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
.endif
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileWrite,eax
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
invoke GetFileSize,hFileRead,NULL
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
invoke UnmapViewOfFile,pMemory
call CloseMapFile
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp
end start
Analysis:
分析:
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
When the user selects a file in the open file dialog, we call CreateFile to open it. Note that we specify GENERIC_READ to open this file for read-only access and dwShareMode is zero because we don't want some other process to modify the file during our operation.
当用户在打开文件对话框中选择一个文件时,我们调用CreateFile来打开它。注意:为了以只读方式打开这个文件我们指定了GENERIC_READ标志字并且将dwShareMode设为0,因为我们不想其它进程在我们操作区间修改这个文件.
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
Then we call CreateFileMapping to create a memory mapped file from the opened file. CreateFileMapping has the following syntax:
然后我们调用FileMapping从被打开文件创建一个内存映射文件CreateFileMapping句法如下:
CreateFileMapping proto hFile:DWORD,\
lpFileMappingAttributes:DWORD,\
flProtect:DWORD,\
dwMaximumSizeHigh:DWORD,\
dwMaximumSizeLow:DWORD,\
lpName:DWORD
You should know first that CreateFileMapping doesn't have to map the whole file to memory. You can use this function to map only a part of the actual file to memory. You specify the size of the memory mapped file in dwMaximumSizeHigh and dwMaximumSizeLow params. If you specify the size that 's larger than the actual file, the actual file will be expanded to the new size. If you want the memory mapped file to be the same size as the actual file, put zeroes in both params.
首先,你应该知道CreateFileMapping函数并不是把整个文件都映射到内存中.你能用这个函数来映射实际文件的一部分到内存中.你可以在dwMaximumSizehigh和dwMaximumSizeLow参数中指定内存映射文件的大小.如果指定了大小大于实际的文件大小,那么实际文件的大小将被扩充到新的大小.如果你想内存映射文件和实际的文件有相同的大小,请将两个参数的值设为零.
You can use NULL in lpFileMappingAttributes parameter to have Windows creates a memory mapped file with default security attributes.
你能用NULL填充lpFileMappingAttributes参数来让windows创建一个有默认安全属性的内存映射文件.
flProtect defines the protection desired for the memory mapped file. In our example, we use PAGE_READONLY to allow only read operation on the memory mapped file. Note that this attribute must not contradict the attribute used in CreateFile else CreateFileMapping will fail.
FlProtect为内存映射文件定义了保护模式.在我们的例子中,我们用PAGE_READONLY来允许在内存映射文件上执行读操作.注意:这个属性不能和用在CreateFile函数中的属性值相抵触否则CreateFileMapping将失效。
lpName points to the name of the memory mapped file. If you want to share this file with other process, you must provide it a name. But in our example, our process is the only one that uses this file so we ignore this parameter.
LpName指向内存映射文件的名称。如果你想和其它进程共享这个文件,你必须提供它的名字。但是在我们的例子中,我们只有一个进程用于这个文件所以我们可以不理睬这个参数。
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
If CreateFileMapping is successful, we change the window caption to the name of the opened file. The filename with full path is stored in buffer, we want to display only the filename in the caption so we must add the value of nFileOffset member of the OPENFILENAME structure to the address of buffer.
如果CreateFielMapping是成功的,我们改变窗口标题为打开文件的名字。文件名和全路径被存储在缓冲区中。我们只是在标题栏中显示文件名所以我们必须增加OPENFILENAME结构的nFileOffset成员的值来指定缓冲区的地址。 (为了找到文件名)
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
As a precaution, we don't want the user to open multiple files at once, so we gray out the Open menu item and enable the Save menu item. EnableMenuItem is used to change the attribute of menu item.
After this, we wait for the user to select File/Save as menu item or close our program. If the user chooses to close our program, we must close the memory mapped file and the actual file like the code below:
作为一个预防,我们不想用户一次打开多个文件,所以我们让打开菜单项呈现灰色并且让保存菜单项正常使用。EnableMenuItem被用于改变一个菜单项的属性。在这之后,我们等待用户选择文件/保存菜单项或是关闭我们的程序。如果用户选择关闭我们的应用程序,我们必须关闭内存映射文件和实际文件,代码如下所示:
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
In the above code snippet, when the window procedure receives the WM_DESTROY message, it checks the value of hMapFile first whether it is zero or not. If it's not zero, it calls CloseMapFile function which contains the following code:
上面的这些代码片断,当窗口过程接受到WM_DESTROY消息时,它首先检查hMapFile的值看看它是否为零。如果它不为零,它调用CloseMapFile函数,它包含的代码如下:
CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp
CloseMapFile closes the memory mapped file and the actual file so that there 'll be no resource leakage when our program exits to Windows.
If the user chooses to save that data to another file, the program presents him with a save as dialog box. After he types in the name of the new file, the file is created by CreateFile function.
CloseMapFile关闭内存映射文件和实际的文件这样当我们程序退出windows时这里将不会有资源泄露。如果用户选择了保存数据到另外一个文件中,程序呈现一个 保存文件对话框给用户。在他输入了新的文件名之后,这个文件被CreateFile 函数创建。
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
Immediately after the output file is created, we call MapViewOfFile to map the desired portion of the memory mapped file into memory. This function has the following syntax:
在输出文件被创建后,我们立即调用MapViewOfFile来映射我们想得到的内存映射文件的一部分到内存中。这个函数的句法如下:
MapViewOfFile proto hFileMappingObject:DWORD,\
dwDesiredAccess:DWORD,\
dwFileOffsetHigh:DWORD,\
dwFileOffsetLow:DWORD,\
dwNumberOfBytesToMap:DWORD
dwDesiredAccess specifies what operation we want to do to the file. In our example, we want to read the data only so we use FILE_MAP_READ.
dwDwsiredAccess 指定我们想要这个文件执行什么样的操作。在我们的例子中,我们仅仅是读文件的数据所以我们用FILE_MAP_READ
dwFileOffsetHigh and dwFileOffsetLowspecify the starting file offset of the file portion that you want to map into memory. In our case, we want to read in the whole file so we start mapping from offset 0 onwards.
DwFileOffsetHigh和dwFileOffsetLow指定你想映射进内存中的那一部分文件的文件偏移量. 在我们的例子中,我们想读整个文件所以我们从偏移量为0的地方向前地开始映射.
dwNumberOfBytesToMap specifies the number of bytes to map into memory. If you want to map the whole file (specified by CreateFileMapping), pass 0 to MapViewOfFile.
After calling MapViewOfFile, the desired portion is loaded into memory. You'll be given the pointer to the memory block that contains the data from the file.
DwNumberOfBytesToMap指定映射进内存中的字节数.如果你想映射整个文件( 通过CreateFileMapping函数指定),传递0给MapViewOfFile
在调用了MapViewFile之后,我们想要的部分已经被装入内存.你将得到一个指针,这个指针指向一个内存块,内存块包含文件中的数据
invoke GetFileSize,hFileRead,NULL
Find out how large the file is. The file size is returned in eax. If the file is larger than 4 GB, the high DWORD of the file size is stored in FileSizeHighWord. Since we don't expect to handle such large file, we can ignore it.
查明文件的大小.文件的大小被返回在eax中.如果文件大于4GB,文件大小( dword) 的高值( 超过出的那一部分,因为32位数最大值是4GB) 存放在FileSizeHighWord中.因为我们并不希望处理这么大的文件,所以我们可以忽略它。
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
Write the data that is mapped into memory into the output file.
把映射到内存中数据( 影射文件的数据)写到输出文件中去。
invoke UnmapViewOfFile,pMemory
When we're through with the input file, unmap it from memory.
当我们完成文件的输入,从内存中撤消它(映射文件)
call CloseMapFile
invoke CloseHandle,hFileWrite
And close all the files.
关闭所有的文件。
invoke SetWindowText,hWnd,ADDR AppName
Restore the original caption text.
恢复原来的标题文本
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
Enable the Open menu item and gray out the Save As menu item.
恢复打开菜单项的正常操作并让另存为菜单项呈现灰色。
________________________________________
This article come from Iczelion's asm page,
风向改变翻译于2007-12-30 日
Tutorial 14: Process
第十四课:进程
________________________________________
We will learn what a process is and how to create and terminate it.
我们将学习什么是进程以及如何创建和终止一个进程。
Download the example here.
Preliminary:
引导:
What is a process? I quote this definition from Win32 API reference:
"A process is an executing application that consists of a private virtual address space, code, data, and other operating system resources, such as files, pipes, and synchronization objects that are visible to the process."
As you can see from the definition above, a process "owns" several objects: the address space, the executing module(s), and anything that the executing modules create or open. At the minimum, a process must consist of an executing module, a private address space and a thread. Every process must have at least one thread. What's a thread? A thread is actually an execution queue. When Windows first creates a process, it creates only one thread per process. This thread usually starts execution from the first instruction in the module. If the process later needs more threads, it can explicitly create them.
When Windows receives a command to create a process, it creates the private memory address space for the process and then it maps the executable file into the space. After that it creates the primary thread for the process.
Under Win32, you can also create processes from your own programs by calling CreateProcess function.
当windows接收到一个创建进程的命令时,它为这个进程创建一个私有的内存地址空间然后它映射这个可执行文件到这个地址空间中。随后它为这个进程创建了一个主线程。(最初的,原始的,第一位的)在win32中,你还可以通过调用CreateProcess函数从你自己的应用程序中创建多个进程。
CreateProcess has the following syntax:
CreateProcess句法如下:
CreateProcess proto lpApplicationName:DWORD,\
lpCommandLine:DWORD,\
lpProcessAttributes:DWORD,\
lpThreadAttributes:DWORD,\
bInheritHandles:DWORD,\
dwCreationFlags:DWORD,\
lpEnvironment:DWORD,\
lpCurrentDirectory:DWORD,\
lpStartupInfo:DWORD,\
lpProcessInformation:DWORD
Don't be alarmed by the number of parameters. We can ignore most of them.
不要为这么多的参数惊慌,我们可以忽略它们中的大多数。
lpApplicationName --> The name of the executable file with or without pathname that you want to execute. If this parameter is null, you must provide the name of the executable file in the lpCommandLine parameter.
LpApplicationName—〉包含或不包含路径名的可执行文件名,这个可执行文件是你想执行的。如果这个参数为空,你必须在lpCommandLine参数中提供这个可执行文件的名字。
lpCommandLine --> The command line arguments to the program you want to execute. Note that if the lpApplicationName is NULL, this parameter must contain the name of the executable file too. Like this: "notepad.exe readme.txt"
lpCommandLine—〉传递给你想执行的程序的命令行参数。注意:如果lpApplicationName参数的值为空,这个参数必须包含可执行文件的名字。像这样:“notepad.exe readme.txt”
lpProcessAttributes and lpthreadAttributes --> Specify the security attributes for the process and the primary thread. If they're NULLs, the default security attributes are used.
LpProcessAttributes和lpthreadAttributes--> 为进程和主线程指定安全属性.如果它们的值为空,默认的安全属性将被使用.
bInheritHandles --> A flag that specify if you want the new process to inherit all opened handles from your process.
bInheritHandles -- > 如果你想让新创建的进程从你的进程中继承所有已打开的句柄, 那么请指定这个标志参数.
dwCreationFlags --> Several flags that determine the behavior of the process you want to created, such as, do you want to process to be created but immediately suspended so that you can examine or modify it before it runs? You can also specify the priority class of the thread(s) in the new process. This priority class is used to determine the scheduling priority of the threads within the process. Normally we use NORMAL_PRIORITY_CLASS flag.
DwCreationFlags --> 用于确定你想创建进程的行为状态的几个标志参数.例如,你可以在创建一个进程后直接暂停它,以至于你能在它运行之前检查或是修改它.你还能用它指定一个新进程的线程的优先级.优先级是用来确定新进程内部线程的调度优先数的.通常我们使用: NORMAL_PRINORITY_CLASS 标志.
lpEnvironment --> A pointer to the environment block that contains several environment strings for the new process. If this parameter is NULL, the new process inherits the environment block from the parent process.
LpEnvironment -- > 指向环境块的指针,环境块包含新进程的几个环境字串.如果这个参数是空,这个进程从它的父进程中继承环境块.
lpCurrentDirectory --> A pointer to the string that specifies the current drive and directory for the child process. NULL if you want the child process to inherit from the parent process.
LpCurrentDirectory -- > 指向一字符串的指针,这个字符串为子进程指定了当前设备和目录。值为空表示你想创建的子进程从它的父进程处继承了属性。
lpStartupInfo --> Points to a STARTUPINFO structure that specifies how the main window for the new process should appear. The STARTUPINFO structure contains many members that specifies the appearance of the main window of the child process. If you don't want anything special, you can fill the STARTUPINFO structure with the values from the parent process by calling GetStartupInfo function.
LpStartupInfo –〉 指向一个STARTUP INFO结构体的指针。这个结构体指定主窗口为了让新进程显示的方法。这个STARTUP INFO 结构包含很多成员。这些成员用于指定子进程的主窗口外观。如果你不想指定任何成员,你能通过调用GetStartupInfo函数来用父进程中的值填充这个结构。
lpProcessInformation --> Points to a PROCESS_INFORMATION structure that receives identification information about the new process. The PROCESS_INFORMATION structure has the following members:
lpProcessInformation – 〉指向一个PROCESS_INFORMATION 结构的指针.这个结构接收关于新进程的标识信息.这个结构有如下成员:
PROCESS_INFORMATION STRUCT
hProcess HANDLE ? ; handle to the child process
hThread HANDLE ? ; handle to the primary \ thread of the child process //子进程的主线程句柄
dwProcessId DWORD ? ; ID of the child process
dwThreadId DWORD ? ; ID of the primary thread of the child process
PROCESS_INFORMATION ENDS
Process handle and process ID are two different things. A process ID is a unique identifier for the process in the system. A process handle is a value returned by Windows for use with other process-related API functions. A process handle cannot be used to identify a process since it's not unique.
进程句柄和进程ID是两个不同个概念.一个进程的ID是系统中这个进程的唯一标识.一个进程的句柄是通过调用windows中其它与进程有关的API函数后得到的一个返回值.一个进程的句柄不能用于标识一个进程因为它是不唯一的.
After the CreateProcess call, a new process is created and the CreateProcess call return immediately. You can check if the new process is still active by calling GetExitCodeProcess function which has the following syntax:
在CreateProcess调用之后,一个新的进程被创建而CreateProcess函数立即返回。你能调用GetExitCodeProcess函数来检查新进程是否一直处于活动状态,这个函数的句法如下:
GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD
If this call is successful, lpExitCode contains the termination status of the process in question. If the value in lpExitCode is equal to STILL_ACTIVE, then that process is still running.
如果这个调用是成功的,lpExitCode 包含进程终止态的可疑信息.如果在lpExitCode中的值为STILL_ACTIVE,表示这个进程正在运行.
You can forcibly terminate a process by calling TerminateProcess function. It has the following syntax:
你能通过调用TeminateProcess函数来强制的终止一个进程.它的句法如下:
TerminateProcess proto hProcess:DWORD, uExitCode:DWORD
You can specify the desired exit code for the process, any value you like. TerminateProcess is not a clean way to terminate a process since any dll attached to the process will not be notified that the process was terminated.
我们能为进程指定任何你想要的退出码.用TerminateProcess函数来结束进程是不干净的,因为任何与该进程相依恋的dll库文件都不会得到进程终止的通知.
Example:
The following example will create a new process when the user selects the "create process" menu item. It will attempt to execute "msgbox.exe". If the user wants to terminate the new process, he can select the "terminate process" menu item. The program will check first if the new process is already destroyed, if it is not, the program will call TerminateProcess function to destroy the new process.
当用户选定” create process”菜单的时候,下面这个例子创建一个新的进程,它将尝试执行‘msgbox.exe’文件。如果用户想终止这个新创建的进程,他能选择终止进程菜单项。程序首先检查新进程是否被销毁,如果它还存在,程序将调用TerminateProcess函数来销毁这个新进程。
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3
.data
ClassName db "Win32ASMProcessClass",0
AppName db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ? ; contains the process exitcode status from GetExitCodeProcess call.
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL startInfo:STARTUPINFO
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analysis:
分析:
The program creates the main window and retrieves the menu handle for future use. It then waits for the user to select a command from the menu. When the user selects "Process" menu item in the main menu, we process WM_INITMENUPOPUP message to modify the menu items inside the popup menu before it's displayed.
应用程序创建主窗口并且保存菜单句柄已备将来使用。然后它将等待用户选择菜单中的一个命令。当用户在主菜单中选择了”process” 菜单项时,在它( 弹出式菜单从下文中得知)被显示前,我们处理WM_INITMENUPOPUP消息来修改在弹出式菜单中的菜单项.(即让它变灰或者是正常)
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
Why do we want to process this message? Because we want to prepare the menu items in the popup menu before the user can see them. In our example, if the new process is not started yet, we want to enable the "start process" and gray out the "terminate process" menu items. We do the reverse if the new process is already active.
为什么我们想处理这个消息? 因为我们想在用户能看到它们之前准备好弹出式菜单中的菜单项。(让菜单项呈现不同的外观,这样方便用户使用)在我们的例子中,如果新进程还没启动,我们让“start process “ 菜单项能用而让“terminate process “菜单项 变灰。如果新进程已经是活动状态,我们做相反的工作。(即让strat process 菜单变灰而让terminate process能用)
We first check if the new process is still running by calling GetExitCodeProcess function with the process handle that was filled in by CreateProcess function. If GetExitCodeProcess returns FALSE, it means the process is not started yet so we gray out the "terminate process" menu item. If GetExitCodeProcess returns TRUE, we know that a new process has been started, but we have to check further if it is still running. So we compare the value in ExitCode to the value STILL_ACTIVE, if they're equal, the process is still running: we must gray out the "start process" menu item since we don't want to start several concurrent processes.
首先,我们 传递由CreateProcess函数返回的进程句柄 作为GetExitCodeProcess函数的参数 并调用它来检查新进程是否是执行态。 如果GetExitCodeProcess返回值是FALSE,它意味着进程还没开始执行,(就绪态)所以我们让terminate process 菜单项变灰。如果GetExitCodeProcess函数返回值为TRUE,我们就知道这个新进程已经执行,但是还必须检查的远一点,看看它是否是持续执行态。所以我们用STILL_ACTIVE的值和在ExitCode中值作比较,看看它们是否相等.如果相等,进程是一直运行的: 这意味着我们必须让start process 菜单项变灰,因为我们不想启动多个并发执行的进程.
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
When the user selects "start process" menu item, we first check if hProcess member of PROCESS_INFORMATION structure is already closed. If this is the first time, the value of hProcess will always be zero since we define PROCESS_INFORMATION structure in .data section. If the value of hProcess member is not 0, it means the child process has exited but we haven't closed its process handle yet. So this is the time to do it.
We call GetStartupInfo function to fill in the startupinfo structure that we will pass to CreateProcess function. After that we call CreateProcess function to start the new process. Note that I haven't checked the return value of CreateProcess because it will make the example more complex. In real life, you should check the return value of CreateProcess. Immediately after CreateProcess, we close the primary thread handle returned in processInfo structure. Closing the handle doesn't mean we terminate the thread, only that we don't want to use the handle to refer to the thread from our program. If we don't close it, it will cause a resource leak.
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
When the user selects "terminate process" menu item, we check if the new process is still active by calling GetExitCodeProcess function. If it's still active, we call TerminateProcess function to kill the process. Also we close the child process handle since we have no need for it anymore.
当用户选择了”terminate process “ 菜单项时,我们通过调用GetExitCodeProcess函数来检查新进程是否还处于运行态.如果它还是运行的,我们调用TerminateProcess函数来杀了这个进程。(终止它)我们同样关闭它所有的子进程句柄因为我们再也不需要它们了。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2007-12-31日
Tutorial 15: Multithreading Programming
第十五课:多线程程序设计
________________________________________
We will learn how to create a multithreading program in this tutorial. We also study the communication methods between the threads.
我们将在这一课中学习如何创建一个多线程程序 。我们也将学习线程之间的通讯方法。
Download the example here.
Theory:
理论:
In previous tutorial, you learn the a process consists of at least one thread: the primary thread. A thread is a chain of execution. You can also create additional threads in your program. You can view multithreading as multitasking within one program. In term of implementation, a thread is a function that runs concurrently with the main program. You can run several instances of the same function or you can run several functions simultaneously depending on your requirement. Multithreading is specific to Win32, no Win16 counterpart exists.
Threads run in the same process so they can access any resources in the process such as global variables, handles etc. However, each thread has its own stack so local variables in each thread are private. Each thread also owns its private register set so when Windows switches to other threads, the thread can "remember" its last status and can "resume" the task when it gains control again. This is handled internally by Windows.
We can divide threads into two caterories:
多个线程运行在同一个进程中,所以他们能访问进程中的任何资源.例如: 全局变量,句柄等等.然而,每一个线程拥有它自己的堆栈所以局部变量在每一个线程中是私有的.每一个线程也拥有它自己的私有寄存器组,所以WINDOWS切换到其它线程时,这些线程能” 记得”它们最后的状态并且当它再一次获得控制权时它能”恢复”这个任务(一组数据,指令)(在操作系统中引入线程,是为了提高系统的吞吐量和资源利用率,线程切换往往发生在多任务系统中的线程调度,在多任务操作系统中,线程作为调度的最小单位,而进程作为资源分配的最小单位)这些都是在WINDOWS内部处理,对用户而言是透明的.
我们能将线程划分为两类:
1. User interface thread: This type of thread creates its own window so it receives windows messages. It can respond to the user via its own window hence the name. This type of thread is subject to Win16 Mutex rule which allows only one user interface thread in 16-bit user and gdi kernel. While a user interface thread is executing code in 16-bit user and gdi kernel, other UI threads cannot use the service of the 16-bit user and gdi kernel. Note that this Win16 Mutex is specific to Windows 95 since underneath, Windows 95 API functions thunk down to 16-bit code. Windows NT has no Win16 Mutex so the user interface threads under NT work more smoothly than under Windows 95.
用户界面线程:这种类型的线程创建它们自己的窗口,所以它接收WINDOWS消息并响应它们。能通过它自己的窗口来响应用户,这是它得名(user interface thread )的原因。这种类型的用户线程服从于WIN16中的互斥规则,这条规则是,仅允许一个用户界面线程在16位的user和gdi内核中。(对内核独占访问)即当一个用户界面线程正在16位的user和gdi内核中执行代码时,其它的用户界面线程就不能够使用16位的user和 gdi内核提供的服务。注意:这个win16的互斥体是windows95特有的,因为windows 95 中的API函数是16位的形式代码。而windows NT并没有WIN16互斥体,所以在NT中工作的用户界面线程比在windows95中工作的用户界面线程更既有稳定性。
2. Worker thread: This type of thread does not create a window so it cannot receive any windows message. It exists primarily to do the assigned job in the background hence the name worker thread.
工人线程:这种类型的线程并不创建窗口,所以它不能接收任何windows消息。它主要在幕后做那些已分配的作业,这是它得名worker thread的原因。
I advise the following strategy when using multithreading capability of Win32: Let the primary thread do user interface stuff and the other threads do the hard work in the background. In this way, the primary thread is like a Governor, other threads are like the Governor's staff. The Governor delegates jobs to his staff while he maintains contact with the public. The Governor staff obediently performs the work and reports back to the Governor. If the Governor were to perform every task himself, he would not be able to give much attention to the public or the press. That's akin to a window which is busy doing a lengthy job in its primary thread: it doesn't respond to the user until the job is completed. Such a program can benefit from creating an additonal thread which is responsible for the lengthy job, allowing the primary thread to respond to the user's commands.
当用户在win32中应用多线程编程时,我建议你们应用如下策略:让主线程作为用户界面线程,而其它的线程在后台中做那些艰难的工作。使用这种方式,主线程就像是统治者,而其它线程就像是统治者的官员。当统治者想维持和公众的联系时,他就把这些工作委托给他的官员。官员在完成工作后将回去给统治者作汇报。如果统治者将自己完成所有的任务,那么他将不能够给群众更多的关心。这与一个在主线程中忙于处理一个冗长作业的窗口类似:除非工作完成,否则它绝不响应用户。这样在创建程序时为这个程序增加多个线程,让新添加的线程为那些冗长的作业负责,允许主线程响应用户的命令。这对程序来说是有益的。
We can create a thread by calling CreateThread function which has the following syntax:
我们能通过调用CreateThread函数来创建一个线程,这个函数句法如下:
CreateThread proto lpThreadAttributes:DWORD,\
dwStackSize:DWORD,\
lpStartAddress:DWORD,\
lpParameter:DWORD,\
dwCreationFlags:DWORD,\
lpThreadId:DWORD
CreateThread function looks a lot like CreateProcess.
CreateThread函数看起来和CreateProcess函数有些相像。
lpThreadAttributes --> You can use NULL if you want the thread to have default security descriptor. lpThreadAttributes -->如果你想让线程有默认的安全性描述你能用NULL指定这个参数。
dwStackSize --> specify the stack size of the thread. If you want the thread to have the same stack size as the primary thread, use NULL as this parameter. dwStackSize -->指定线程的堆栈大小。如果你想让创建的线程和主线程的堆栈大小一样,用NULL作为这个参数的值。
lpStartAddress--> Address of the thread function.It's the function that will perform the work of the thread. This function MUST receive one and only one 32-bit parameter and return a 32-bit value.
lpStartAddress-->线程函数的开始地址。执行函数是线程的工作。这个函数必须接收一个并且仅接收一个的32位参数然后返回一个32位的值。
lpParameter --> The parameter you want to pass to the thread function. lpParameter -->指定你想传递给线程函数的参数。
dwCreationFlags --> 0 means the thread runs immediately after it's created. The opposite is CREATE_SUSPENDED flag. dwCreationFlags -->创建标记。0意味着线程在被创建后立即执行。于之相对的是CREATE_SUSPENDED标记。
lpThreadId --> CreateThread function will fill the thread ID of the newly created thread at this address. lpThreadId -->创建线程的函数将在这个地址上填充新创建线程的ID。
If CreateThread call is sucessful, it returns the handle of the newly created thread. Otherwise, it returns NULL. 如果CreateThread调用是成功的。它返回新创建线程的局部,否则,返回值为空。
The thread function runs as soon as CreateThread call is success ful unless you specify CREATE_SUSPENDED flag in dwCreationFlags. In that case, the thread is suspended until ResumeThread function is called.
一旦CreateThread函数调用成功,线程函数就立即运行。(注意线程函数和创建线程函数的区别)除非在dwCreationFlags参数中指定CREATE_SUSPENDED标记,如果是这样的话,在ResumeThread函数被调用之前,线程都将被暂停执行。
When the thread function returns with ret instruction, Windows calls ExitThread function for the thread function implicitly. You can call ExitThread function with in your thread function yourself but there' s little point in doing so.
当线程函数伴随着ret指令的执行而返回时,windows调用ExitThread函数来处理线程函数返回的清理工作。(堆栈平衡,等等)你也可以自己调用这个函数,但这样做几乎没有意义。
You can retrieve the exit code of a thread by calling GetExitCodeThread function.
你能通过调用GetExitCodeThread函数来得到一个线程的退出码。
If you want to terminate a thread from other thread, you can call TerminateThread function. But you should use this function under extreme circumstance since this function terminates the thread immediately without giving the thread any chance to clean up after itself.
如果你想从其它的线程中终止一个线程,你能调用TerminateThread函数。但是你要在极端的环境下用这个函数,因为这个函数立即就终止了线程而没有给线程任何机会来做清理工作。
Now let's move to the communication methods between threads.
There are three of them:
让我们来看看线程间的通讯方法。这里主要有三种:
• Using global variables 使用全局变量
• Windows messages windows 消息
• Event 事件
Threads share the process's resources including global variables so the threads can use global varibles to communicate with each other. However this method must be used with care. Thread synchronization must enter into consideration. For example, if two threads use the same structure of 10 members , what happens when Windows suddenly yanks the control from one of the thread when it was in the middle of updating the structure? The other thread will be left with an inconsistent data in the structure! Don't make any mistake, multithreading programs are harder to debug and maintain. This sort of bug seems to happen at random which is very hard to track down.
一个线程共享进程的所有资源,包括全局变量。所有线程能用全局变量来和其它的线程通讯。然而这种方法必须小心使用。线程同步是必须要考虑的一部分。例如:如果两个线程同时使用一个有10个成员的数据结构,当一个线程正在修改这个结构的数据时,windows突然将控制权转交给另外一个线程,什么会产生呢?另外的那个线程将留下不相容数据在这个结构中!(导致了程序的不可再现性)不要犯任何错误,因为调试和维护一个多线程程序是非常困难的。这种程序错误似乎是随机产生的,并且也很难被跟踪到。
You can also use Windows messages to communicate between threads. If the threads are all user interface ones, there's no problem: this method can be used as a two-way communication. All you have to do is defining one or more custom windows messages that are meaningful to the threads. You define a custom message by using WM_USER message as the base value say , you can define it like this:
你也能用windows消息来完成线程之间的通讯。如果所有的线程都是用户界面线程,这种方法是没有任何问题:这种方法能用于双向通讯。你要做的就是为每一个线程有目的的定义一个或多个windows消息。你可以通过用WM_USER消息作为基值来定义其它的自定义消息。
像这样定义:
WM_MYCUSTOMMSG equ WM_USER+100h
Windows will not use any value from WM_USER upward for its own messages so you can use the value WM_USER and above as your own custom message value.
If one of the thread is a user interface thread and the other is a worker one, you cannot use this method as two-way communication since a worker thread doesn't have its own window so it doesn't have a message queue. You can use the following scheme:
Windows将不使用WM_USER以上的任何值来作为它的消息,所以你能用WM_USER上面的任何值作为你自定义的消息值。如果一个线程是用户界面线程而另外一个线程是工作者线程,你不能用这种方法来进行双向通讯,因为一个工作者线程没有它自己的窗口所以它没有消息队列。但你可以用下面这种方案:
User interface Thread ------> global variable(s)----> Worker thread
Worker Thread ------> custom window message(s) ----> User interface Thread
In fact, we will use this method in our example.
The last communication method is an event object. You can view an event object as a kind of flag. If the event object is in "unsignalled" state, the thread is dormant or sleeping, in this state, the thread doesn't receive CPU time slice. When the event object is in "signalled" state,Windows "wakes up" the thread and it starts performing the assigned task.
事实上,我们将在我们的例子中用这种方法:
最后一种通讯方法是一个事件对象。你能将一个事件对象看作标记的一种。如果事件对象处于“无信号“状态,这个线程是休眠的或者是睡眠的。在这种状态的线程并不接收CPU时间片。当事件对象是”有信号“状态,windows”唤醒“这个线程并让它开始执行已分配任务。
Example:
例子:
You should download the example zip file and run thread1.exe. Click the "Savage Calculation" menu item. This will instruct the program to perform "add eax,eax " for 600,000,000 times. Note that during that time, you cannot do anything with the main window: you cannot move it, you cannot activate its menu, etc. When the calculation is completed, a message box appears. After that the window accepts your command normally.
To avoid this type of inconveniece to the user, we can move the "calculation" routine into a separate worker thread and let the primary thread continue with its user interface task. You can see that even though the main window responds more slowly than usual, it still responds
你能下载这个例子文件并且运行thread.exe.点“Savage Calculation “菜单项,程序将执行”add eax eax “指令600,000,000次。注意在这段时间,你不能对主窗口做任何事:你不能移动它,你不能启动它的菜单,等等。当计算完成,一个消息框将出现。在这之后窗口才能接收你正常的命令。为了避免对用户的这种不便,我们能移动”计算“程序给一个单独的工作者线程并且让主线程继续做它的用户界面工作。你能看出,虽然主窗口响应慢了很多,但是它任然在响应你。
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_CREATE_THREAD equ 1
IDM_EXIT equ 2
WM_FINISH equ WM_USER+100h
.data
ClassName db "Win32ASMThreadClass",0
AppName db "Win32 ASM MultiThreading Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
ThreadID DWORD ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
0,\
ADDR ThreadID
invoke CloseHandle,eax
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP
end start
Analysis:
分析:
The main program presents the user with a normal window with a menu. If the user selects "Create Thread" menu item, the program creates a thread as below:
主程序呈现给用户一个正常的窗口和菜单。如果用户选择“CreateThread”菜单项,这个程序将创建一个线程,代码如下:
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
The above function creates a thread that will run a procedure named ThreadProc concurrently with the primary thread. After the successful call, CreateThread returns immediately and ThreadProc begins to run. Since we do not use thread handle, we should close it else there'll be some leakage of memory. Note that closing the thread handle doesn't terminate the thread. Its only effect is that we cannot use the thread handle anymore.
上面的函数创建一个线程,这个线程运行一个名为ThreadProc的过程,它还和主线程并发执行。在成功调用之后,CreateThread立即返回并且ThreadProc开始运行。因为我们不能使用线程句柄,我们应该关闭它否则它将占用一些内存空间。注意:关闭线程句柄并不是终止线程。它的效果仅仅是我们在也不能使用这个线程句柄了。
ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP
As you can see, ThreadProc performs a savage calculation which takes quite a while to finish and when it finishs it posts a WM_FINISH message to the main window. WM_FINISH is our custom message defined like this:
如你所见,ThreadProc执行一个原始的计算,这个计算将花费一会儿的时间才能完成,当它完成时它传递一个WM_FINISH消息给主窗口。WM_FINISH是我们自定义的消息,句法如下:
WM_FINISH equ WM_USER+100h
You don't have to add WM_USER with 100h but it's safer to do so.
The WM_FINISH message is meaningful only within our program. When the main window receives the WM_FINISH message, it respons by displaying a message box saying that the calculation is completed.
You can create several threads in succession by selecting "Create Thread" several times.
你不必要为WM_USER加上100h 但是这样做比较安全。这个WM_FINISH消息仅仅在我们的程序中才有意义。当主程序接收到wm_finish消息时,它通过弹出一个消息框来响应这个消息,告诉用户计算已经完成。
你能连续的点击“CreateThread” 多次来创建多个线程。
In this example, the communication is one-way in that only the thread can notify the main window. If you want the main thread to send commands to the worker thread, you can so as follows:
在这个例子中,通讯是单向的,在这里仅有一个线程能通知主窗口。如果你想主线程发送命令给工作者线程。你只要如下做:
• add a menu item saying something like "Kill Thread" in the menu
• 在菜单中增加一个菜单项并在菜单中说一些像“Kill Thread”(Caption属性)
• a global variable which is used as a command flag. TRUE=Stop the thread, FALSE=continue the thread
• 声明一个用来作为命令标志的全局变量,TRUE==停止这个线程,FALSE=继续运行这个线程。
• Modify ThreadProc to check the value of the command flag in the loop.在ThreadProc函数的循环语句中添加一段代码用来检查命令标志的值。
When the user selects "Kill Thread" menu item, the main program will set the value TRUE in the command flag. When ThreadProc sees that the value of the command flag is TRUE, it exits the loop and returns thus ends the thread.
当用户选择”Kill Thread“菜单项时,主程序将命令标志设置为TRUE。当ThreadProc看到命令标志的值为TRUE时,它退出这个循环从而 ends指令了结束这个线程。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-1-8
放假回家在车上耽误了几天,呵呵
Tutorial 16: Event Object
第十六课:事件对象
________________________________________
We will learn what an event object is and how to use it in a multithreaded program.
我们将学习什么是一个事件对象和如何在多线程程序中使用它。
Download the example here.
Theory:
原理:
From the previous tutorial, I demonstrated how threads communicate with a custom window message. I left out two other methods: global variable and event object. We will use both of them in this tutorial.
An event object is like a switch: it has only two states: on or off. When an event object is turned on, it's in the "signalled" state. When it is turned off, it's in the "nonsignalled" state. You create an event object and put in a code snippet in the relevant threads to watch for the state of the event object. If the event object is in the nonsignalled state, the threads that wait for it will be asleep.When the threads are in wait state, they consume little CPU time.
一个事件对象如同一个开关:它仅有两种状态:开和关。当一个事件对象状态变为开时,它处于“有信号”状态。当它为关时,它处于“无信号状态”你创建一个事件对象并把它放置在和线程相关的代码片段中,线程等待这事件对象的状态。如果事件对象处于无信号状态,等待的线程将睡眠。当线程在等待态时,它消耗的CPU时间就小得多了。
You create an event object by calling CreateEvent function which has the following syntax:
你可以通过调用CreateEvent函数来创建一个事件对象,这个函数句法如下:
CreateEvent proto lpEventAttributes:DWORD,\
bManualReset:DWORD,\
bInitialState:DWORD,\
lpName:DWORD
lpEventAttribute--> If you specify NULL value, the event object is created with default security descriptor.
lpEventAttribute-->如果你指定的值为空(NULL),被创建的对象具有默认的安全性描述。
bManualReset--> If you want Windows to automatically reset the event object to nonsignalled state after WaitForSingleObject call, you must specify FALSE as this parameter. Else you must manually reset the event object with the call to ResetEvent.
bManualReset-->如果想在调用了WaitForSIngleObject函数后想让windows自动复位事件对象的状态为“无信号”状态,你必须指定FALSE作为这个参数的值。否则你必须调用ResetEvent函数来手动复位这个事件对象。
bInitialState--> If you want the event object to be created in the signalled state, specify TRUE as this parameter else the event object will be created in the nonsignalled state.
bInitialState-->如果你想让创建的事件对象处于“有信号状态”中,指定TURE作为这个参数的参数值,否则这个事件对象将在被创建后进入”无信号状态“。
lpName --> Pointer to an ASCII string that is the name of the event object. This name is used when you want to call OpenEvent.
lpName -->指向一个ASCII 字符串的指针。字符是事件对象的名称。当你想调用OpenEvent函数时,这个名字将被使用。
If the call is successful, it returns the handle to the newly created event object else it returns NULL.
You can modify the state of an event object with two API calls: SetEvent and ResetEvent. SetEvent function sets the event object into signalled state. ResetEvent does the reverse.
如果这个调用是成功的,它返回一个新创建的事件对象句柄,否则它的返回值为空(NULL)。 你能调用两个API函数来修改一个事件对象的状态: SetEvent 和 ResetEvent函数。SetEvent函数设置事件对象进入有信号状态。ResetEvent功能和SetEvent相反。
When the event object is created, you must put the call to WaitForSingleObject in the thread that wants to watch for the state of the event object. WaitForSingleObject has the following syntax:
当事件对象被创建时,你必须把WaitForSingleObject函数放置于想等待事件对象状态的线程中。WaitForSingleObject句法如下:
WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD
hObject --> A handle to one of the synchronization object. Event object is a type of synchronization object.
hObject -->同步对象的一个句柄,事件对象属于同步对象的一种。
dwTimeout --> specify the time in milliseconds that this function will wait for the object to be in signalled state. If the specified time has passed and the event object is still in nonsignalled state, WaitForSingleObject returns the the caller. If you want to wait for the object indefinitely, you must specify the value INFINITE as this parameter.
dwTimeout -->以毫秒为单位指定一个时间值,这个时间值是函数等待事件对象进入有信号状态的时间。如果指定的时间已经过了而事件对象还处于无信号状态,WaitForSingleObject将返回给调用者。如果你想不确定的等待事件对象,你必须指定INFINITE作为这个参数的值。
Example:
The example below displays a window waiting for the user to select a command from the menu. If the user selects "run thread", the thread starts the savage calculation. When it's finished, a message box appears informing the user that the job is done. During the time that the thread is running, the user can select "stop thread" to stop the thread.
下面的例子将显示一个窗口并等待用户从菜单中选择一个命令。如果用户选择“Run thread“,这个线程开始原始计算。当它完成后,弹出一个消息框通知用户工作已经完成。在线程运行的那段时间,用户能选择”stop thread” 来终止线程。
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h
.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED
.elseif ax==IDM_STOP_THREAD
mov EventStop,TRUE
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
.WHILE ecx!=0
.if EventStop!=TRUE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif
.ENDW
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
jmp ThreadProc
ret
ThreadProc ENDP
end start
Analysis:
In this example, I demonstrate another thread technique.
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
You can see that I create the event object and the thread during the processing of WM_CREATE message. I create the event object in the nonsignalled state with automatic reset. After the event object is created, I create the thread. However the thread doesn't run immediately because it waits for the event object to be in the signalled state as the code below:
你能看到,在处理WM_CREATE消息区间,我创建了事件对象和线程。我创建的事件对象为无信号状态并让windows自动复位它。在事件对象被创建后,我创建了线程。然而,这个线程并没有立即运行,因为它要等到事件对象的状态为有信号时才执行,代码如下:
ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
The first line of the thread procedure is the call to WaitForSingleObject. It waits infinitely for the signalled state of the event object before it returns. This means that even when the thread is created, we put it into a dormant state.
第一行是调用WaitForSIngleObject API函数的线程处理程序。在它返回之前,它将无限的等待事件对象到有信号状态。这意味着,即使是线程被创建,我们也将让它进入等待态。
When the user selects "run thread" command from the menu, we set the event object into signalled state as below:
当用户从菜单中选择了“run thread “命令,我们设置事件对象进入到有信号状态的代码如下:
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
The call to SetEvent turns the event object into the signalled state which in turn makes the WaitForSingleObject call in the thread procedure return and the thread starts running. When the user selects "stop thread" command, we set the value of the global variable "EventStop" to TRUE.
SetEvent这个调用改变事件对象的状态让事件对象进入到有信号状态。这种状态能让WaitForSingleObject函数调用在线程过程中返回并且让线程开始运行。当用户选择了“stop thread ”命令时,我们设置全局变量“EventStop”的值为TRUE。
.if EventStop==FALSE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif
This stops the thread and jumps to the WaitForSingleObject call again. Note that we don't have to manually reset the event object into nonsignalled state because we specify the bManualReset parameter of the CreateEvent call as FALSE.
这是终止线程并且跳转到WaitForSingleObject调用处。注意:我们并不需要手动恢复事件对象进入无信号状态,因为我们调用CreateEvent函数时指定了它的bManualReset参数的值为FALSE。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-1-8日晚
Tutorial 17: Dynamic Link Libraries
第十七课:动态链接库
________________________________________
In this tutorial, we will learn about DLLs , what are they and how to create them.
在这一课中,我们将学习关于动态链接库(DLL)的知识,什么是动态链接库和如何创建一个动态链接库。
Theory:
原理:
If you program long enough, you'll find that the programs you wrote usually have some code routines in common. It's such a waste of time to rewrite them everytime you start coding new programs. Back in the old days of DOS, programmers store those commonly used routines in one or more libraries. When they want to use the functions, they just link the library to the object file and the linker extracts the functions from the library and inserts them into the final executable file. This process is called static linking. C runtime libraries are good examples. The drawback of this method is that you have identical functions in every program that calls them. Your disk space is wasted storing several identical copies of the functions. But for DOS programs, this method is quite acceptable since there is usually only one program that's active in memory. So there is no waste of precious memory.
Under Windows, the situation becomes much more critical because you can have several programs running simultaneously. Memory will be eat up quickly if your program is quite large. Windows has a solution for this type of problem: dynamic link libraries. A dynamic link library is a kind of common pool of functions. Windows will not load several copies of a DLL into memory so even if there are many instances of your program running at the same time, there'll be only one copy of the DLL that program uses in memory. And I should clarify this point a bit. In reality, all processes that use the same dll will have their own copies of that dll. It will look like there are many copies of the DLL in memory. But in reality, Windows does it magic with paging and all processes share the same DLL code.So in physical memory, there is only one copy of DLL code. However, each process will have its own unique data section of the DLL.
如果你的程序足够长,你将发现:你通常为一个程序写代码的时候有一些程序代码是相同的。每当你为一个新程序编码时,重写它们只不过是在浪费时间。回到从前DOS的日子,程序员储存那些经常用到的相同的代码在一个或多个库文件中。当他们想用这些函数时,他们仅需要把这个库文件链接到目标文件中并且链接器从库文件中取出这些函数并把他们插入在最后的可执行文件中。这种处理方式被称为静态链接。C 运行库就是很好的例子。这种方法唯一的缺点,就是在每一个调用库文件的程序中,可能存在相同的程序代码。存储这些函数的多份复件浪费了你的磁盘空间。但是对于DOS程序员,这种方法是完全是可接受的,因为通常仅有一道程序运行在内存中,所以宝贵的内存并没有被浪费。
The program links to a DLL at runtime unlike the old static library. That's why it's called dynamic link library. You can also unload a DLL at runtime as well when you don't need it. If that program is the only one that uses the DLL, it'll be unloaded from memory immediately. But if the DLL is still used by some other program, the DLL remains in memory until the last program that uses its service unloads it.
程序在运行时才链接到动态链接库的这种方式并不像古老的静态链接。这也是为什么叫动态链接库的原因。当你不需要它时,你最好在运行时刻卸载这个DLL文件。如果仅有一个程序使用这个DLL文件,它将立即从内存中释放出来。但是如果动态链接库还被其它程序使用,那么这个DLL文件将保留在内存里,只到最后一个使用它的程序卸载它的服务。
However, the linker has a more difficult job when it performs address fixups for the final executable file. Since it cannot "extract" the functions and insert them into the final executable file, somehow it must store enough information about the DLL and functions into the final execuable file for it to be able to locate and load the correct DLL at runtime.
然而,当连接器为一个可执行文件完成地址修正后,它还有很多困难的工作要做。因为它不能提取这些函数并且把它们插入到最后的可执行文件中,为了能够在运行时定位和装载正确的DLL,它必须把关于这个DLL文件以及 在可执行文件中的函数的 足够多的 信息 用某种方式储存起来。
That's where import library comes in. An import library contains the information about the DLL it represents. The linker can extract the info it needs from the import libraries and stuff it into the executable file. When Windows loader loads the program into memory, it sees that the program links to a DLL so it searches for that DLL and maps it into the address space of the process as well and performs the address fixups for the calls to the functions in the DLL.
简而言之,存放这些信息的地方就是引入库。一个引入库包含DLL文件所表征的所有信息。连接器能从引入库中获取它所需要的信息并且填充它到可执行文件中。当windows装载器把一个程序装入内存中时,它设法让程序和dll链接在一起,所以它搜索那个DLL文件并把它映射到进程的地址空间中,然后为了调用这个dll文件中的函数,它还要完成地址修正。
You may choose to load the DLL yourself without relying on Windows loader. This method has its pros and cons:
你可以自己决定装载那个dll文件而没有必要依赖于windows的装载器。
这种方法有它肯定的地方和否定的地方:
• It doesn't need an import library so you can load and use any DLL even if it comes with no import library. However, you still have to know about the functions inside it, how many parameters they take and the likes.
它并不需要一个引入库,所以你能装载和使用任何的dll文件,即使它从来就没有引入库。然而,你需要知道它内部的函数功能,它们需要多少个参数和参数的类型。
• When you let the loader load the DLL for your program, if the loader cannot find the DLL it will report "A required .DLL file, xxxxx.dll is missing" and poof! your program doesn't have a chance to run even if that DLL is not essential to its operation. If you load the DLL yourself, when the DLL cannot be found and it's not essential to the operation, your program can just tell the user about the fact and go on.
当你让装载器为你的程序装载一个dll文件时,如果装载器不能找到dll文件它将报告”A required .DLL file,xxxxx.dll is missing”并且有POOF的声音!这样你的程序将没有机会运行,即使这个dll文件并不是程序运行时必不可少的。如果是你自己加载这个dll文件,当dll文件不能被发现时它对程序运行来说并不是必不可少的,你的程序只是告诉用户事实然后继续运行。
• You can call *undocumented* functions that are not included in the import libraries. Provided that you know enough info about the functions.
你能调用“无书面文件的”的函数,这些函数并不包含在输入表中。前提是假如你知道这个函数足够多的信息。
• If you use LoadLibrary, you have to call GetProcAddress for every function that you want to call. GetProcAddress retrieves the entrypoint address of a function in a particular DLL. So your code might be a little bit larger and slower but by not much.
如果你用LoadLibrary,你必须为你想调用的每一个函数调用GetProcAddress函数。GetProcAddress函数在一个特殊的dll中获得函数的入口地址。所以你的程序运行起来可能要多占一些内存,也会有点慢不过并不明显。
Seeing the advantages/disadvantages of LoadLibrary call, we go into detail how to create a DLL now.
The following code is the DLL skeleton.
观看LoadLibrary调用的有利条件和不利条件,现在我们现在进入如何创建dll的细节。下面这些代码是dll文件的框架。
;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
; This is a dummy function 这是一个空函数
; It does nothing. I put it here to show where you can insert functions into
; a DLL. 它什么都没有,我放置在这是为了告诉你能在dll文件的什么地方插入函数
;----------------------------------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp
End DllEntry
;-------------------------------------------------------------------------------------
; DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction
The above program is the DLL skeleton. Every DLL must have an entrypoint function. Windows will call the entrypoint function everytime that:
上面的程序是dll的框架。每一个dll必须有一个入口函数。Windows每一次遇到如下情况时都将调用入口函数。
• The DLL is first loaded 第一次装载这个dll文件。
• The DLL is unloaded dll文件被卸载
• A thread is created in the same process 同一进程的一个线程被创建
• A thread is destroyed in the same process
同一进程的一个线程被销毁。
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
You can name the entrypoint function anything you wish so long as you have a matching END <Entrypoint function name>. This function takes three parameters, only the first two of which are important.
你能用任何你想要的字串来为入口点函数命名只要你让它和END标志想匹配。END<函数入口点名称>.这个函数有三个参数,仅有开始的两个是重要的。
hInstDLL is the module handle of the DLL. It's not the same as the instance handle of the process. You should keep this value if you need to use it later. You can't obtain it again easily.
HinstDll是dll的模块句柄。它并不同于进程的实例句柄。如果你稍后将用到它你应该保存它的值。你能不费力的再次获得它。
reason can be one of the four values:
reason的值是下面四个中的一个:
• DLL_PROCESS_ATTACH The DLL receives this value when it is first injected into the process address space. You can use this opportunity to do initialization.
DLL_PROCESS_ATTACH当它第一次注入到进程的地址空间时,dll文件接收到这个值。你能使用这个机会做一些初始化的工作。
• DLL_PROCESS_DETACH The DLL receives this value when it is being unloaded from the process address space. You can use this opportunity to do some cleanup such as deallocate memory and so on.
DLL_PROCESS_DETACH 当它从进程地址空间中卸载时,dll接收到这个值。你能利用这个机会做一些清理工作。例如:释放内存等等。
• DLL_THREAD_ATTACH The DLL receives this value when the process creates a new thread.
DLL_THEAD_ATTACH 当进程创建一个新线程时,dll接收到这个值。
• DLL_THREAD_DETACH The DLL receives this value when a thread in the process is destroyed.
DLL_THREAD_DETACH 当进程中的线程被销毁时,dll接收到这个值。
You return TRUE in eax if you want the DLL to go on running. If you return FALSE, the DLL will not be loaded. For example, if your initialization code must allocate some memory and it cannot do that successfully, the entrypoint function should return FALSE to indicate that the DLL cannot run.
如果你想dll继续运行,在eax中返回TRUE,如果返回FALSE,这个dll将不被装载。例如:如果初始化代码必须分配一些内存而不成功时,这个入口函数应该返回FALSE来指示dll文件不能运行。
You can put your functions in the DLL following the entrypoint function or before it. But if you want them to be callable from other programs, you must put their names in the export list in the module definition file (.def).
你能把你的函数放在dll文件的入口函数之后或者之前。但是如果你想让它们能被其它程序随时支取,你必须把它们的名字放在模块定义文件的输出表中。
A DLL needs a module definition file in its developmental stage. We will take a look at it now.
一个dll文件需要一个模块定义文件在它们的发展阶段。现在我们来看一下它:
LIBRARY DLLSkeleton
EXPORTS TestFunction
Normally you must have the first line.The LIBRARY statement defines the internal module name of the DLL. You should match it with the filename of the DLL.
通常第一行你必须有。LIBRARY 语句定义了dll文件的内部模块名。你应该让dll的文件名和它相配。
The EXPORTS statement tells the linker which functions in the DLL are exported, that is, callable from other programs. In the example, we want other modules to be able to call TestFunction, so we put its name in the EXPORTS statement.
EXPORTS语句告诉连接器在dll中的输出函数,也就是能被其它程序随时支取的函数。在这个例子中,我们想让其它模块能够调用TextFunction函数,所以我们把它的名字放在EXPORTS语句中。
Another change is in the linker switch. You must put /DLL switch and /DEF:<your def filename> in your linker switches like this:
例外的变化是连接器的开关。你必须放置/DLL开关和/DEF:<your def filename>在你的链接器中,像这样:
link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj
The assembler switches are the same, namely /c /coff /Cp. So after you link the object file, you will get .dll and .lib. The .lib is the import library which you can use to link to other programs that use the functions in the DLL.
汇编器的开关选项是一样的 ,就是/c /coff /Cp。当你连接成目标文件后,你将得到.dll和.lib文件。这.lib是引入库,这个引入库能用来链接其它程序,链接之后,其它程序就能使用在dll中的函数。
Next I'll show you how to use LoadLibrary to load a DLL.
下面我将向你现实如何用LoadLibrary来装载一个dll文件。
;---------------------------------------------------------------------------------------------
; UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0
.data?
hLib dd ? ; the handle of the library (DLL)
TestHelloAddr dd ? ; the address of the TestHello function
.code
start:
invoke LoadLibrary,addr LibName
;---------------------------------------------------------------------------------------------------------
; Call LoadLibrary with the name of the desired DLL. If the call is successful
; it will return the handle to the library (DLL). If not, it will return NULL
; You can pass the library handle to GetProcAddress or any function that requires
; a library handle as a parameter.
传递你想要的dll名字给LoadLibrary并调用它,如果调用成功,它将返回一个动态链接库的句柄,如果不成功,它返回NULL。你能传递dll的句柄给GetProcAddress或者是任何需要一个动态链接库句柄作为它参数的函数。
;------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetProcAddress,hLib,addr FunctionName
;-------------------------------------------------------------------------------------------------------------
; When you get the library handle, you pass it to GetProcAddress with the address
; of the name of the function in that DLL you want to call. It returns the address
; of the function if successful. Otherwise, it returns NULL
; Addresses of functions don't change unless you unload and reload the library.
; So you can put them in global variables for future use.
当你得到动态链接库句柄时,你传递一个在dll文件中你想调用的函数名的地址给GetProcAddress函数。如果成功,它将返回函数的地址。其它情况,它返回NULL。
除非你卸载了库文件或者是重新加载库文件,否则函数的地址不会改变。所以你能将它们放在一全局变量中已备将来使用。
;-------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;-------------------------------------------------------------------------------------------------------------
; Next, you can call the function with a simple call with the variable containing
; the address of the function as the operand.
以后您就可以和调用其它函数一样调用该函数了。其中要把包含函数地址信息的变量用方括号括起来。
;-------------------------------------------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;-------------------------------------------------------------------------------------------------------------
; When you don't need the library anymore, unload it with FreeLibrary.
当你再也不需要这个库文件时,调用FreeLibrary卸载它。
;-------------------------------------------------------------------------------------------------------------
.endif
invoke ExitProcess,NULL
end start
So you can see that using LoadLibrary is a little more involved but it's also more flexible.
如你所见,使用LoadLibrary是有那么一点棘手,但是它有更多的灵活性。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-1-9
Tutorial 18: Common Controls
第十八课:通用控件
________________________________________
We will learn what common controls are and how to use them. This tutorial will be a quick introduction to them only.
我们将学习什么是通用控件,如何应用通用控件。这一课仅仅是快速的介绍它们。
Download the example source code here.
Theory:
原理:
Windows 95 comes with several user-interface enhancements over Windows 3.1x. They make the GUI richer. Several of them are in widely used before Windows 95 hit the shelf, such as status bar, toolbars etc. Programmers have to code them themselves. Now Microsoft has included them with Windows 9x and NT. We will learn about them here.
Windows95比起windows3.1x来提供了几个加强的用户界面控件。他们让Gui更富有。他们中的几个在windows95架构之前就被使用,例如:状态栏,工具栏等等。程序员必须为它们编码才能使用它们。现在windows把它们包含在windows9x和windowsNt中。我们将在这儿学习它们。
These are the new controls:
这些是新控件:
• Toolbar 工具栏
• Tooltip 提示文本
• Status bar 状态栏
• Property sheet 属性表
• Property page 属性页
• Tree view 树视图 (如:目录树)
• List view 列表视图
• Animation 动画
• Drag list 拖动列表框
• Header 标题
• Hot-key 热键
• Image list 图像列表
• Progress bar 进度条
• Right edit
• Tab 标签 工作表选项卡
• Trackbar 跟踪条
• Up-down 滚动条
Since there are many of them, loading them all into memory and registering them would be a waste of resource. All of them, with the exception of rich edit control, are stored in comctl32.dll with applications can load when they want to use the controls. Rich edit control resides in its own dll, richedXX.dll, because it's very complicated and hence larger than its brethren.
You can load comctl32.dll by including a call to InitCommonControls in your program. InitCommonControls is a function in comctl32.dll, so referring to it anywhere in your code will make PE loader load comctl32.dll when your program runs.You don't have to execute it, just include it in your code somewhere. This function does NOTHING! Its only instruction is "ret". Its sole purpose is to include reference to comctl32.dll in the import section so that PE loader will load it whenever the program is loaded. The real workhorse is the DLL entrypoint function which registers all common control classes when the dll is loaded. Common controls are created based on those classes just like other child window controls such as edit, listbox etc.
Now we learn how to create them. You can use a resource editor to incorporate them into dialog boxes or you can create them yourself. Nearly all common controls are created by calling CreateWindowEx or CreateWindow, passing it the name of the control class. Some common controls have specific creation functions , however, they are just wrappers around CreateWindowEx to make it easier to create those controls. Existing specific creation functions are listed below:
现在我们学习如何创建它们。你可以用资源编辑器来把它们并入到对话框中,也可以自己创建它们。几乎创建所有通用控件的方法,都是把控件名作为参数传递给CreateWindowEx或CreateWindow函数并调用这两个函数。一些通用控件有它们自己的创建函数,然而,它们仅仅是对CreateWindowEx函数的包装以便让它更容易地创建这些控件。现有指定的创建函数列表如下:
• CreateToolbarEx
• CreateStatusWindow
• CreatePropertySheetPage
• PropertySheet
• ImageList_Create
In order to create common controls, you have to know their class names. They are listed below:
为了创建通用控件,你必须知道它们的类名.类列表如下:
Class Name
类名 Common Control
通用控件
ToolbarWindow32 Toolbar
tooltips_class32 Tooltip
msctls_statusbar32 Status bar
SysTreeView32 Tree view
SysListView32 List view
SysAnimate32 Animation
SysHeader32 Header
msctls_hotkey32 Hot-key
msctls_progress32 Progress bar
RICHEDIT Rich edit
msctls_updown32 Up-down
SysTabControl32 Tab
Property sheets and property pages and image list control have their own specific creation functions. Drag list control are souped-up listbox so it doesn't have its own class. The above class names are verified by checking resource script generated by Visual C++ resource editor. They differ from the class names listed by Borland's win32 api reference and Charles Petzold's Programming Windows 95. The above list is the accurate one.
Property sheets ,property pages 和image list 控件有它们自己指定的创建函数。Drag list 控件其实是可以拖拉的Listbox 控件,所以它不需要有它自己类。要验证上面的这些类名,可以通过检查Visual C++资源编辑器产生资源脚本。它们的类名和borland公司的win32手册还有charles petzolds 的《Programming Windows 95》所列出的类名有不同之处。上面的类列表是精确的。
Those common controls can use general window styles such as WS_CHILD etc. They also have their own specific styles such as TVS_XXXXX for tree view control, LVS_xxxx for list view control, etc. Win32 api reference is your best friend in this regard.
这些通用控件可以使用所有的窗口样式例如:WS_CHILD等等。它们也有它们自己特有的样式例如:tree View 控件的TVS_XXXX ,list view 控件的LVS_XXXX。等等。在这点上win32API手册是你最好的朋友。
Now that we know how to create common controls, we can move on to communication method between common controls and their parent. Unlike child window controls, common controls don't communicate with the parent via WM_COMMAND. Instead they send WM_NOTIFY messages to the parent window when some interesting events occur with the common controls. The parent can control the children by sending messages to them. There are also many new messages for those new controls. You should consult your win32 api reference for more detail.
既然我们知道了如何创建一个窗口,我们就可以往前走到通用控件和它们父窗口的通讯方法上。通用窗口并不像子窗口控件一样用WM_COMMAND消息来和父窗口通讯。当在通用控件上那些令人感兴趣的事件发生时,通用控件发送WM_NOTIFY消息给它们的父窗口。通过发送消息给通用控件,这个父亲就能够控制它的孩子们。对于这些新的控件,这里有很多新的消息。至于更多的细节你应该参考你的Win32 API手册。
Let's examine progress bar and status bar controls in the following example.
在下面的例子中,让我们来分析进度条和状态栏控件。
Sample code:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDC_PROGRESS equ 1 ; control IDs
IDC_STATUS equ 2
IDC_TIMER equ 3
.data
ClassName db "CommonControlWinClass",0
AppName db "Common Control Demo",0
ProgressClass db "msctls_progress32",0 ; the class name of the progress bar
Message db "Finished!",0
TimerID dd 0
.data?
hInstance HINSTANCE ?
hwndProgress dd ?
hwndStatus dd ?
CurrentStep dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
WS_CHILD+WS_VISIBLE,100,\
200,300,20,hWnd,IDC_PROGRESS,\
hInstance,NULL
mov hwndProgress,eax
mov eax,1000 ; the lParam of PBM_SETRANGE message contains the range
mov CurrentStep,eax
shl eax,16 ; the high range is in the high word
invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
mov hwndStatus,eax
invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer
mov TimerID,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.if TimerID!=0
invoke KillTimer,hWnd,TimerID
.endif
.elseif uMsg==WM_TIMER ; when a timer event occurs
invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 ; step up the progress in the progress bar
sub CurrentStep,10
.if CurrentStep==0
invoke KillTimer,hWnd,TimerID
mov TimerID,0
invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start
Analysis:
分析:
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls
I deliberately put InitCommonControls after ExitProcess to demonstrate that InitCommonControls is just there for putting a reference to comctl32.dll in the import section. As you can see, the common controls work even if InitCommonControls doesn't execute.
我故意把InitCommonControls放在ExitProcess之后来证明那个InitCommonControls函数在这里仅仅是放置comctl32.dll的说明信息在输入节区中。如你所见,即使是InitCommonControls不执行,通用控件也能正常工作。
Here is where we create the common control. Note that this CreateWindowEx call contains hWnd as the parent window handle. It also specifies a control ID for identifying this control. However, since we have the control's window handle, this ID is not used. All child window controls must have WS_CHILD style.
After the progress bar is created, we can set its range. The default range is from 0 to 100. If you are not satisfied with it, you can specify your own range with PBM_SETRANGE message. lParam of this message contains the range, the maximum range is in the high word and the minimum one is in the low word. You can specify how much a step takes by using PBM_SETSTEP message. The example sets it to 10 which means that when you send a PBM_STEPIT message to the progress bar, the progress indicator will rise by 10. You can also set your own indicator level by sending PBM_SETPOS messages. This message gives you tighter control over the progress bar.
在进度条被创建之后,我们能设置它的范围。默认的范围是0-100.如果你对这个值并不满意,你能在传递PBM_SETRANGE消息的时候指定你想要的范围。消息的lParam包含这个范围,最大的范围值在高字节中,最小的范围值在低字节中。你通过使用PBM_SETSTEP消息,可以指定进度条移动一格的步长。例子中设置它的值为10,这意味着当你发送PBM_STEPIT消息给进度条的时候,这个进度条的指示器将以10为单位增长。你也能通过发送PBM_SETPOS消息来设置你自己的指示器位置。这个消息能让你更好的控制进度条。
Next, we create a status bar by calling CreateStatusWindow. This call is easy to understand so I'll not comment on it. After the status window is created, we create a timer. In this example, we will update the progress bar at a regular interval of 100 ms so we must create a timer control. Below is the function prototype of SetTimer.
SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD
hWnd : Parent window handle
hWnd:父窗口句柄。
TimerID : a nonzero timer identifier. You can create your own identifier.
TimerID:一个非零的定时器标识符。你能创建你特有的标识符。
TimerInterval : the timer interval in milliseconds that must pass before the timer calls the timer procedure or sends a WM_TIMER message
TimerInterval:以毫秒为单位的时间间隔,它必须在定时器过程调用或者是发送WM_TIMER之前传送这个参数给定时器。
lpTimerProc : the address of the timer function that will be called when the time interval expires. If this parameter is NULL, the timer will send WM_TIMER message to the parent window instead.
LpTimerProc:定时器过程的地址。当定时器的时间间隔到达时,这个函数将被调用。如果这个参数值为空(NULL),定时器将发送WM_TIMER消息来代替这个过程。
If this call is successful, it will return the TimerID. If it failed, it returns 0. This is why the timer identifer must be a nonzero value.
如果调用成功,它返回定时器的ID。如果失败,它返回0.这就是为什么定时器标识必须是非0值的原因。
When the specified time interval expires, the timer sends a WM_TIMER message. You will put your code that will be executed here. In this example, we update the progress bar and then check if the maximum limit has been reached. If it has, we kill the timer and then set the text in the status window with SB_SETTEXT message. A message box is displayed and when the user clicks OK, we clear the text in the status bar and the progress bar.
当指定的时间间隔到达时,定时器发送一个WM_TIMER消息。你可以将你想执行的代码放在这里。在这个例子中,我们更新这个进度条,然后检查它的最大上限是否到达。如果达到上限,我们发送SB_SETTEXT消息从而在状态栏中设置一文本。当用户点击OK时,一个消息框被弹出,我们清除状态栏中的文本和进度条。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-1-9日
Tutorial 19: Tree View Control
第十九课:树视图控件
________________________________________
In this tutorial, we will learn how to use tree view control. Moreover, we will also learn how to do drag and drop under tree view control and how to use an image list with it.
在这一课中,我们将学习如何使用一个树视图控件。此外,我们还将学习如何拖放一个树视图以及如何让它和一个图像列表一起使用。
Download the example here.
Theory:原理:
A tree view control is a special kind of window that represents objects in hierarchical order. An example of it is the left pane of Windows Explorer. You can use this control to show relationships between objects.
You can create a tree view control by calling CreateWindowEx, passing "SysTreeView32" as the class name or you can incorporate it into a dialog box. Don't forget to put InitCommonControls call in your code.
There are several styles specific to the tree view control. These three are the ones mostly used.
一个树视图控件是一种很特别的窗口,它表征了多个对象之间的层次关系。它的一个例子就是windows资源管理器左边的长方块。你能用这个控件显示对象之间的联系。你可以通过调用CreateWindowEx函数创建一个树视图,传递“SysTreeView32”字串作为类名或者是将它并入到一个对话框中。不要忘记在你的代码中放置InitCommonControls函数调用。
树视图有多种样式供用户选择使用。但这三种是用户通常使用的。
TVS_HASBUTTONS == Displays plus (+) and minus (-) buttons next to parent items. The user clicks the buttons to expand or collapse a parent item's list of child items. To include buttons with items at the root of the tree view, TVS_LINESATROOT must also be specified.
TVS_HASBUTTONS=紧接于父选项显示加号(+)和(-)减号按钮。用户点击按钮来展开和折叠一个父选项的子项目列表。选项包含这两个按钮是树视图的根本,所以TVS_LINESATROOT必须被指定。
TVS_HASLINES == Uses lines to show the hierarchy of items.
TVS_HASLINES = 用线条来显示选项之间的层次关系
TVS_LINESATROOT == Uses lines to link items at the root of the tree-view control. This value is ignored if TVS_HASLINES is not also specified.
TVS_LINESATROOT= 用线来连接选项是树视图的根本。如果TVS_HASLINES没有被指定,这个值就是看忽略的。
The tree view control, like other common controls, communicates with the parent window via messages. The parent window can send various messages to it and the tree view control can send "notification" messages to its parent window. In this regard, the tree view control is not different that any window.
树视图和其它的通用控件一样,也是通过消息和父窗口进行通讯。父窗口可以发送不同的消息给它并且树视图能也能发送“NOTIFICATION“消息给它的父窗口。在这点上,树视图和其它任何窗口并没有什么不同。
When something interesting occurs to it, it sends a WM_NOTIFY message to the parent window with accompanying information.
当一些它感兴趣的事件发生时,它就发送一个WM_NOTIFY消息给它的父窗口并伴随着附加信息。
WM_NOTIFY
wParam == Control ID, this value is not guaranteed to be unique so we don't use it. Instead, we use hwndFrom or IDFrom member of the NMHDR structure pointed to by lParam
wParam == 控件ID,这个值不能保证它的唯一性,所以我们不用它。
作为替代,我们使用由lParam参数指向的NMHDR结构的hwndFrom或Idfrom成员。
lParam == Pointer to NMHDR structure. Some controls may pass a pointer to larger structure but it must have a NMHDR structure as its first member. That is, when you have lParam, you can be sure that it points to a NMHDR structure at least.
lParam == 指向NMHDR的指针。一些控件可能传递一个指向一个大结构的指针,但是它必须要有一个NMHDR结构作为它的第一个成员。也就是,当你有一个lParam时,你必须确信它至少指向一个NMHDR结构体。
Next we will examine NMHDR structure.
下面,我们分析这个NMHDR结构体。
hwndFrom is the window handle of the control that sends this WM_NOTIFY message.
HwndFrom是发送WM_NOTLFY消息的控件窗口句柄,。
idFrom is the control ID of the control that sends this WM_NOTIFY message.
idFrom是发送WM_NOTIFY消息的控件ID值。
code is the actual message the control wants to send to the parent window.
Code实际是控件想发送给父窗口的消息。
Tree view notifications are those with TVN_ at the beginning of the name. Tree view messages are those with TVM_, like TVM_CREATEDRAGIMAGE. The tree view control sends TVN_xxxx in the code member of NMHDR. The parent window can send TVM_xxxx to control it.
树视图的通知名前面有TVN_这样的前缀。树视图的消息前面的前缀是TVM_,如:
TVM_CREATEDRAGIMAGE。树视图控件发送TVN_XXXX这样的消息在NMHDR的code成员中。而父窗口发送TVM_XXXX这样的消息来控制它。
Adding items to a tree view control
为树视图控件添加一个选项。
After you create a tree view control, you can add items to it. You can do this by sending TVM_INSERTITEM to it.
在你创建了一个树视图之后,你能添加它的选项。你能通过发送TVM_INSERTITEM给它时做到这一点。
TVM_INSERTITEM
wParam = 0;
lParam = pointer to a TV_INSERTSTRUCT;
You should know some terminology at this point about the relationship between items in the tree view control.
An item can be parent, child, or both at the same time. A parent item is the item that has some other subitem(s) associated with it. At the same time, the parent item may be a child of some other item. An item without a parent is called a root item. There can be many root items in a tree view control. Now we examine TV_INSERTSTRUCT structure
在这里你应该知道一些关于在树视图中的选项之间的术语。
一个选项可以是父亲,孩子,或者在同时刻两者兼得。一个父选项有很多和它关联的子项。同时,这个父选项可能又是其它项的子项。一个没有父亲的项叫根选项。一个树视图中可以有很多个根选项。现在我们分析TV_INSERTSTRUCT。
TV_INSERTSTRUCT STRUCT DWORD
hParent DWORD ?
hInsertAfter DWORD ?
ITEMTYPE <>
TV_INSERTSTRUCT ENDS
hParent = Handle to the parent item. If this member is the TVI_ROOT value or NULL, the item is inserted at the root of the tree-view control.
hParent = 父选项的句柄。如果这个成员的值为TVI_ROOT或是NULL,那么这个选项作为树视图的根项。
hInsertAfter = Handle to the item after which the new item is to be inserted or one of the following values:
在一个选项后面插入的新选项的句柄或者是下面这些值:
• TVI_FIRST ==> Inserts the item at the beginning of the list.
• TVI_FIRST ==》在列表的开始插入新项
• TVI_LAST ==> Inserts the item at the end of the list.
• TVI_LAST ==》在列表的最后插入新项
• TVI_SORT ==> Inserts the item into the list in alphabetical order.
• TVI_SORT 按字母顺序插入新项。
ITEMTYPE UNION
itemex TVITEMEX <>
item TVITEM <>
ITEMTYPE ENDS
This structure is used to send and receive info about a tree view item, depending on messages. For example, with TVM_INSERTITEM, it is used to specify the attribute of the item to be inserted into the tree view control. With TVM_GETITEM, it'll be filled with information about the selected tree view item.
这个结构依赖于消息,用于发送和接收一个树视图选项的信息。例如:有TVM_INSERTITEM,它被用于指定插入到树视图控件中的选项的属性。而对于TVM_GETITEM,当树视图中的选项被选中时,它将填充这个选项的信息。
imask is used to specify which member(s) of the TV_ITEM structure is (are) valid. For example, if the value in imask is TVIF_TEXT, it means only the pszText member is valid. You can combine several flags together.
Imask被用于指定TV_ITEM结构中的那个 成员是有效的。例如,如果在imask中的值是TVIF_TEXT,它意味着只有psztext成员是有效的。你能将几个标记组合在一起。
hItem is the handle to the tree view item. Each item has its own handle, like a window handle. If you want to do something with an item, you must select it by its handle.
hItem 是一个指向树视图项的句柄。每一个项目都有它自己的句柄,就像窗口一样。如果你想让一个项目做一些事,你必须通过它的句柄选择它。
pszText is the pointer to a null-terminated string that is the label of the tree view item.
PszText是一个指向以空字符结束的字串,这个字符串是树视图项的标签。
cchTextMax is used only when you want to retrieve the label of the tree view item. Because you will supply the pointer to the buffer in pszText, Windows has to know the size of the provided buffer. You have to give the size of the buffer in this member.
CchTextMax仅当你想得到树视图项的标签时才用这个参数。因为你在pszText中为指针提供了缓冲区,windows必须知道这个被提供的缓冲区的大小。所以,你必须在成员中给定缓冲区的大小。
iImage and iSelectedImage refers to the index into an image list that contains the images to be shown when the item is not selected and when it's selected. If you recall Windows Explorer left pane, the folder images are specified by these two members.
iImage和iSelectedImage引用一个图像列表的索引号,当一个项被选定或没被选定时,图像列表将对应的图像显示出来。如果你回想一下windows资源管理器左边的长方块,文件夹的图标就是被这两个成员指定。
In order to insert an item into the tree view control, you must at least fill in the hParent, hInsertAfter and you should fill imask and pszText members as well.
为了插入一个选项到树视图控件中,你至少要填写hParent,hInsertAfter,成员,另外您还要设定imask和pszText值。
Adding images to the tree view control
在树视图中添加图像
If you want to put an image to the left of the tree view item's label, you have to create an image list and associate it with the tree view control.
如果你想在树视图项目标签的左边放置一个图像。你必须创建一个图像列表并让它和树视图控件联合在一起。
You can create an image list by calling ImageList_Create.
你通过调用ImageList_Create函数创建一个图像列表。
ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, \
cInitial:DWORD, cGrow:DWORD
This function returns the handle to an empty image list if successful.
如果创建成功,这个函数返回一个空图像列表句柄。
cx == width of each image in this image list, in pixels.
Cx ==在图像列表中的每一个图像的宽,以像素为单位。
cy == height of each image in this image list, in pixels. Every image in an image list must be equal to each other in size. If you specify a large bitmap, Windows will use cx and cy to *cut* it into several pieces. So you should prepare your own image as a strip of pictures with identical dimensions.
Cy ==在图像列表中的每一个图像的高,以像素为单位。在图像列表中的每一个图像彼此之间的大小必须相等。如果你指定了一张大的位图,windows会按cx,cy的标准来把这张图剪切成几块。所以你自己准备的图像应该有相同大小的尺寸。
flags == specify the type of images in this image list whether they are color or monochrome and their color depth. Consult your win32 api reference for more detail
Flags == 指定在图像列表中的图像类型。诸如它们是否有颜色或者是不是单色和它们的色深。更多的细节请参考你的win32 api 手册。
cInitial == The number of images that this image list will initially contain. Windows will use this info to allocate memory for the images.
cInitial == 图像列表中最初包含的图像数目。Windows将用这信息为这些图像分配内存。
cGrow == Amount of images by which the image list can grow when the system needs to resize the list to make room for new images. This parameter represents the number of new images that the resized image list can contain.
An image list is not a window! It's only an image deposit for use by other windows.
cGrow == 当系统需要调整图像列表的大小以便为新增的图像腾出空位时,cGrow参数表示这个图像列表还能增加的图像数目。一个图像列表并不是一个窗口,它仅仅是存放其它窗口使用到的图像。
After an image list is created, you can add images to it by calling ImageList_Add
在图像列表被创建后,你能通过调用ImageList_Add函数来为它添加图像。
ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD
This function returns -1 if unsuccessful.
如果函数调用不成功,返回值为-1.
himl == the handle of the image list you want to add images to. It is the value returned by a successful call to ImageList_Create
Himl == 你想添加到图像列表中的图像的句柄。它是调用ImageList_Create函数成功后返回的值。
hbmImage == the handle to the bitmap to be added to the image list. You usually store the bitmap in the resource and load it with LoadBitmap call. Note that you don't have to specify the number of images contained in this bitmap because this information is inferred from cx and cy parameters passed to ImageList_Create call.
HbmImage == 将被添加到图像列表中的位图的句柄。你通常在资源中储存这个位图并通过调用LoadBitmap函数来装载它。注意:你没有必要指定这个位图所包含的图像的数目,因为这些信息能从传递给ImageList_Create函数的cx和cy参数中被推定。
hbmMask == Handle to the bitmap that contains the mask. If no mask is used with the image list, this parameter is ignored.
HbmMask == 包含者掩码的位图句柄。如果掩码不被用于图像列表中,这个参数被忽略。
Normally, we will add only two images to the image list for use with the tree view control: one that is used when the tree view item is not selected, and the other when the item is selected.
通常,为了能使用树视图控件,我们将仅添加两个图像到图像列表中。:一个用于当树视图项目没有被选定时,一个用于树视图项目被选定时。
When the image list is ready, you associate it with the tree view control by sending TVM_SETIMAGELIST to the tree view control.
TVM_SETIMAGELIST
wParam = type of image list to set. There are two choices:
设置图像列表的类型。这里有两种选择:
o TVSIL_NORMAL Set the normal image list, which contains the selected and unselected images for the tree-view item.
TVSIL_NORMAL 设置正常的图像列表,对于树视图项目,它包含被选定和未被选定两种图像。
o TVSIL_STATE Set the state image list, which contains the images for tree-view items that are in a user-defined state.
TVSIL_STATE 设置状态图像列表,这种列表对于树视图项目中那些自定义的每一个状态都包含一个图像。
lParam = Handle to the image list
图像列表句柄。
Retrieve the info about tree view item
获取树视图选项的信息
You can retrieve the information about a tree view item by sending TVM_GETITEM message.
你能通过发送TVM_GETITEM消息来获取有关一个树视图选项的信息。
TVM_GETITEM
wParam = 0
lParam = pointer to the TV_ITEM structure to be filled with the information 指向一个被填充的TV_ITEM结构体的指针。
Before you send this message, you must fill imask member with the flag(s) that specifies which member(s) of TV_ITEM you want Windows to fill. And most importantly, you must fill hItem with the handle to the item you want to get information from. And this poses a problem: How can you know the handle of the item you want to retrieve info from? Will you have to store all tree view handles?
The answer is quite simple: you don't have to. You can send TVM_GETNEXTITEM message to the tree view control to retrieve the handle to the tree view item that has the attribute(s) you specified. For example, you can query the handle of the first child item, the root item, the selected item, and
so on.
答案是相当简单的:你没有必要。你能发送TVM_GETNEXTITEM消息给树视图控件来获取那个有你指定的属性值的树视图选项。例如:你能查询第一个孩子选项,根选项,已经被选定的选项的句柄,等等。
TVM_GETNEXTITEM
wParam = flag
lParam = handle to a tree view item (only necessary for some flag values) 一个树视图选项的句柄(仅对于一些flag值才是必须的)
The value in wParam is very important so I present all the flags below:
在wParam中的值是非常重要的,所以我把所有的标记呈现在下面:
o TVGN_CARET Retrieves the currently selected item.
TVGN_CARET 检索当前选中的项。
o TVGN_CHILD Retrieves the first child item of the item specified by the hitem parameter
TVGN_CHILD 检索由hitem参数指定的选项的第一个孩子选项。
o TVGN_DROPHILITE Retrieves the item that is the target of a drag-and-drop operation.
TVGND_DROPHILITE 检索那个拖放操作的目标项。
o TVGN_FIRSTVISIBLE Retrieves the first visible item.
TVGN_FIRSTVISIBLE 检索第一个可见的项。
o TVGN_NEXT Retrieves the next sibling item.
TVGN_NEXT 检索下一个兄弟项。
o TVGN_NEXTVISIBLE Retrieves the next visible item that follows the specified item. The specified item must be visible. Use the TVM_GETITEMRECT message to determine whether an item is visible.
TVGN_NEXTVISIBLE 检索跟指定项后下一个显示的项。这个指定的项必须可见的。用TVM_GETITEMRECT消息来判断一个项是不是可见的。
o TVGN_PARENT Retrieves the parent of the specified item.
TVGN_PARENT 检索指定项的父亲。
o TVGN_PREVIOUS Retrieves the previous sibling item.
TVGN_PREVIOUS 检索上一个兄弟项
o TVGN_PREVIOUSVISIBLE Retrieves the first visible item that precedes the specified item. The specified item must be visible. Use the TVM_GETITEMRECT message to determine whether an item is visible.
TVGN_PREVIOUSVISILE 检索先前指定的第一个可见项。这个指定项必须是可见的。用TVM_GETITEMRECT消息来确定一个项目是否是可见的。
o GN_ROOT Retrieves the topmost or very first item of the tree-view control.
GN_ROOT 检索树视图的最高项或者是真正的第一项。
You can see that, you can retrieve the handle to the tree view item you are interested in from this message. SendMessage returns the handle to the tree view item if successful. You can then fill the returned handle into hItem member of TV_ITEM to be used with TVM_GETITEM message.
如你所见,你能从消息中得到你感兴趣的树视图选项句柄。如果成功,SendMessage函数返回这个树视图选项的句柄。你能把这个句柄传递给TV_ITEM结构体的成员hIetm,以便在TVM_GETITEM消息中使用它。
Drag and Drop Operation in tree view control
在树视图中进行拖放操作。
This part is the reason I decided to write this tutorial. When I tried to follow the example in win32 api reference (the win32.hlp from InPrise), I was very frustrated because the vital information is lacking. From trial and error, I finally figured out how to implement drag & drop in a tree view control and I don't want anyone to walk the same path as myself.
Below is the steps in implementing drag & drop operation in a tree view control.
这一部分是我决定写这一课的原因。当我尝试跟随在win32 api手册中的例子时(从InPrise获得win32.Hlp)我是非常失败的,因为至关重要的信息是缺乏的。
从试验和错误中,我最后终于解决了如何在树视图控件中进行拖放操作这一难题。我也不希望任何人再步我后尘。下面是在树视图中进行拖放操作的步骤:
1. When the user tries to drag an item, the tree view control sends TVN_BEGINDRAG notification to the parent window. You can use this opportunity to create a drag image which is the image that will be used to represent the item while it's being dragged. You can send TVM_CREATEDRAGIMAGE to the tree view control to tell it to create a default drag image from the image that is currently used by the item that will be dragged. The tree view control will create an image list with just one drag image and return the handle to that image list to you.
当用户尝试拖拉一个项目时,树视图发送TVN_BEGINDRAG消息通知它的父窗口。你能利用这个时机来创建一个拖拉的图像,当这个项目被拖拉时,这个图像将被用于代表这个项。你也可以发送TVM_CREATEDRAGIMAGE消息给树视图控件,用来告诉它,从那个被拖动的 选项 当前使用的 图像 中创建一个自定义的拖动图像。这个树视图将创建一个仅有一个拖动图像的图像列表,并将这个图像列表的句柄返回给你。
2. After the drag image is created, you specify the hotspot of the drag image by calling ImageList_BeginDrag.
在拖动图像被创建后,你通过调用ImageLIst_BeginDrag函数指定这个拖拉图像的热点。
ImageList_BeginDrag PROTO himlTrack:DWORD, \
iTrack:DWORD , \
dxHotspot:DWORD, \
dyHotspot:DWORD
himlTrack is the handle to the image list that contains the drag image.
HimlTrack是包含那个拖拉图像的图像列表句柄。
iTrack is the index into the image list that specifies the drag image
iTrack 是在图像列表中指定的那个拖拉图像的索引值。
dxHotspot specifies the relative distance of the hotspot in horizontal plance in the drag image since this image will be used in place of the mouse cursor, so we need to specify which part of the image is the hotspot.
DxHotspot 指定热点区域在水平面上的相对距离,因为这个图像将被用于鼠标光标的地方,所以我们指定这张图片的一部分作为热点区域。
dyHotspot specifies the relative distance of the hotspot in the vertical plane.
DyHotspot 指定热点在垂直面上的相对距离。
Normally, iTrack would be 0 if you tell the tree view control to create the drag image for you. and dxHotspot and dyHotspot can be 0 if you want the left upper corner of the drag image to be the hotspot.
通常,如果你通知树视图为你创建拖动图像,那么iTrick的值为0.此外,如果你希望热点是拖动图像的左上角,那么dxHotspot和dyHotspot的值为0.
3. When the drag image is ready to be displayed, we call ImageList_DragEnter to display the drag image in the window.
当拖拉图像准备好被显示时,我们调用Imagelist_DragEnter来将图像显示在窗口中。
ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD
hwndLock is the handle of the window that owns the drag image. The drag image will not be able to move outside that window.
HwndLock 是拥有拖拉图像的窗口句柄。拖拉图像不能被移到窗口外。
x and y are the x-and y-coordinate of the place where the drag image should be initially displayed. Note that these values are relative to the left upper corner of the window, not the client area.
X 和 y 是拖拉图像最初被显示的位置的x坐标和y坐标。这些值相对于窗口的左上角而不是客户区。
4. Now that the drag image is displayed on the window, you will have to support the drag operation in the tree view control. However, there is a little problem here. We have to monitor the drag path with WM_MOUSEMOVE and the drop location with WM_LBUTTONUP messages. However, if the drag image is over some other child windows, the parent window will never receive any mouse message. The solution is to capture the mouse input with SetCapture. Using the call, the mouse messages will be directed to the specified window regardless of where the mouse cursor is.
现在这个拖动图像被显示在窗口上,你将在树视图控件上进行拖拉操作。然而,这里有一个小小的问题。我们必须用WM_MOUSEMOVE消息来监视拖动的路径并且用WM_LBUTTONUP消息来监视图像落下的位置。可是,如果拖动图像时越过了某些其它子窗口,那么它的父窗口将接收不到任何鼠标消息。解决的办法是用SetCapture函数捕获鼠标输入。在使用这个函数调用时,鼠标消息被直接指定给窗口而不管鼠标光标在什么地方。
5. Within WM_MOUSEMOVE handler, you will update the drag path with ImageList_DragMove call. This function moves the image that is being dragged during a drag-and-drop operation. Furthermore, if you so desire, you can hilite the item that the drag image is over by sending TVM_HITTEST to check if the drag image is over some item. If it is, you can send TVM_SELECTITEM with TVGN_DROPHILITE flag to hilite that item. Note that before sending TVM_SELECTITEM message, you must hide the drag image first else your drag image will leave ugly traces. You can hide the drag image by calling ImageList_DragShowNolock and, after the hilite operation is finished, call ImageList_DragShowNolock again to show the drag image.
在WM_MOUSEMOVE消息处理程序中,调用ImageList_DragMove函数来更新拖拉路径。在图像被拖放的区间,这个函数移动这个图像。如果你非常希望你在拖动图像的时候那些它经过的项目呈高亮显示,你可以发送TVM_HITTEST消息来检查拖拉图像是否经过某些项。如果经过,你可以发送TVM_SELECTITEM消息并设置TVGN_DROPHILITE标志来让那些项目高亮显示(很醒目吧)。注意,在发送TVM_SELECTITEM之前,你必须先隐藏这个拖拉图像否则这个图像将留下非常丑陋的轨迹。你能通过调用ImageList_DragShowNolock函数来隐藏这个图像,然后在高亮操作完成后,在调用一次ImageList_DragShowNolock函数来显示这个图像。
6. When the user releases the left mouse button, you must do several things. If you hilite an item, you must un-hilite it by sending TVM_SELECTITEM with TVGN_DROPHILITE flag again, but this time, lParam MUST be zero. If you don't un-hilite the item, you will get a strange effect: when you select some other item, that item will be enclosed by a rectangle but the hilite will still be on the last hilited item. Next, you must call ImageList_DragLeave followed by ImageList_EndDrag. You must release the mouse by calling ReleaseCapture. If you create an image list, you must destroy it by calling ImageList_Destroy. After that, you can go on with what your program wants to do when the drag & drop operation is completed.
当用户释放左键时,你必须做几件事情,如果你高亮显示了某个选项,你必须在发送一次TVM_SELECTITEM消息并设置TVGN_DROPHILITE标志来使该项恢复正常,而且在这时,lParam参数必须为0。如果你不恢复高亮显示的项,你将得到一个奇怪的结果:当你选择其它项时,那个项将被一个长方形包住,而高亮显示的项目却一直都很醒目。接下来,你必须在函数ImageLIst_EndDrag调用之后接着调用ImageList_DragLeave函数。你必须调用ReleaseCapture函数来释放鼠标。如果你创建了一个图像列表,你必须调用ImageList_Destroy销毁它。当拖放操作完成后,你能让你程序继续任何其它你想做的事情。
Code sample:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDB_TREE equ 4006 ; ID of the bitmap resource
.data
ClassName db "TreeViewWinClass",0
AppName db "Tree View Demo",0
TreeViewClass db "SysTreeView32",0
Parent db "Parent Item",0
Child1 db "child1",0
Child2 db "child2",0
DragMode dd FALSE ; a flag to determine if we are in drag mode
.data?
hInstance HINSTANCE ?
hwndTreeView dd ? ; handle of the tree view control
hParent dd ? ; handle of the root tree view item
hImageList dd ? ; handle of the image list used in the tree view control
hDragImageList dd ? ; handle of the image list used to store the drag image
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,200,400,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL tvinsert:TV_INSERTSTRUCT
LOCAL hBitmap:DWORD
LOCAL tvhit:TV_HITTESTINFO
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
0,200,400,hWnd,NULL,\
hInstance,NULL ; Create the tree view control
mov hwndTreeView,eax
invoke ImageList_Create,16,16,ILC_COLOR16,2,10 ; create the associated image list
mov hImageList,eax
invoke LoadBitmap,hInstance,IDB_TREE ; load the bitmap from the resource
mov hBitmap,eax
invoke ImageList_Add,hImageList,hBitmap,NULL ; Add the bitmap into the image list
invoke DeleteObject,hBitmap ; always delete the bitmap resource
invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
mov tvinsert.hParent,NULL
mov tvinsert.hInsertAfter,TVI_ROOT
mov tvinsert.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
mov tvinsert.item.pszText,offset Parent
mov tvinsert.item.iImage,0
mov tvinsert.item.iSelectedImage,1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov hParent,eax
mov tvinsert.hParent,eax
mov tvinsert.hInsertAfter,TVI_LAST
mov tvinsert.item.pszText,offset Child1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov tvinsert.item.pszText,offset Child2
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
.elseif uMsg==WM_MOUSEMOVE
.if DragMode==TRUE
mov eax,lParam
and eax,0ffffh
mov ecx,lParam
shr ecx,16
mov tvhit.pt.x,eax
mov tvhit.pt.y,ecx
invoke ImageList_DragMove,eax,ecx
invoke ImageList_DragShowNolock,FALSE
invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
.if eax!=NULL
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
.endif
invoke ImageList_DragShowNolock,TRUE
.endif
.elseif uMsg==WM_LBUTTONUP
.if DragMode==TRUE
invoke ImageList_DragLeave,hwndTreeView
invoke ImageList_EndDrag
invoke ImageList_Destroy,hDragImageList
invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
invoke ReleaseCapture
mov DragMode,FALSE
.endif
.elseif uMsg==WM_NOTIFY
mov edi,lParam
assume edi:ptr NM_TREEVIEW
.if [edi].hdr.code==TVN_BEGINDRAG
invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
mov hDragImageList,eax
invoke ImageList_BeginDrag,hDragImageList,0,0,0
invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
invoke SetCapture,hWnd
mov DragMode,TRUE
.endif
assume edi:nothing
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start
Analysis:
分析:
Within WM_CREATE handler, you create the tree view control
在WM_CREATE消息的处理程序中,你创建一个树视图控件。
invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
0,200,400,hWnd,NULL,\
hInstance,NULL
Note the styles. TVS_xxxx are the tree view specific styles.
注意样式,TVS_XXXX是树视图特有的样式。
invoke ImageList_Create,16,16,ILC_COLOR16,2,10
mov hImageList,eax
invoke LoadBitmap,hInstance,IDB_TREE
mov hBitmap,eax
invoke ImageList_Add,hImageList,hBitmap,NULL
invoke DeleteObject,hBitmap
invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
Next, you create an empty image list with will accept images of 16x16 pixels in size, 16-bit color and initially, it will contain 2 images but can be expanded to 10 if need arises. We then load the bitmap from the resource and add it to the image list just created. After that, we delete the handle to the bitmap since it will not be used anymore. When the image list is all set, we associate it with the tree view control by sending TVM_SETIMAGELIST to the tree view control.
接下来,你创建一个空的图像列表,并且初始化接收两张16x16像素大小,16位颜色的图像,但是,如果你需要你能扩张到10张。(一个图像列表接收的图像属性必须相同)然后我们从资源中加载一张位图并且将它添加进刚刚创建好的图像列表中。在这之后,我们删除了位图的句柄,因为它再也不会被使用。当图像列表的所有设置都完成后,我们发送TVM_SETIMAGELIST消息给树视图控件,使得它和图像列表相互关联。
mov tvinsert.hParent,NULL
mov tvinsert.hInsertAfter,TVI_ROOT
mov tvinsert.u.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
mov tvinsert.u.item.pszText,offset Parent
mov tvinsert.u.item.iImage,0
mov tvinsert.u.item.iSelectedImage,1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
We insert items into the tree view control, beginning from the root item. Since it will be root item, hParent member is NULL and hInsertAfter is TVI_ROOT. imask member specifies that pszText, iImage and iSelectedImage members of the TV_ITEM structure is valid. We fill those three members with appropriate value. pszText contains the label of the root item, iImage is the index into the image in the image list that will be displayed to the left of the unselected item, and iSelectedImage is the index into the image in the image list that will be displayed when the item is selected. When all appropriate members are filled in, we send TVM_INSERTITEM message to the tree view control to add the root item to it.
我们插入项目到树视图控件中,从根项目开始。因为它是根选项,所以hParant成员是NULL并且hInsertAfter是TVI_ROOT。Imask成员指定那个pszText,TV_ITEM结构体的iImage 和IselectedIma成员是有效的。我们用适当的值填充这三个成员,pszText成员包含根选项的标签,iImage成员是图像列表中图像的索引号,这个图像将显示在未选定的项目名称的左边,而IselectedImage是选项被选定时,在图像列表中被显示的图像的索引号。当所有合适的成员都填充好时,我们发送TVM_INSERTTEM消息给树视图控件来为它添加一个根选项。
mov hParent,eax
mov tvinsert.hParent,eax
mov tvinsert.hInsertAfter,TVI_LAST
mov tvinsert.u.item.pszText,offset Child1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov tvinsert.u.item.pszText,offset Child2
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
After the root item is added, we can attach the child items to it. hParent member is now filled with the handle of the parent item. And we will use identical images in the image list so we don't change iImage and iSelectedImage member.
当根选项被添加后,我们能为它加上子选项。hParent成员现在用父选项的句柄填充。(hInsertAfter的值是TVI_LAST)并且我们将使用图像列表中的同一图像,所以我们并不改变iImage和iSelectedImage成员。
.elseif uMsg==WM_NOTIFY
mov edi,lParam
assume edi:ptr NM_TREEVIEW
.if [edi].hdr.code==TVN_BEGINDRAG
invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
mov hDragImageList,eax
invoke ImageList_BeginDrag,hDragImageList,0,0,0
invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
invoke SetCapture,hWnd
mov DragMode,TRUE
.endif
assume edi:nothing
Now when the user tries to drag an item, the tree view control sends WM_NOTIFY message with TVN_BEGINDRAG as the code. lParam is the pointer to an NM_TREEVIEW structure which contains several pieces of information we need so we put its value into edi and use edi as the pointer to NM_TREEVIEW structure. assume edi:ptr NM_TREEVIEW is a way to tell MASM to treat edi as the pointer to NM_TREEVIEW structure. We then create a drag image by sending TVM_CREATEDRAGIMAGE to the tree view control. It returns the handle to the newly created image list with a drag image inside. We call ImageList_BeginDrag to set the hotspot in the drag image. Then we enter the drag operation by calling ImageList_DragEnter. This function displays the drag image at the specified location in the specified window. We use ptDrag structure that is a member of NM_TREEVIEW structure as the point where the drag image should be initially displayed.After that, we capture the mouse input and set the flag to indicate that we now enter drag mode.
.elseif uMsg==WM_MOUSEMOVE
.if DragMode==TRUE
mov eax,lParam
and eax,0ffffh
mov ecx,lParam
shr ecx,16
mov tvhit.pt.x,eax
mov tvhit.pt.y,ecx
invoke ImageList_DragMove,eax,ecx
invoke ImageList_DragShowNolock,FALSE
invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
.if eax!=NULL
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
.endif
invoke ImageList_DragShowNolock,TRUE
.endif
Now we concentrate on WM_MOUSEMOVE. When the user drags the drag image along, our parent window receives WM_MOUSEMOVE messages. In response to these messages, we update the drag image position with ImageList_DragMove. After that, we check if the drag image is over some item. We do that by sending TVM_HITTEST message to the tree view control with a point for it to check. If the drag image is over some item, we hilite that item by sending TVM_SELECTITEM message with TVGN_DROPHILITE flag to the tree view control. During the hilite operation, we hide the drag image so that it will not leave unsightly blots on the tree view control.
现在,我们将精力集中在WM_MOUSEMOVE消息上。当用户向前拖动拖拉图像时,我们的父窗口接收到WM_MOUSEMOVE消息。在响应这个消息的程序代码中,我们用ImageList_DragMove来更新拖拉图像的位置。在这之后,我们检查拖拉图像是否经过某些选项。我们通过发送TVM_HITTEST消息给树视图来做这个工作。如果拖拉图像时经过某些选项,我们就发送TVM_SELECTITEM并设置TVGH_DROPHILITE给树视图控件来让这些选项高亮显示。在高亮显示的操作期间,我们隐藏了拖动图像以至于它不留下不好看的污点在树视图控件上。
.elseif uMsg==WM_LBUTTONUP
.if DragMode==TRUE
invoke ImageList_DragLeave,hwndTreeView
invoke ImageList_EndDrag
invoke ImageList_Destroy,hDragImageList
invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
invoke ReleaseCapture
mov DragMode,FALSE
.endif
When the user releases the left mouse button, the drag operation is at the end. We leave the drag mode by calling ImageList_DragLeave, followed by ImageList_EndDrag and ImageList_Destroy. To make the tree view items look good, we also check the last hilited item, and select it. We must also un-hilite it else the other items will not get hilited when they are selected. And lastly, we release the mouse capture.
当用户释放鼠标左键时,拖动操作就被终止。我们通过调用ImageList_DragLeave, ImageList_EndDrag和ImageList_Destroy函数来离开拖动模式。为了让树视图好看一些,我们最后也检查醒目项(高亮显示的),并且选定它们。然后让它们从高亮显示恢复到正常状态,否则当其它的项被选择时将不能够被高亮显示。在最后,我们释放鼠标捕获。
________________________________________
This article come from Iczelion's asm page,
风向改变翻译于2008-1-9日
Tutorial 20: Window Subclassing
第二十课:窗口子类化
________________________________________
In this tutorial, we will learn about window subclassing, what it is and how to use it to your advantage.
在这一课中,我们将学习什么是窗口子类化,以及如何使用它才能对你有利。
Download the example here.
Theory:
原理:
If you program in Windows for some time, you will find some cases where a window has nearly the attributes you need in your program but not quite. Have you encountered a situation where you want some special kind of edit control that can filter out some unwanted text? The straightforward thing to do is to code your own window. But it's really hard work and time-consuming. Window subclassing to the rescue.
In a nutshell, window subclassing allows you to "take over" the subclassed window. You will have absolute control over it. Let's take an example to make this clearer. Suppose you need a text box that accepts only hex numbers. If you use a simple edit control, you have no say whatsoever when your user types something other than hex numbers into your text box, ie. if the user types "zb+q*" into your text box, you can't do anything with it except rejecting the whole text string. This is unprofessional at least. In essence, you need the ability to examine each character the user typed into the text box right at the moment he typed it.
We will examine how to do that now. When the user types something into a text box, Windows sends WM_CHAR message to the edit control's window procedure. This window procedure resides inside Windows itself so we can't modify it. But we can redirect the message flow to our own window procedure. So that our window procedure will get first shot at any message Windows sends to the edit control. If our window procedure chooses to act on the message, it can do so. But if it doesn't want to handle the message, it can pass it to the original window procedure. This way, our window procedure inserts itself between Windows and the edit control. Look at the flow below:
现在,我们将要分析如何做这些工作。当用户输入一些东西在一个文本框的时候,windows发送WM_CHAR消息给编辑控件的窗口过程。这个窗口过程驻留在windows内部所以我们不能修改它。但是我们能重定向这条消息让它流向我们自己的窗口过程。这样我们的窗口过程将首先获得windows发给编辑控件的任何消息。如果我们的窗口过程愿意对这条消息起作用,它就能这样做。但是如果它不想处理这条消息,它能把它传给原来的窗口过程。这样,我们窗口过程将它自己插入在windows和edit 控件之间。看起来像下面这样:
Before Subclassing
在窗口子类化之前
Windows ==> edit control's window procedure
Windows ==>Edit 控件的窗口处理函数。
After Subclassing
在子类化之后
Windows ==> our window procedure -----> edit control's window procedure
Windows ==>自定义的窗口处理函数==> Edit 控件的窗口处理函数
Now we put our attention on how to subclass a window. Note that subclassing is not limited to controls, it can be used with any window.
现在我们把注意力放在如何子类化一个窗口上。注意,子类化并不是仅限制于控件,它能被用于任何窗口。
Let's think about how Windows knows where the edit control's window procedure resides. A guess?......lpfnWndProc member of WNDCLASSEX structure. If we can replace this member with the address of our own window procedure, Windows will send messages to our window proc instead.
We can do that by calling SetWindowLong.
SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd = handle of the window to change the value in the WNDCLASSEX structure
窗口的句柄,将改变后的值放在WNDCLASSEX结构中。
nIndex == value to change.
改变的值。
GWL_EXSTYLE Sets a new extended window style.
设置一个新的扩张窗口样式。
GWL_STYLE Sets a new window style.
设置一个新的窗口样式
GWL_WNDPROC Sets a new address for the window procedure.
为窗口过程设置一个新的地址
GWL_HINSTANCE Sets a new application instance handle.
设置一个新的应用程序实例句柄
GWL_ID Sets a new identifier of the window.
设置一个新的窗口标识符
GWL_USERDATA Sets the 32-bit value associated with the window. Each window has a corresponding 32-bit value intended for use by the application that created the window.
设置一个和窗口相关联的32位值。每一个窗口都有一个32位的值当算在应用程序创建窗口的时候使用。
dwNewLong = the replacement value. 替换值
So our job is easy: We code a window proc that will handle the messages for the edit control and then call SetWindowLong with GWL_WNDPROC flag, passing along the address of our window proc as the third parameter. If the function succeeds, the return value is the previous value of the specified 32-bit integer, in our case, the address of the original window procedure. We need to store this value for use within our window procedure.
Remember that there will be some messages we don't want to handle, we will pass them to the original window procedure. We can do that by calling CallWindowProc function.
CallWindowProc PROTO lpPrevWndFunc:DWORD, \
hWnd:DWORD,\
Msg:DWORD,\
wParam:DWORD,\
lParam:DWORD
lpPrevWndFunc = the address of the original window procedure.
原来的窗口过程地址
The remaining four parameters are the ones passed to our window procedure. We just pass them along to CallWindowProc.
剩下的四个参数是传递给我们的窗口过程的,我们直接把它们传递给CallWindowProc函数就可。
Code Sample:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "SubclassWinClass",0
AppName db "Subclassing Demo",0
EditClass db "EDIT",0
Message db "You pressed Enter in the text box!",0
.data?
hInstance HINSTANCE ?
hwndEdit dd ?
OldWndProc dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
20,300,25,hWnd,NULL,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,eax
;-----------------------------------------
; Subclass it!
;-----------------------------------------
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start
Analysis:
分析:
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
After the edit control is created, we subclass it by calling SetWindowLong, replacing the address of the original window procedure with our own window procedure. Note that we store the address of the original window procedure for use with CallWindowProc. Note the EditWndProc is an ordinary window procedure.
在编辑控件被创建后,我们通过调用SetWindowLong函数让它子类化,用我们自己窗口过程地址替换原来的窗口过程地址。注意,我们必须储存原来的窗口过程地址以便以后调用CallWindowProc函数。注意:EditWndProc是一个普通的窗口过程。
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
Within EditWndProc, we filter WM_CHAR messages. If the character is between 0-9 or a-f, we accept it by passing along the message to the original window procedure. If it is a lower case character, we convert it to upper case by adding it with 20h. Note that, if the character is not the one we expect, we discard it. We don't pass it to the original window proc. So when the user types something other than 0-9 or a-f, the character just doesn't appear in the edit control.
在EditWndProc中,我们筛选WM_CHAR消息。如果字符在0-9或者是a-f之间,我们通过把消息传递给它原来的窗口过程来接受它。如果它们是小写字母,我们让它们都加上20h,来将它们全部转换为大写。注意,如果字符不是我们期望的,我们抛弃它。我们并不传递它给原来的窗口过程。所以当用户输入一些不在0-9或者是a-f的字符时,这些字符将不显示在编辑控件上。
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.end
I want to demonstrate the power of subclassing further by trapping Enter key. EditWndProc checks WM_KEYDOWN message if it's VK_RETURN (the Enter key). If it is, it displays a message box saying "You pressed the Enter key in the text box!". If it's not an Enter key, it passes the message to the original window procedure.
通过陷入回车键,我想进一步的示范窗口子类化的能力。EditWndProc检查WM_KEYDOWN消息看它是否是VK_RETURN(回车键)。如果它是,它将弹出一个对话框并显示文本“you pressed the enter key in the box!”如果它不是回车键,它将这个消息传递给原来的窗口程序。
You can use window subclassing to take control over other windows. It's one of the powerful techniques you should have in your arsenal.
你能用窗口子类化来控制其它的窗口。它是种有力的技术,你应该将它存入你的兵工厂。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-1-10
Tutorial 21: Pipe
第二十一课:管道
________________________________________
In this tutorial, we will explore pipe, what it is and what we can use it for. To make it more interesting, I throw in the technique on how to change the background and text color of an edit control.
在这一课中,我们将探究一下管道,它是什么和我们能用它来做什么。为了让它更加有趣,我将此技术放在如何改变一个编辑控件的背景和文本颜色上。
Download the example here.
Theory:
原理:
Pipe is a communication conduit or pathway with two ends. You can use pipe to exchange the data between two different processes, or within the same process. It's like a walkie-talkie. You give the other party one set and he can use it to communicate with you.
管道是一通讯管道或者是有两端的通路。你能用管道在两个不同的进程或同一个进程之间交换数据。它看起来像步话机。你给对方一个装置然后他就能用它来和你进行通讯。
There are two types of pipes: anonymous and named pipes. Anonymous pipe is, well, anonymous: that is, you can use it without knowing its name. A named pipe is the opposite: you have to know its name before you can use it.
这里有两种类型的管道:有名管道和匿名管道。匿名管道的意思是:你使用它的时候不用知道它的名字。有名管道正好相反:你必须知道它的名字才能使用它。
You can also categorize pipes according to its property: one-way or two-way. In a one-way pipe, the data can flow only in one direction: from one end to the other. While in a two-way pipe, the data can be exchanged between both ends.
你也能依据管道的特性来对它加以分类:单向的或双向的。在一个单向管道中,数据只能在一个方向流动:即从一端流向另一端。而在双向管道中,数据能在管道的两端之间进行交换。
An anonymous pipe is always one-way while a named pipe can be one-way or two-way. A named pipe is usually used in a network environment where a server can connect to several clients.
一个匿名管道总是单向的,而一个已命名的管道则是单向或双向中的一种。有名管道通常用于一个服务器连接多个客户端的网络环境中。
In this tutorial, we will examine anonymous pipe in some detail. Anonymous pipe's main purpose is to be used as a communication pathway between a parent and child processes or between child processes.
在这一课中,我们将详细地讨论匿名管道。匿名管道的主要用途是作为父进程和子进程之间或者是子进程之间的通讯通路。
Anonymous pipe is really useful when you deal with a console application. A console application is a kind of win32 program which uses a console for its input & output. A console is like a DOS box. However, a console application is a fully 32-bit program. It can use any GUI function, the same as other GUI programs. It just happens to have a console for its use.
当你涉及到控制台程序时,命名管道是很有用的。控制台应用程序是Win32程序的一种,用于控制它们的输入和输出。一个控制台就像一个DOS窗口。然而,一个控制台应用程序又的的确确是32位的应用程序。它和其它的图形用户界面程序一样,可以使用任何GUI函数。它仅是偶然的使用一下控制台罢了。
A console application has three handles it can use for its input & output. They are called standard handles. There are three of them: standard input, standard output and standard error. Standard input handle is used to read/retrieve the information from the console and standard output handle is used to output/print the information to the console. Standard error handle is used to report error condition since its output cannot be redirected.
一个控制台应用程序有三个句柄,它能用作它的输入和输出。它们是被调用的标准句柄。这三个句柄是:标准输入,标准输出和标准错误句柄。标准输入句柄被用于从一个控制台中读或者是获取信息;标准输出句柄被用于输出或打印控制台信息。标准错误句柄用于报告错误状态,因为控制台的输出可能被重定向。
A console application can retrieve those three standard handles by calling GetStdHandle function, specifying the handle it wants to obtain. A GUI application doesn't have a console. If you call GetStdHandle, it will return error. If you really want to use a console, you can call AllocConsole to allocate a new console. However, don't forget to call FreeConsole when you're done with the console.
一个控制台应用程序能通过调用GetStdHandle函数来获得这三个标准句柄,只需要指定你想要的句柄就能够得到它。一个图像用户界面程序没有控制台。如果你调用GetStHandle函数,它将返回错误代码。如果你真的想使用控制台,你可以调用AllocConsole来分配一个新个控制台。然后,当你完成控制台的工作时,不要忘记调用FreeConsole函数释放它。
Anonymous pipe is most frequently used to redirect input and/or output of a child console application. The parent process may be a console or a GUI application but the child must be a console app. for this to work. As you know, a console application uses standard handles for its input and output. If we want to redirect the input and/or output of a console application, we can replace the handle with a handle to one end of a pipe. A console application will not know that it's using a handle to one end of a pipe. It'll use it as a standard handle. This is a kind of polymorphism, in OOP jargon. This approach is powerful since we need not modify the child process in anyway.
匿名管道被频繁的用于重定向 子控制台应用程序的输入和输出。父进程可能是一个控制台或者是一个GUI应用程序,但是子进程必须是一个控制台应用程序。为了这个工作,像你所知道的一样,一个控制台应用程序为它的输入和输出使用标准句柄。如果你想重定向一个控制台应用程序的输入和(或)输出,我们必须用一个管道一端的句柄来替换这个句柄,而控制台应用程序并不知道它使用的句柄是一个管道的一端。它仍将把它当作一个标准句柄使用。在面向对象的行话中,这属于多态性的一种。这是一种很有效的方法,因为我们并不需要用任何途径来修改子进程。
Another thing you should know about a console application is where it gets those standard handles from. When a console application is created, the parent process has two choices: it can create a new console for the child or it can let the child inherit its own console. For the second approach to work, the parent process must be a console application or if it's a GUI application, it must call AllocConsole first to allocate a console.
其它你还应该知道的事,就是一个控制台程序从哪儿获取这些标准句柄。当一个控制台应用程序被创建时,父进程有两个选择:为子进程创建一个新的控制台 或者让子进程继承它已有的控制台。如果选用的是第二种方法,父进程必须是一个控制台应用程序,如果它是一个图形用户界面程序,那么它必须首先调用AllocConsole函数来分配一个控制台。
Let's begin the work. In order to create an anonymous pipe you need to call CreatePipe. CreatePipe has the following prototype:
让我们开始工作吧!为了创建一个匿名管道,你需要调用CreatePipe函数,CreatePipe句法如下:
CreatePipe proto pReadHandle:DWORD, \
pWriteHandle:DWORD,\
pPipeAttributes:DWORD,\
nBufferSize:DWORD
• pReadHandle is a pointer to a dword variable that will receive the handle to the read end of the pipe
pReadHandle是一指向DWORD类型变量的指针,这个变量接收管道读的那端的句柄。
• pWriteHandle is a pointer to a dword variable that will receive the handle to the write end of the pipe.
pWriteHandle 是一指向DWORD类型变量的指针,这个变量接收管道写的那端的句柄。
• pPipeAttributes points to a SECURITY_ATTRIBUTES structure that determines whether the returned read & write handles are inheritable by child processes
pPipeAttributes 指向一个SECURITY_ATTRIBUTES结构体的指针,这个结构用于确定读写句柄是否能被子进程继承。
• nBufferSize is the suggested size of the buffer the pipe will reserve for use. This is a suggested size only. You can use NULL to tell the function to use the default size.
nBufferSize 建议为用户保留的缓冲区大小。这仅仅是一个建议的大小。你能使用NULL来告诉函数使用默认的大小。
If the call is successful, the return value is nonzero. If it failed, the return value is zero.
如果这个调用是成功的,返回值非零。如果失败,返回值为零(0)。
After the call is successful, you will get two handles, one to read end of the pipe and the other to the write end. Now I will outline the steps needed for redirecting the standard output of a child console program to your own process.Note that my method differs from the one in Borland's win32 api reference. The method in win32 api reference assumes the parent process is a console application and thus the child can inherit the standard handles from it. But most of the time, we will need to redirect output from a console application to a GUI one.
在这个调用成功之后,你将得到两个句柄,一个是管道读端的句柄,另一个是写端的句柄。现在我将重点放在重定向子控制台程序的标准输出到你拥有的进程所需要的步骤上。注意,我使用的方法和在Borland的win32 api 手册中的方法有所不同。在win32 api手册中的这种方法如下:父进程是一个控制台程序从而子进程能从它那继承这些标准句柄。但是大所数时候,我们将需要从一个控制台程序中重定向输出到一个图形用户界面程序。
1. Create an anonymous pipe with CreatePipe. Don't forget to set the bInheritable member of SECURITY_ATTRIBUTES to TRUE so the handles are inheritable.
用CreatePipe函数创建一个匿名管道,不要忘记设置SECUTITY_ATTRIBUTES结构的bInheritable成员的值为TRUE,如此,这些句柄才能被继承。
2. Now we must prepare the parameters we will pass to CreateProcess since we will use it to load the child console application. One important structure is the STARTUPINFO structure. This structure determines the appearance of the main window of the child process when it first appears. This structure is vital to our purpose. You can hide the main window and pass the pipe handle to the child console process with it. Below is the members you must fill:
现在我们必须准备一下将传递给CreateProcess函数的参数了,因为我们将使用它加载一个子控制台应用程序。一个重要的结构是STARTUPINF结构体。这个结构确定当子进程第一次出现时主窗口的外部特征。这个结构对我们的目的来说是必不可少的。你能用它隐藏主窗口并把管道句柄传递给子控制台进程。下面这些成员是你必须填写的 :
o cb : the size of STARTUPINFO structure
cb:STARTUPINFO结构体的大小。
o dwFlags : the binary bit flags that determine which members of the structure are valid also it governs the show/hide state of the main window. For our purpose, you should use a combination of STARTF_USESHOWWINDOW and STARTF_USESTDHANDLES
dwFlags:二进制位标志字。它确定了结构的那一个成员是有效的 同时还支配着主窗口的显示或隐藏状态。为了我们的目标你应该用STARTF_USESHOWWINDOW和STARTF_USESTDHANDLES的组合值。
o hStdOutput and hStdError : the handles you want the child process to use as standard output/error handles. For our purpose, we will pass write handle of the pipe as the standard output and error of the child. So when the child outputs something to the standard output/error, it actually passes the info via the pipe to the parent process.
hStdOutput 和hSedError: 这是你想在子进程中使用的标准输出和标准错误的句柄。为了达到我们的目标,我们将传递管道写端的句柄作为子进程的标准输出和标准错误的句柄。所以当子进程输出一些东西到标准输出和错误时,它实际上是通过管道把这些信息发送给父进程。
o wShowWindow governs the show/hide state of the main window. For our purpose, we don't want the console window of the child to show so we put SW_HIDE into this member.
WshowWindow 管理主窗口的显示和隐藏状态。为达到我们的目标,我们并不想子进程的控制窗口显示,所以我们把SW_HIDE放进这个成员中。
3. Call CreateProcess to load the child application. After CreateProcess is successful, the child is still dormant. It is loaded into memory but it doesn't run immediately
调用CreateProcess来加载子应用程序。在CreateProcess调用成功后,子进程处于睡眠状态,它被装进内存但是它不立即运行。
4. Close the write pipe handle. This is necessary. Because the parent process has no use for the write pipe handle, and the pipe won't work if there are more than one write end, we MUST close it before reading the data from the pipe. However, don't close the write handle before calling CreateProcess, your pipe will be broken. You should close it just after CreateProcess returns and before you read data from the read end of the pipe.
关闭些管道句柄。这一步是必须的。因为父进程并不需要写管道句柄,如果这里有超过一个写端,这个管道将不工作,为了从一个管道中读进数据我们必须关闭它。然而,如果在调用CreateProcess之前,不关闭写句柄,你们的管道将是破裂的。你应该在CreateProcess刚刚返回并且在你从一个管道的一个读端读数据之前,关闭管道写端。
5. Now you can read data from the read end of the pipe with ReadFile. With ReadFile, you kick the child process into running mode. It will start execution and when it writes something to the standard output handle (which is actually the handle to the write end of the pipe), the data are sent through the pipe to the read end. You can think of ReadFile as sucking data from the read end of the pipe. You must call ReadFile repeatedly until it returns 0 which means there are no more data to be read. You can do anything with the data you read from the pipe. In our example, I put them into an edit control.
现在你可以用ReadFile函数从管道的读端读取数据。通过使用ReadFile函数,你能把子进程踢进运行模式。它将开始执行并且当它写一东西到标准输出句柄(实际上是管道的写端句柄)时,这些数据通过管道被送到读端。你可以把ReadFile函数看作是从管道的读端上吸食数据。你应该重复的调用ReadFile函数直到它的返回值为0. 返回0意味着这里没有更多数据能被读取了。你能对从管道读来的数据做任何处理。在我们的例子中,我把它们放在编辑控件中。
6. Close the read pipe handle.
关闭读管道句柄。
Example:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDR_MAINMENU equ 101 ; the ID of the main menu
IDM_ASSEMBLE equ 40001
.data
ClassName db "PipeWinClass",0
AppName db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError db "Error during pipe creation",0
CreateProcessError db "Error during process creation",0
CommandLine db "ml /c /coff /Cp test.asm",0
.data?
hInstance HINSTANCE ?
hwndEdit dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect:RECT
LOCAL hRead:DWORD
LOCAL hWrite:DWORD
LOCAL startupinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
LOCAL buffer[1024]:byte
LOCAL bytesRead:DWORD
LOCAL hdc:DWORD
LOCAL sat:SECURITY_ATTRIBUTES
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
mov hwndEdit,eax
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetBkColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
.elseif uMsg==WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
.if eax==NULL
invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
.else
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
.if eax==NULL
invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK
.else
invoke CloseHandle,hWrite
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
.endif
invoke CloseHandle,hRead
.endif
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
.endif
xor eax,eax
ret
WndProc endp
end start
Analysis:
分析:
The example will call ml.exe to assemble a file named test.asm and redirect the output of ml.exe to the edit control in its client area.
When the program is loaded, it registers the window class and creates the main window as usual. The first thing it does during main window creation is to create an edit control which will be used to display the output of ml.exe.
这个例子将调用Ml.EXE来汇编一个名字为test.asm的文件并重定向ml.exe的输出到编辑框的客户区中。
当程序被装载时,它照常注册一个窗口类,并创建一个主窗口。它要做的第一件事就是在主窗口创建的时候创建一个编辑控件。这个编辑控件被用于显示ml.exe的输出。
Now the interesting part, we will change the text and background color of the edit control. When an edit control is going to paint its client area, it sends WM_CTLCOLOREDIT message to its parent.
wParam contains the handle to the device context that the edit control will use to write its own client area. We can use this opportunity to modify the characteristics of the HDC.
现在来到我们感兴趣的部分,我们将改变编辑控件的背景色和文本颜色。当编辑控件将要绘制它的客户区时,它发送WM_CTLCOLOREDIT消息给它的父亲。
wParam包含的是设备描述表的句柄,设备描述表将被编辑控件用来写它的客户区。我们能利用这个时机来修改HDC(设备描述表也称设备环境)的特征属性值。
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetTextColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
SetTextColor changes the text color to yellow. SetTextColor changes the background color of the text to black. And lastly, we obtain the handle to the black brush which we return to Windows. With WM_CTLCOLOREDIT message, you must return a handle to a brush which Windows will use to paint the background of the edit control. In our example, I want the background to be black so I return the handle to the black brush to Windows.
Now when the user selects Assemble menuitem, it creates an anonymous pipe.
SetTextColor 改变文本颜色为黄色。SetTextColor改变文本的背景色为黑色。最后,我们获得了黑色画刷的句柄并将它返回给Windows。在处理WM_CTLCOLOREDIT消息时,你必须返回一个画刷的句柄。Windows将用这个画刷来绘制编辑控件的背景色。在我们的例子中,我想让背景为黑色所以我返回一个黑色的画刷给Windows。现在,当用户选择Assemble菜单项时,它将创建一个匿名管道。
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
Prior to calling CreatePipe, we must fill the SECURITY_ATTRIBUTES structure first. Note that we can use NULL in lpSecurityDescriptor member if we don't care about security. And the bInheritHandle member must be TRUE so that the pipe handles are inheritable to the child process.
在调用CreatePipe之前,我们必须首先填充SECURITY_ATTRIBUTES结构。如果我们不关心安全性描述我们能设置ipSecurityDescriptor成员的值为NULL。还有,我们的bInheritHandle成员必须为TRUE以至于让我们的管道句柄能被子进程继承。
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
After that, we call CreatePipe which, if successful, will fill hRead and hWrite variables with the handles to read and write ends of the pipe respectively.
在上面工作完成之后,我们调用CreatePipe函数,如果这个函数调用成功,hRead和hWrite变量将被分别填充到管道的读端和写端的句柄中。
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
Next we must fill the STARTUPINFO structure. We call GetStartupInfo to fill the STARTUPINFO structure with default values of the parent process. You MUST fill the STARTUPINFO structure with this call if you intend your code to work under both win9x and NT. After GetStartupInfo call returns, you can modify the members that are important. We copy the handle to the write end of the pipe into hStdOutput and hStdError since we want the child process to use it instead of the default standard output/error handles. We also want to hide the console window of the child process, so we put SW_HIDE value into wShowWidow member. And lastly, we must indicate that hStdOutput, hStdError and wShowWindow members are valid and must be used by specifying the flags STARTF_USESHOWWINDOW and STARTF_USESTDHANDLES in dwFlags member.
下面我们将填写STARTUPINFO结构。我们调用GetStartupInfo函数来用父进程的默认值填充STARTUPINFO结构。如果你打算让你的代码在Win9x和NT中都能工作,那么你必须用这个调用来填充这个STARTUPINFO结构。在GetStartupInfo调用返回后,你能修改那些重要的成员。我们复制管道的写端句柄给hStdOutput和hStdError因为我们想让子进程使用它来代替默认的标准输出/错误句柄。我们还需要隐藏子进程的控制台窗口,所以我们设置wShowWIndow成员的值为SW_HIDE。在最后,我们必须指出hStdOutput ,hStdError和wShowWindow成员是有效的并且必须把被指定的标志STARTF_USESHOWWINDOW和STARTF_USESTDHANDLES的组合值填进dwFlags成员中。
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
We now create the child process with CreateProcess call. Note that the bInheritHandles parameter must be set to TRUE for the pipe handle to work.
现在,我们调用CreateProcess函数创建了子进程。注意,为了能让管道句柄能工作,我们必须把bInheritHandles参数值设置为TRUE。
invoke CloseHandle,hWrite
After we successfully create the child process, we must close the write end of the pipe. Remember that we passed the write handle to the child process via STARTUPINFO structure. If we don't close the write handle from our end, there will be two write ends. And that the pipe will not work. We must close the write handle after CreateProcess but before we read data from the read end of the pipe.
在成功创建了子进程之后,我们必须关闭管道的写端。记住,我们通过STARTUPINFO结构来传递写端句柄给子进程。如果我们不关闭管道一端的写句柄,这个管道将有两个写端。这样,管道就不能工作了。我们必须在CreateProcess调用后但是要在从管道的读端读数据之前关闭写端句柄。
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
Now we are ready to read the data from the standard output of the child process. We will stay in an infinite loop until there are no more data left to read from the pipe. We call RtlZeroMemory to fill the buffer with zeroes then call ReadFile, passing the read handle of the pipe in place of a file handle. Note that we only read a maximum of 1023 bytes since we need the data to be an ASCIIZ string which we can pass on to the edit control.
现在,我们准备从子进程的标准输出中读数据。 我们将驻留在一个死循环中,直到从管道中再也没有数据读出为止才离开。我们调用RtlZeroMemory函数用零填充缓冲区然后传递管道的读端句柄给ReadFile函数的文件句柄并调用它。注意,我们能读的最大字节数为1023字节,因为我们需要把数据转换成能传递给编辑控件的ASCIIZ字符串。
When ReadFile returns with the data in the buffer, we fill the data into the edit control. However, there is a slight problem here. If we use SetWindowText to put the data into the edit control, the new data will overwrite existing data! We want the data to append to the end of the existing data.
当ReadFile把数据置于缓冲区中并返回时,我们把数据填进编辑控件中。然而,这里有一个小问题。如果我们用SetWindowText函数来把数据放入编辑控件中,新的数据将覆盖已经存在的数据!我们希望数据能给补充在存在数据的后面。
To achieve that goal, we first move the caret to the end of the text in the edit control by sending EM_SETSEL message with wParam==-1. Next, we append the data at that point with EM_REPLACESEL message.
为完成这个目标,首先,我们通过在发送EM_SETSEL消息的时候设置wParam参数的值为-1来把插入符移动到文本的末尾。然后,我们发送一个EM_REPLACESEL消息来添加加数据。
invoke CloseHandle,hRead
When ReadFile returns NULL, we break out of the loop and close the read handle.
当ReadFile函数返回值为NULL时,我们退出循环并且关闭读端句柄。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-1-11
Tutorial 22: Superclassing
第二十二课:超类化
________________________________________
In this tutorial, we will learn about superclassing, what it is and what it is for. You will also learn how to provide Tab key navigation to the controls in your own window.
在这一课中,我们将学习什么是超类化以及它能用来做什么。你也将学习到如何在你自己的窗口中为控件提供Tab键导航。(切换窗口)
Download the example here
Theory:
In your programming career, you will surely encounter a situation where you need several controls with *slightly* different behavior. For example, you may need 10 edit controls which accept only number. There are several ways to achieve that goal:
在你的程序员生涯中,你必定遭遇过这种情形,这里有你需要的一系列控件,但它们的行为又稍微有些不同。例如,你可能需要10个仅接收数字的编辑控件。这里有几种方法能完成目标:
• Create your own class and instantiate the controls
创建你自己的类并将控件实例化。
• Create those edit control and then subclass all of them
创建这些编辑控件然后将它们全部子类化。
• Superclass the edit control
超类化这个编辑控件。
The first method is too tedious. You have to implement every functionality of the edit control yourself. Hardly a task to be taken lightly. The second method is better than the first one but still too much work. It is ok if you subclass only a few controls but it's going to be a nightmare to subclass a dozen or so controls. Superclassing is the technique you should use for this occasion.
第一种方法是非常乏味的。你必须自己实现编辑控件的每一个功能。艰难的任务不是能被轻松完成的。第二种方法比第一种方法好很多但是仍要做很多工作。如果你子类化的控件仅有几个,那么这种方法仍可行。但是当你子类化一打(十二个)左右的控件时它将是一个恶梦。在这种场合,超类化才是你应该使用的技术。
Subclassing is the method you use to *take control* of a particular window class. By *taking control*, I mean you can modify the properties of the window class to suit your purpose then then create the bunch of controls.
The steps in superclassing is outlined below:
超类化用于操纵特别窗口类控件的方法。用操纵控件,我的意思是你能修改这个窗口类的性质来让它于你的目的想匹配,然后你能用它创建一捆的控件。
超类化的步骤 概要如下:
• call GetClassInfoEx to obtain the information about the window class you want to superclass. GetClassInfoEx requires a pointer to a WNDCLASSEX structure which will be filled with the information if the call returns successfully.
调用GetClassInfoEx函数来获得你想超类化的那个窗口的信息。GetClassInfoEx函数需要一个指向WNDCLADDEX结构体的指针,如果调用成功,这个结构体将被成功返回的信息填满。
• Modify the WNDCLASSEX members that you want. However, there are two members which you MUST modify:
修改你想改变的WNDCLASSEX结构的成员。无论如何,这里有两个成员是你必须修改。
o hInstance You must put the instance handle of your program into this member.
hInstance 你必须把你们的应用程序实例句柄放到这个成员中。
o lpszClassName You must provide it with a pointer to a new class name.
LpszClassName 你必须为它提供一个包含新类名的字符指针。
You need not modify lpfnWndProc member but most of the time, you need to do it. Just remember to save the original lpfnWndProc member if you want to call it with CallWindowProc.
你并不需要修改lpfnWndProc成员,但是在大多数情况下,你还是修改一下。仅需要记住的是如果你要调用的函数是CallWindowProc函数,你要保存lpfnWndProc的初始值。
• Register the modifed WNDCLASSEX structure. You'll have a new window class which has several characteristics of the old window class.
注册已修改过的WNDCLASSEX结构。你将有一个新的窗口类, 这个新窗口类将有几个特征和老窗口类的相同。
• Create windows from the new class
从这个窗口类中创建窗口。
Superclassing is better than subclassing if you want to create many controls with the same characteristics.
如果你想创建具有相同特征值的多个控件,超类化比子类化好得多。
Example:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WM_SUPERCLASS equ WM_USER+5
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "SuperclassWinClass",0
AppName db "Superclassing Demo",0
EditClass db "EDIT",0
OurClass db "SUPEREDITCLASS",0
Message db "You pressed the Enter key in the text box!",0
.data?
hInstance dd ?
hwndEdit dd 6 dup(?)
OldWndProc dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,220,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL wc:WNDCLASSEX
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
invoke RegisterClassEx, addr wc
xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start
Analysis:
分析:
The program will create a simple window with 6 "modified" edit controls in its client area. The edit controls will accept only hex digits. Actually, I modified the subclassing example to do superclassing. The program starts normally and the interesting part is when the main window is created:
程序将创建一个简单的窗口,在窗口的客户区有6个修改过的编辑控件。这些编辑控件仅接收16进制数。实际上,我是修改了子类化的例子做到超类化的。程序正常开始,有趣的部分是在主窗口被创建的时候:
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
We must first fill the WNDCLASSEX structure with the data from the class which we want to superclass, in this case, it's EDIT class. Remember that you must set the cbSize member of the WNDCLASSEX structure before you call GetClassInfoEx else the WNDCLASSEX structure will not be filled properly. After GetClassInfoEx returns, wc is filled with all information we need to create a new window class.
首先,我们必须用我们想超类化的那个类的数据成员来填充WNDCLASSEX结构。在我们的例子中,它就是EDIT类。记住,在你调用GetClassInfoEx函数之前,你必须设置WNDCLASSEX结构的cbSize成员的值,否则,WNDCLASSEX结构将不能被完全填充。在GetClassInfoEx返回之后,变量 wc中保存的就是想要创建一个新类所需要的所有信息。
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
Now we must modify some members of wc. The first one is the pointer to the window procedure. Since we need to chain our own window procedure with the original one, we have to save it into a variable so we can call it with CallWindowProc. This technique is identical to subclassing except that you modify the WNDCLASSEX structure directly without having to call SetWindowLong. The next two members must be changed else you will not be able to register your new window class, hInstance and lpsClassName. You must replace original hInstance value with hInstance of your own program. And you must choose a new name for the new class.
现在我们修改wc的一些成员。第一个成员是指向窗口过程的指针。因为在我们自己的窗口过程中要用到原来的窗口过程,我们必须把它保存在一个变量中,那么我们在调用CallWindowProc时就能够使用它。除了没有直接调用SetWindowLong函数来修改WNDCLADSDEX结构外,这个技术和子类化是一样的。接下来的两个成员必须被改变,否则你就不能注册你的新窗口类,它们是hInstance和lpsClassName。你必须用你拥有的程序实例替代原来的实例值并且你必须为这个新类选择一个新类名。
invoke RegisterClassEx, addr wc
When all is ready, register the new class. You will get a new class with some characteristics of the old class.
当所有的这些都准备好时,注册这个新的类。这个新窗口类将有几个特征和老窗口类的相同。
xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit
Now that we registered the class, we can create windows based on it. In the above snippet, I use ebx as the counter of the number of windows created. edi is used as the y coordinate of the left upper corner of the window. When a window is created, its handle is stored in the array of dwords. When all windows are created, set input focus to the first window.
At this point, you got 6 edit controls which accept only hex digits. The substituted window proc handles the filter. Actually, it's identical to the window proc in subclassing example. As you can see, you don't have to do extra work of subclassing them.
现在,我们注册了这个窗口类,我们就能基于它创建窗口了。在上面的代码片段中,我用ebx作为创建了多少个窗口的计数器。Edi保存窗口左上角的y坐标值。当一个窗口被创建时,它的句柄就被储存在一个DWORD的数组中。当所有的窗口都被创建时,设置输入焦点给第一个窗口。到这里,你得到了6个仅接收16进制数的编辑控件。取代的窗口过程处理了字符过滤。实际上,它和在子类化例子中的窗口过程一致。如你所见,你不必要去做窗口子类化的那些额外的工作了。
I throw in a code snippet to handle control navigation with tabs to make this example more juicy. Normally, if you put controls on a dialog box, the dialog box manager handles the navigation keys for you so you can tab to go to the next control or shift-tab to go back to the previous control. Alas, such feature is not available if you put your controls on a simple window. You have to subclass them so you can handle the Tab keys yourself. In our example, we need not subclass the controls one by one because we already superclassed them, so we can provide a "central control navigation manager" for them.
我插入用TAB键处理控件切换的代码片段来让这个例子更有趣。通常,你把一个控件放置在一个对话框上,对话框管理器为你处理定位键,所以你能用TAB键定位到下一个控件或者是用shift-tab键返回到上一个控件。唉,如果你把控件放置在一个窗口上时,这样的特性是无法使用的。你必须子类化它们来让你自己处理tab 键。在我们的例子中,我们不需要一个一个的子类化这些控件,因为我们已经超类化了它们。所以我们为它们提供了一个“中央控制导航管理器”
.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret
The above code snippet is from EditWndClass procedure. It checks if the user press Tab key, if so, it call GetKeyState to check if the SHIFT key is also pressed. GetKeyState returns a value in eax that determines whether the specified key is pressed or not. If the key is pressed, the high bit of eax is set. If not, the high bit is clear. So we test the return value against 80000000h. If the high bit is set, it means the user pressed shift+tab which we must handle separately.
If the user press Tab key alone, we call GetWindow to retrieve the handle of the next control. We use GW_HWNDNEXT flag to tell GetWindow to obtain the handle to the window that is next in line to the current hEdit. If this function returns NULL, we interpret it as no more handle to obtain so the current hEdit is the last control in the line. We will "wrap around" to the first control by calling GetWindow with GW_HWNDFIRST flag. Similar to the Tab case, shift-tab just works in reverse.
如果用户仅按下TAB键,我们调用GetWindow来获取下一个控件的句柄。我们使用GW_HWNDNEXT标志来告诉GetWindow函数获取的窗口句柄是和当前hEdit句柄链上的下一个句柄。如果这个函数返回值为NULL,说明在当前窗口句柄链中没有其它的句柄了,所以当前的hEdit是这个链上的最后一个句柄。我们在调用GetWindow函数时设置GW_HWNDFIRST来绕回到第一个控件上。类似于tab键,shift键处理过程正好相反。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-1-12