Win32汇编教程12-管道操作

来源(asm.yeah.net)

作者:罗云彬·发布日期:2000-10-7·阅读次数:9010

在这里下载本文的源代码

概述

  Windows 引入了多进程和多线程机制。同时也提供了多个进程之间的通信手段,包括剪贴板、DDE、OLE、管道等,和其他通信手段相比,管道有它自己的限制和特点,管道实际上是一段共享内存区,进程把共享消息放在那里。并通过一些 API 提供信息交换。

  管道是两个头的东西,每个头各连接一个进程或者同一个进程的不同代码,按照管道的类别分有两种管道,匿名的和命名的;按照管道的传输方向分也可以分成两种,单向的双向的。根据管道的特点,命名管道通常用在网络环境下不同计算机上运行的进程之间的通信(当然也可以用在同一台机的不同进程中)它可以是单向或双向的;而匿名管道只能用在同一台计算机中,它只能是单向的。匿名管道其实是通过用给了一个指定名字的有名管道来实现的。

  使用管道的好处在于:读写它使用的是对文件操作的 api,结果操作管道就和操作文件一样。即使你在不同的计算机之间用命名管道来通信,你也不必了解和自己去实现网络间通信的具体细节。

  我们简单的介绍一下命名管道的使用。

  命名管道是由服务器端的进程建立的,管道的命名必须遵循特定的命名方法,就是 "\\.\pipe\管道名",当作为客户端的进程要使用时,使用"\\计算机名\\pipe\管道名" 来打开使用,具体步骤如下:

1.服务端通过函数 CreateNamedPipe 创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。
2.服务端侦听来自客户端的连接请求,该功能通过 ConnectNamedPipe 函数实现。
3.客户端通过函数 WaitNamedPipe 来等待管道的出现,如果在超时值变为零以前,有一个管道可以使用,则 WaitNamedPipe 将返回 True,并通过调用 CreateFile 或 CallNamedPipe 来呼叫对服务端的连接。
此时服务端将接受客户端的连接请求,成功建立连接,服务端 ConnectNamedPipe 返回 True
4.建立连接之后,客户端与服务器端即可通过 ReadFile 和 WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。
5.当客户端与服务端的通信结束,客户端调用 CloseFile,服务端接着调用 DisconnectNamedPipe。最后调用函数CloseHandle来关闭该管道。

  由于命名管道使用时作为客户端的程序必须知道管道的名称,所以更多的用在同一“作者”编写的服务器/工作站程序中,你不可能随便找出一个程序来要求它和你写的程序来通过命名管道通信。而匿名管道的使用则完全不同,它允许你和完全不相干的进程通信,条件是这个进程通过控制台“console”来输入输出,典型的例子是老的 Dos 应用程序,它们在运行时 Windows 为它们开了个 Dos 窗口,它们的输入输出就是 console 方式的。还有一些标准的 Win32 程序也使用控制台输入输出,如果在 Win32 编程中不想使用图形界面,你照样可以使用 AllocConsole 得到一个控制台,然后通过 GetStdHandle 得到输入或输出句柄,再通过 WriteConsole 或 WriteFile 把结果输出到控制台(通常是一个象 Dos 窗口)的屏幕上。虽然这些程序看起来象 Dos 程序,但它们是不折不扣的 Win32 程序,如果你在纯 Dos 下使用,就会显示“The program must run under Windows!”。

  一个控制台有三个句柄:标准输入、标准输出和和标准错误句柄,标准输入、标准输出句柄是可以重新定向的,你可以用匿名管道来代替它,这样一来,你可以在管道的另一端用别的进程来接收或输入,而控制台一方并没有感到什么不同,就象 Dos 下的 > 或者 < 可以重新定向输出或输入一样。通常控制台程序的输入输出如下:

(控制台进程output) write ----> 标准输出设备(一般是屏幕)
(控制台进程input) read <---- 标准输入设备(一般是键盘)

而用管道代替后:

(作为子进程的控制台进程output) write ----> 管道1 ----> read (父进程)
(作为子进程的控制台进程input) read <----> 管道2 <---- write (父进程)

使用匿名管道的步骤如下:

1.使用 CreatePipe 建立两个管道,得到管道句柄,一个用来输入,一个用来输出
2.准备执行控制台子进程,首先使用 GetStartupInfo 得到 StartupInfo
3.使用第一个管道句柄代替 StartupInfo 中的 hStdInput,第二个代替 hStdOutput、hStdError,即标准输入、输出、错误句柄
4.使用 CreateProcess 执行子进程,这样建立的子进程输入和输出就被定向到管道中
5.父进程通过 ReadFile 读第二个管道来获得子进程的输出,通过 WriteFile 写第一个管道来将输入写到子进程
6.父进程可以通过 PeekNamedPipe 来查询子进程有没有输出
7.子进程结束后,要通过 CloseHandle 来关闭两个管道。

下面是具体的说明和定义:

1. 建立匿名管道使用 CreatePipe 原形如下:

BOOL CreatePipe(
    PHANDLE hReadPipe, // address of variable for read handle
    PHANDLE hWritePipe, // address of variable for write handle
    LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes
    DWORD nSize // number of bytes reserved for pipe
);



当管道建立后,结构中指向的 hReadPipe 和 hWritePipe 可用来读写管道,当然由于匿名管道是单向的,你只能使用其中的一个句柄,参数中的 SECURITY_ATTRIBUTES 的结构必须填写,定义如下:

typedef struct_SECURITY_ATTRIBUTES{
    DWORD   nLength:                //定义以字节为单位的此结构的长度
    LPVOID  lpSecurityDescriptor;   //指向控制这个对象共享的安全描述符,如果为NULL这个对象将被分配一个缺省的安全描述
    BOOL    bInheritHandle;         //当一个新过程被创建时,定义其返回是否是继承的.供系统API函数使用.
}SECURITY_ATTRIBUTES;



2. 填写创建子进程用的 STARTUPINFO 结构,一般我们可以先用 GetStartupInfo 来填写一个缺省的结构,然后改动我们用得到的地方,它们是:

hStdInput -- 用其中一个管道的 hWritePipe 代替
hStdOutput、hStdError -- 用另一个管道的 hReadPipe 代替
dwFlags -- 设置为 STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW 表示输入输出句柄及 wShowWindow 字段有效
wShowWindow -- 设置为 SW_HIDE,这样子进程执行时不显示窗口。
填写好以后,就可以用 CreateProcess 来执行子进程了,具体有关执行子进程的操作可以参考上一篇教程《进程控制》

3. 在程序中可以用 PeekNamedPipe 查询子进程有没有输出,原形如下:

BOOL PeekNamedPipe(
    HANDLE  hNamedPipe,             // handle to pipe to copy from
    LPVOID  lpBuffer,               // pointer to data buffer
    DWORD   nBufferSize,            // size, in bytes, of data buffer
    LPDWORD lpBytesRead,            // pointer to number of bytes read
    LPDWORD lpTotalBytesAvail,      // pointer to total number of bytes available
    LPDWORD lpBytesLeftThisMessage  // pointer to unread bytes in this message
);



我们可以将尝试读取 nBuffersize 大小的数据,然后可以通过返回的 BytesRead 得到管道中有多少数据,如果不等于零,则表示有数据可以读取。

4. 用 ReadFile 和 WriteFile 来读写管道,它们的参数是完全一样的,原形如下:

ReadFile or WriteFile(
    HANDLE  hFile,                  // handle of file to read 在这里使用管道句柄
    LPVOID  lpBuffer,               // address of buffer that receives data 缓冲区地址
    DWORD   nNumberOfBytesToRead,   // number of bytes to read 准备读写的字节数
    LPDWORD lpNumberOfBytesRead,    // address of number of bytes read,实际读到的或写入的字节数
    LPOVERLAPPED lpOverlapped       // address of structure for data 在这里用 NULL
);



5. 用 CloseHandle 关闭管道一和管道二的 hReadPipe和 hWritePipe 这四个句柄。

下面给出了一个例子程序,这个程序是上篇教程《进程控制》的例子的扩充,如果你对有的 api 感到陌生的话,请先阅读上一篇教程。

源程序 - 汇编源文件

DEBUG       equ     0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;    Programmed by 罗云彬, bigluo@telekbird.com.cn
;    Website: http://asm.yeah.net
;    LuoYunBin's Win32 ASM page (罗云彬的编程乐园)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;    版本信息
;    汇编教程附带例子程序 - 管道例子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat, stdcall
option casemap :none   ; case sensitive
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;    include     数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include     windows.inc
include     user32.inc
include     kernel32.inc
include     comctl32.inc
include     comdlg32.inc
include     gdi32.inc

includelib  user32.lib
includelib  kernel32.lib
includelib  comctl32.lib
includelib  comdlg32.lib
includelib  gdi32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;    Equ 数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN    equ     1000
MENU_MAIN   equ     2000
IDM_EXEC    equ     2001
IDM_EXIT    equ     2002

F_RUNNING   equ     0001h   ;进程在运行中
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;    数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?

stStartUp   STARTUPINFO

hInstance   dd  ?
hMenu       dd  ?
hWinMain    dd  ?
hWinText    dd  ?
hFont       dd  ?
hRunThread  dd  ?
hRead1      dd  ?
hWrite1     dd  ?
hRead2      dd  ?
hWrite2     dd  ?
szBuffer    db  512 dup (?)

dwFlag      dd  ?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data

szMenuExecute       db  '连接 MS-&DOS 方式',0
szExcuteError       db  '启动应用程序错误!',0
szCaption           db  '管道示例程序 ... http://asm.yeah.net',0
szClassName         db  'PipeExample',0
;szDllName          db  'riched32.dll',0
;szClassNameRedit   db  'RichEdit',0
szDllName           db  'riched20.dll',0
szClassNameRedit    db  'richedit20a',0
szCommand           db  'c:\command.com',0

stLogFont   LOGFONT <24,0,0,0,FW_NORMAL,\
                    0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,\
                    CLIP_STROKE_PRECIS,DEFAULT_QUALITY,\
                    DEFAULT_PITCH or FF_SWISS,"Fixedsys">

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;    代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

if DEBUG
    include     Debug.asm
endif
include     Win.asm

;********************************************************************
; 执行程序用的线程
; 1. 用 CreateProcess 建立进程
; 2. 用 WaitForSingleOject 等待进程结束
;********************************************************************
_RunThread  proc    uses ebx ecx edx esi edi,\
        dwParam:DWORD
    local   @stSecurity:SECURITY_ATTRIBUTES
    local   @dwExitCode
    local   @dwBytesRead
    local   @stRange:CHARRANGE

    or      dwFlag,F_RUNNING
;********************************************************************
; “执行”菜单改为“结束”
;********************************************************************
    invoke  EnableMenuItem,hMenu,IDM_EXEC,MF_GRAYED
    invoke  EnableMenuItem,hMenu,IDM_EXIT,MF_GRAYED
;********************************************************************
; 建立管道
;********************************************************************
    mov     @stSecurity.nLength,sizeof SECURITY_ATTRIBUTES
    mov     @stSecurity.lpSecurityDescriptor,NULL
    mov     @stSecurity.bInheritHandle,TRUE
    invoke  CreatePipe,addr hRead1,addr hWrite1,addr @stSecurity,NULL
    invoke  CreatePipe,addr hRead2,addr hWrite2,addr @stSecurity,NULL

;********************************************************************
; 执行文件,如果成功则等待程序结束
;********************************************************************
    invoke  GetStartupInfo,addr stStartUp
    mov     eax,hRead1
    mov     stStartUp.hStdInput,eax
    mov     eax,hWrite2
    mov     stStartUp.hStdOutput,eax
    mov     stStartUp.hStdError,eax
    mov     stStartUp.dwFlags,STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW
    mov     stStartUp.wShowWindow,SW_HIDE
    invoke  CreateProcess,NULL,addr szCommand,NULL,NULL,\
                    NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo
    .if eax != 0
        .while TRUE
            invoke  GetExitCodeProcess,stProcInfo.hProcess,addr @dwExitCode
            .break  .if @dwExitCode != STILL_ACTIVE
            invoke  PeekNamedPipe,hRead2,addr szBuffer,511,addr @dwBytesRead,NULL,NULL
            .if @dwBytesRead != 0
                invoke  RtlZeroMemory,addr szBuffer,512
                invoke  ReadFile,hRead2,addr szBuffer,@dwBytesRead,\
                                addr @dwBytesRead,NULL
                mov     @stRange.cpMin,-1
                mov     @stRange.cpMax,-1
                invoke  SendMessage,hWinText,EM_EXSETSEL,0,addr    @stRange
                invoke  SendMessage,hWinText,EM_REPLACESEL,FALSE,addr szBuffer
                invoke  SendMessage,hWinText,EM_SCROLLCARET,NULL,NULL
                invoke  SendMessage,hWinText,WM_SETFONT,hFont,0
            .endif
        .endw
        invoke  CloseHandle,stProcInfo.hProcess
        invoke  CloseHandle,stProcInfo.hThread
    .else
        invoke  MessageBox,hWinMain,addr szExcuteError,NULL,MB_OK or MB_ICONERROR
    .endif
;********************************************************************
; 关闭管道
;********************************************************************
    invoke  CloseHandle,hRead1
    invoke  CloseHandle,hWrite1
    invoke  CloseHandle,hRead2
    invoke  CloseHandle,hWrite2
;********************************************************************
; 把“结束”菜单改为“执行”
;********************************************************************
    invoke  EnableMenuItem,hMenu,IDM_EXEC,MF_ENABLED
    invoke  EnableMenuItem,hMenu,IDM_EXIT,MF_ENABLED
    invoke  EnableWindow,hWinText,FALSE
    and     dwFlag,not F_RUNNING
    ret

_RunThread  endp

;********************************************************************
;    窗口程序
;********************************************************************
WndMainProc proc    uses ebx edi esi, \
    hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

    mov     eax,wMsg
;********************************************************************
    .if eax == WM_CREATE
        mov     eax,hWnd
        mov     hWinMain,eax
        call    _Init
;********************************************************************
    .elseif eax == WM_SIZE
        mov     edx,lParam
        mov     ecx,edx
        shr     ecx,16
        and     edx,0ffffh
        invoke  MoveWindow,hWinText,0,0,edx,ecx,TRUE
        invoke  PostMessage,hWinText,WM_SIZE,wParam,lParam
;********************************************************************
    .elseif eax == WM_CLOSE
        test    dwFlag,F_RUNNING
        .if ZERO?
            invoke  DestroyWindow,hWinMain
            invoke  PostQuitMessage,NULL
        .endif
;********************************************************************
    .elseif eax == WM_COMMAND
        mov     eax,wParam
        .if ax == IDM_EXEC
;********************************************************************
; 如果没有在执行中(dwFlag 没有置位) 则建立线程,在线程中执行程序
; 如果已经在执行中,则用 TerminateProcess 终止执行
;********************************************************************
            test    dwFlag,F_RUNNING
            .if ZERO?
                invoke  EnableWindow,hWinText,TRUE
                invoke  SetFocus,hWinText
                invoke  CreateThread,NULL,NULL,offset _RunThread,\
                                NULL,NULL,offset hRunThread
            .else
                invoke  TerminateProcess,stProcInfo.hProcess,-1
            .endif
        .elseif ax == IDM_EXIT
            invoke  DestroyWindow,hWinMain
            invoke  PostQuitMessage,NULL
        .endif
    .else
        invoke  DefWindowProc,hWnd,wMsg,wParam,lParam
        ret
    .endif
    xor    eax,eax
    ret

WndMainProc endp
;********************************************************************
; 程序入口
;********************************************************************
start:
    call    _WinMain
    invoke  ExitProcess,NULL
;********************************************************************
_WinMain    proc
    local   @stWcMain:WNDCLASSEX
    local   @stMsg:MSG
    local   @hRichEdit

    invoke  LoadLibrary,offset szDllName
    mov     @hRichEdit,eax

    invoke  InitCommonControls
    invoke  GetModuleHandle,NULL
    mov     hInstance,eax
    invoke  LoadMenu,hInstance,MENU_MAIN
    mov     hMenu,eax
;***************** 注册窗口类 ***************************************
    invoke  LoadCursor,0,IDC_ARROW
    mov     @stWcMain.hCursor,eax
    mov     @stWcMain.cbSize,sizeof    WNDCLASSEX
    mov     @stWcMain.hIconSm,0
    mov     @stWcMain.style,CS_HREDRAW or CS_VREDRAW
    mov     @stWcMain.lpfnWndProc,offset WndMainProc
    mov     @stWcMain.cbClsExtra,0
    mov     @stWcMain.cbWndExtra,0
    mov     eax,hInstance
    mov     @stWcMain.hInstance,eax
    invoke  LoadIcon,hInstance,ICO_MAIN
    mov     @stWcMain.hIcon,eax
    mov     @stWcMain.hbrBackground,COLOR_BTNFACE+1
    mov     @stWcMain.lpszClassName,offset szClassName
    mov     @stWcMain.lpszMenuName,0
    invoke  RegisterClassEx,addr @stWcMain
;***************** 建立输出窗口    *****************************************
    invoke  CreateWindowEx,NULL,\
                    offset szClassName,offset szCaption,\
                    WS_OVERLAPPEDWINDOW,\
                    0,0,680,420,\
                    NULL,hMenu,hInstance,NULL

    invoke  ShowWindow,hWinMain,SW_SHOWNORMAL
    invoke  UpdateWindow,hWinMain
;********************************************************************
    .while TRUE
        invoke  GetMessage,addr @stMsg,NULL,0,0
        .break  .if eax == 0
        invoke  TranslateMessage,addr @stMsg
        invoke  DispatchMessage,addr @stMsg
    .endw
    invoke  FreeLibrary,@hRichEdit
    invoke  DeleteObject,hFont
    ret

_WinMain    endp

;********************************************************************
;    输入程序
;********************************************************************
_InputProc  proc    uses ebx edi esi, \
        hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    local   @szBuffer[4]:BYTE
    local   @dwBytesWrite

    mov     eax,uMsg
    .if eax == WM_CHAR
        mov     eax,wParam
        movzx   eax,al
        mov     dword ptr @szBuffer,eax
        test    dwFlag,F_RUNNING
        .if !ZERO?
            invoke  WriteFile,hWrite1,addr @szBuffer,1,addr @dwBytesWrite,NULL
        .endif
        xor    eax,eax
        ret
    .endif
    invoke  GetWindowLong,hWnd,GWL_USERDATA
    invoke  CallWindowProc,eax,hWnd,uMsg,wParam,lParam
    ret

_InputProc  endp
;********************************************************************
_Init   proc

;*************** 建立输出 RICHEDIT 窗口    ***********************************
    invoke  CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassNameRedit,\
                NULL,WS_CHILD OR WS_VISIBLE OR WS_VSCROLL OR WS_HSCROLL\
                OR ES_MULTILINE    OR ES_AUTOHSCROLL OR ES_AUTOVSCROLL,\
                0,0,0,0,\
                hWinMain,NULL,hInstance,NULL
    mov     hWinText,eax
;*************** 设置字体 ***********************************************
    invoke  CreateFontIndirect,offset stLogFont
    mov     hFont,eax
    invoke  SendMessage,hWinText,WM_SETFONT,hFont,0
    invoke  SendMessage,hWinText,EM_SETREADONLY,TRUE,NULL

    invoke  SetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc
    invoke  SetWindowLong,hWinText,GWL_USERDATA,eax
    invoke  EnableWindow,hWinText,FALSE

    invoke  _CenterWindow,hWinMain
    invoke  SetFocus,hWinText

    ret

_Init   endp
;********************************************************************
        end     start



程序的分析和要点

  在程序中,我先建立了一个 Richedit 控件用来显示子进程的输出,同时将 RichEdit 子类化,截取它的键盘输入以便把它发给子进程

invoke  SetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc


  这条语句将 RichEdit 的过程指到了 _InputProc 中,然后在 _InputProc 的 WM_CHAR 中将键入的字符 WriteFile 到管道中,我在程序中先建立了两个管道,然后执行 c:\command.com,这样就得到了一个 dos 的命令行进程,然后在循环中通过 PeekNamedPipe 检测子进程有无输出,如果有的话则通过 ReadFile 读出,在显示到 RichEdit 中。

  在运行例子程序的时候要注意,你可以在这个“Command.com” 中执行几乎所有的别的程序,但是不要执行如 ucdos,pctools 之类不使用标准输入输出的程序(就是在 dos 下用不了“>”或者“<”重定向的程序),由于我们在装载子进程的时候用了 WS_HIDE,所以原来的 command.com 的窗口是隐藏的,如果你执行了这种程序那就意味着你失去的对子进程的控制,因为它们不使用标准输入来接收键盘,你也就无法通过管道让它们退出。

  在这里还可以引申出匿名管道的另一个用法,如果你执行的不是 command.com 而是类似于 arj.exe 的程序,然后也不用把它的输出显示到 RichEdit 中,而是在程序中处理,那么,你就可以编写一个 winarj,当然你只需编写窗口界面和 arj.exe 之间的配合而已。

Link: http://www.asm32.net/article_details.aspx?id=3159


浏览次数 204 发布时间 2007-10-20 02:14:27 从属分类 ASM/ASM32 【评论】【 】【打印】【关闭
 
| www.asm32.net | 2006版 | 资料中心 | linux | asm/asm32 | C/C++ | VC++ | java | Python | 书签 | ASP.Net书签 | 京ICP备09029108号-1