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下使用,就会显示 “TheprogrammustrununderWindows!”。
一个控制台有三个句柄:标准输入、标准输出和和标准错误句柄,标准输入、标准输出句柄是可以重新定向的,你可以用匿名管道来代替它,这样一来,你可以在管道的另一端用别的进程来接收或输入,而控制台一方并没有感到什么不同,就象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原形如下:
BOOLCreatePipe(
PHANDLEhReadPipe,//addressofvariableforreadhandle
PHANDLEhWritePipe,//addressofvariableforwritehandle
LPSECURITY_ATTRIBUTESlpPipeAttributes,//pointertosecurityattributes
DWORDnSize//numberofbytesreservedforpipe
);
当管道建立后,结构中指向的hReadPipe和hWritePipe可用来读写管道,当然由于匿名管道是单向的,你只能使用其中的一个句柄,参数中的SECURITY_ATTRIBUTES的结构必须填写,定义如下:
includelibcomdlg32.lib
includelibgdi32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Equ数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAINequ1000
MENU_MAINequ2000
IDM_EXECequ2001
IDM_EXITequ2002
F_RUNNINGequ0001h;进程在运行中
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
stStartUpSTARTUPINFO
hInstancedd?
hMenudd?
hWinMaindd?
hWinTextdd?
hFontdd?
hRunThreaddd?
hRead1dd?
hWrite1dd?
hRead2dd?
hWrite2dd?
szBufferdb512dup(?)
dwFlagdd?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
szMenuExecutedb’连接MS-&DOS方式’,0
szExcuteErrordb’启动应用程序错误!’,0
szCaptiondb’管道示例程序...http://asm.yeah.net’,0
szClassNamedb’PipeExample’,0
;szDllNamedb’riched32.dll’,0
;szClassNameReditdb’RichEdit’,0
szDllNamedb’riched20.dll’,0
szClassNameReditdb’richedit20a’,0
szCommanddb’c:\\command.com’,0
stLogFontLOGFONT<24,0,0,0,FW_NORMAL,\\
0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,\\
CLIP_STROKE_PRECIS,DEFAULT_QUALITY,\\
DEFAULT_PITCHorFF_SWISS,\"Fixedsys\">
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
ifDEBUG
includeDebug.asm
endif
includeWin.asm
;********************************************************************
;执行程序用的线程
;1.用CreateProcess建立进程
;2.用WaitForSingleOject等待进程结束
;********************************************************************
_RunThreadprocusesebxecxedxesiedi,\\
dwParam:DWORD
local@stSecurity:SECURITY_ATTRIBUTES
local@dwExitCode
local@dwBytesRead
local@stRange:CHARRANGE
ordwFlag,F_RUNNING
;********************************************************************
;“执行”菜单改为“结束”
;********************************************************************
invokeEnableMenuItem,hMenu,IDM_EXEC,MF_GRAYED
invokeEnableMenuItem,hMenu,IDM_EXIT,MF_GRAYED
;********************************************************************
;建立管道
;********************************************************************
mov@stSecurity.nLength,sizeofSECURITY_ATTRIBUTES
mov@stSecurity.lpSecurityDescriptor,NULL
mov@stSecurity.bInheritHandle,TRUE
invokeCreatePipe,addrhRead1,addrhWrite1,addr@stSecurity,NULL
invokeCreatePipe,addrhRead2,addrhWrite2,addr@stSecurity,NULL
typedefstruct_SECURITY_ATTRIBUTES{
DWORDnLength://定义以字节为单位的此结构的长度
LPVOIDlpSecurityDescriptor;//指向控制这个对象共享的安全描述符,如果为NULL这个对象将被分配一个缺省的安全描述
BOOLbInheritHandle;//当一个新过程被创建时,定义其返回是否是继承的.供系统API函数使用.
}SECURITY_ATTRIBUTES;
2.填写创建子进程用的STARTUPINFO结构,一般我们可以先用GetStartupInfo来填写一个缺省的结构,然后改动我们用得到的地方,它们是:
hStdInput--用其中一个管道的hWritePipe代替
hStdOutput、hStdError--用另一个管道的hReadPipe代替
dwFlags--设置为STARTF_USESTDHANDLESorSTARTF_USESHOWWINDOW表示输入输出句柄及wShowWindow字段有效
wShowWindow--设置为SW_HIDE,这样子进程执行时不显示窗口。
填写好以后,就可以用CreateProcess来执行子进程了,具体有关执行子进程的操作可以参考上一篇教程《进程控制》
3.在程序中可以用PeekNamedPipe查询子进程有没有输出,原形如下:
BOOLPeekNamedPipe(
HANDLEhNamedPipe,//handletopipetocopyfrom
LPVOIDlpBuffer,//pointertodatabuffer
DWORDnBufferSize,//size,inbytes,ofdatabuffer
LPDWORDlpBytesRead,//pointertonumberofbytesread
LPDWORDlpTotalBytesAvail,//pointertototalnumberofbytesavailable
LPDWORDlpBytesLeftThisMessage//pointertounreadbytesinthismessage
);
我们可以将尝试读取nBuffersize大小的数据,然后可以通过返回的BytesRead得到管道中有多少数据,如果不等于零,则表示有数据可以读取。
4.用ReadFile和WriteFile来读写管道,它们的参数是完全一样的,原形如下:
ReadFileorWriteFile(
HANDLEhFile,//handleoffiletoread在这里使用管道句柄
LPVOIDlpBuffer,//addressofbufferthatreceivesdata缓冲区地址
DWORDnNumberOfBytesToRead,//numberofbytestoread准备读写的字节数
LPDWORDlpNumberOfBytesRead,//addressofnumberofbytesread,实际读到的或写入的字节数
LPOVERLAPPEDlpOverlapped//addressofstructurefordata在这里用NULL
);
5.用CloseHandle关闭管道一和管道二的hReadPipe和hWritePipe这四个句柄。
下面给出了一个例子程序,这个程序是上篇教程《进程控制》的例子的扩充,如果你对有的api感到陌生的话,请先阅读上一篇教程。
源程序-汇编源文件
DEBUGequ0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Programmedby罗云彬,bigluo@telekbird.com.cn
;Website:http://asm.yeah.net
;LuoYunBin’sWin32ASMpage(罗云彬的编程乐园)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;版本信息
;汇编教程附带例子程序-管道例子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.modelflat,stdcall
optioncasemap:none;casesensitive
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Include数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
includewindows.inc
includeuser32.inc
includekernel32.inc
includecomctl32.inc
includecomdlg32.inc
includegdi32.inc
includelibuser32.lib
includelibkernel32.lib
includelibcomctl32.lib