接《win32管道技术和进程通信实例(二)》,win32还有一种方法实现进程的通信,就是邮槽。
邮槽
邮槽是基于广播通信体系设计出来的,拥有一个服务端程序和一个客户端程序,服务端用来接收数据,客户端用来发送数据。
邮槽服务端编写步骤:
①使用CreateMailslot创建一个邮槽并且指定邮槽的名字和返回邮槽服务端的句柄。如果邮槽名称已经存在,那么会发生错误。
HANDLE CreateMailslot( LPCTSTR lpName, // pointer to string for mailslot name DWORD nMaxMessageSize, // maximum message size DWORD lReadTimeout, // milliseconds before read time-out LPSECURITY_ATTRIBUTES lpSecurityAttributes // pointer to security structure );
第一个参数邮槽的名字,它的格式是\\.\mailslot\[path]name;第二个参数,写入邮槽的最大字节数,如果设置为0表示,大小不限制(据说我们应该把传输的字节控制在424字节以下);第三个参数,以毫秒为单位设置超时时间,也可以使用0或者MAILSLOT_WAIT_FOREVER,前者表示当前没有消息就立即返回,后者表示一直等待消息。第四个参数,指向安全属性结构,主要设置创建的对象能都被子进程继承,可以设置为NULL,表示不可继承。(这样看来,邮槽也是个内核对象?)
②使用ReadFile读取数据。
邮槽客户端编写程序
①使用CreateFile打开邮槽
②使用WriteFile写入数据
看上去挺简单的,下面是一个实例:
服务端:
#include<windows.h> #include<iostream.h> int main(){ //创建邮槽 HANDLE hMailslot=CreateMailslot("\\\\.\\mailslot\\MailslotForTest",0,MAILSLOT_WAIT_FOREVER,NULL); if(INVALID_HANDLE_VALUE==hMailslot){ cout<<"Fail to create mailslot..."<<endl; } char readBuff[100]; ZeroMemory(readBuff,sizeof(readBuff)); if(!ReadFile(hMailslot,readBuff,100,0,NULL)){ cout<<"Fail to read from mailslot..."<<endl; CloseHandle(hMailslot); } cout<<readBuff<<endl; CloseHandle(hMailslot); return 0; }
客户端:
#include<windows.h> #include<iostream.h> int main(){ HANDLE hMailslot=CreateFile("\\\\.\\mailslot\\MailslotForTest",GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hMailslot){ cout<<"Fail to open mailslot..."<<endl; return 0; } char *writeBuff="Hello!I‘am Client,I‘ll tell u something..."; if(!WriteFile(hMailslot,writeBuff,strlen(writeBuff),0,NULL)){ cout<<"Fail to write to mailslot..."<<endl; CloseHandle(hMailslot); return 0; } CloseHandle(hMailslot); return 0; }
运行的时候先打开服务端,让它先创建一个邮槽并一直等待客户端打开邮槽,并往邮槽写入数据;再打开客户端打开邮槽并且写入数据。
进程的概念
前面了解了进程是一个内核对象,进程的创建,子进程如何继承父进程的句柄表,以及进程的通信,进程的诞生到死亡的过程等。那么进程的概念是什么?系统如何创建一个进程内核对象来管理每个进程?如何利用进程关联的内核对象来操作进程?下面的内容可以看作《windows核心编程》第四章的学习笔记。
①进程的定义
一般将进程定义成一个正在运行的一个实例。它由两部分构成:
一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方。这是不是有点绕?
一个地址空间,其包含所有可执行文件或DLL模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。
进程是有“惰性”的。进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。一个进程可以有多个线程,它们在地址空间“同时”执行代码。为此,每个线程都有它自己的一组CPU寄存器和它自己的堆栈。进程至少要有一个主线程。使用CreateProcess创建进程的时候,其实也创建了一个主线程。没有了线程,进程中的代码得不到执行,进程也失去了意义,系统就把进程销毁了。
②对于标准的win32应用程序还有什么可说的
想一下,我们编写一段win32程序的代码,这段代码被编译链接为一个可执行文件,双击这个可执行文件,系统为它分配资源,一个进程就诞生了。这不就是一个进程的诞生吗?不管程序里的静态资源文件有没有参与编译,exe就是我们的程序生成的最终的程序。系统为这个程序配备资源,这个程序才最终得以运行,成为一个进程。我想的是我们通过编写程序,可以控制进程的哪些东西?哪些是我们没法控制的?
来看看一个标准的win32程序值得注意的?来看看WinMain入口函数:
int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window );
第一个参数hInstance,可执行文件的实例句柄,加载到进程地址空间的每一个可执行文件或者DLL文件都被赋予了一个独一无二的实例句柄。在需要加载资源的函数调用中,一般要用到此句柄,比如LoadIcon(hInstance,pszIcon)就是表示,从可执行文件的映像中加载图标资源。hInstance参数的实际值是一个内存基地址;系统将可执行文件的映像加载到进程地址空间中的这个位置。可以使用GetModileHandle函数返回一个句柄/基地址。
第二个参数hPrevInstance,在win32程序中总是设为NULL,目的兼容16位Windows系统。
第三个参数lpCmdLine指向命令行参数,系统创建新进程的时候,会传一个命令给它。试了一下,可以这样获得命令行参数:
case WM_CREATE: { LPSTR lpCmdLine=GetCommandLine(); MessageBox(hwnd,lpCmdLine,"msg",MB_OK); return 0; }
直接运行,输出
也可以在dos下输入命令“ProcessTest.exe param1 param2”,运行,得到的结果是:
这样看来跟console程序的argc和argv没有差别嘛。事实上,我们也可以使用ShellAPI.h文件中申明并由Shell32.dll导出的函数CommandLineToArgvW(将任何Unicode字符串分解为单独的标记)来lpCmdLine分解为argc和argv。例如
LPSTR lpCmdLine=GetCommandLine(); //MessageBox(hwnd,lpCmdLine,"msg",MB_OK); int argc; PWSTR *ppArgv=CommandLineToArgvW((LPCWSTR)lpCmdLine,&argc); MessageBox(hwnd,(char *)ppArgv[0],"",MB_OK);
③进程的环境变量
每个进程都有一个与它关联的环境块(Environment block),这是进程地址空间内分配的一块内存,其中包含字符串与下面相似。
=::=::\ ...
VarName1=VarValue1\0
VarName2=VarValue2\0
VarName3=VarValue3\0
VarNameX=VarValueX\0
\0
意思是“环境变量的名称=变量的值”
这里又需要理解了,进程的环境变量有什么用?
之前需要在dos下不带文件路径直接输命令的时候,经常配置这样的环境变量。而且通常是手工配置的,比如win10环境变量设置界面是一个这样的东东
环境变量相当于给系统或用户应用程序设置一些参数,具体其什么作用视具体的环境变量而定。比如path,是告诉系统,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统出了在当前目录下寻找此程序外,还应到哪些目录下去寻找。在tc或者vc++中,set include=path1;path2;是告诉编译程序到哪里去找.h类型的文件。(就是比如在vc6依次打开工具-选项-目录,里面可以看到目录和路径的关系。在里面设置include=path;set include=是使用dos命令设置。具体可以参考《配置VC++环境变量》)当然不仅仅是指定什么路径,还有其他的作用,如set dircmd=/4设置一个环境变量的作用是在使用dir命令时会把/4作为缺省的参数添加到dir命令之后,它实际上是给命令解释程序command设置的一个环境变量,并且是给dir这个内部命令设置的。(百度百科)
我们可以通过GetEnvironmentStrings函数来获得完整的环境块。得到的环境块格式与前面描述的完全一致,因此需要从中提取环境变量和内容。(未完待续...)