《VC++深入详解》学习笔记 第十七章 进程间通信
方法一:通过剪贴板实现通信
打开剪贴板: BOOL OpenClipboard( ); 开启成功返回true
清空剪贴板数据: EmptyClipboard();
添加数据到剪贴板: HANDLE SetClipboardData(UINT uFormat(剪贴板格式), HANDLE hMen(指定格式数据句柄));
延迟提交技术: 第一次添加数据时,不添加数据内容(数据句柄为NULL),当其他进程访问该数据时,第二次添加数据添加数据内容,避免数据内存长时间占用产生的浪费。
内存堆分配: HGLOBAL GlobalAlloc(UINT uFlag(分配方式), SIZE_T dwBytes(分配字节数));
加锁目标内存,返回第一字节指针: LPVOID GlobalLock(HGLOBAL hMem); 句柄和指针的转换
解锁目标内存: GlobalUnlock(HGLOBAL);
重分配目标内存: GlobalRealloc();
关闭剪贴板: OpenClipboard( ); 必须关闭剪贴板后,其他进程才能打开
检查剪贴板中数据格式: BOOL IsClipboardFormatAvailable(UINT uFormat)
获取数据从剪贴板: HANDLE GetClipboardData(UINT uFormat(剪贴板格式);
向剪贴板发送数据(复制): 打开剪贴板>清空剪贴板>获取数据字符串>根据字符串长度分配内存,内存长度要加一>加锁内存获取地址>将字符串拷贝到目标地址上>解锁内存>添加数据到剪贴板>关闭剪贴板
从剪贴板接收数据(粘贴): 打开剪贴板>检查剪贴板数据格式>获取剪贴板数据句柄>加锁内存获取地址>解锁内存>根据地址使用数据>关闭剪贴板
方法二:通过匿名管道实现通信
匿名管道: 未命名、单向的管道,常用于父进程和子进程传输数据。只支持本地通信
创建匿名管道: BOOL CreatePipe(返回读取句柄,返回写入句柄,检查句柄是否能被继承(默认安全符),指定缓冲区大小(不是设定值))
SECURITY_ATTRIBUTES指针:
typedef struct _SECURITY_ATTRIBUTES{
DWOED nLength; //结构体大小
LPVOID lpSecurityDescriptor; //指向安全描述符指针,本次可以设定为NULL有系统创建默认的安全描述符
BOOL bInheritHandle; //指定返回句柄是否能被继承
}SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;
创建新进程:
BOOL CreateProcess ( LPCTSTR lpApplicationName, //以NULL结尾的字符串,指定程序名称(包含路径,扩展名)。该参数为NULL,文件名是lpCommandLine第一个空格界定标记,支持“” LPTSTR lpCommandLine, //以NULL结尾的字符串,传递给新进程命令行。该参数为NULL,函数使用lpApp...作为命令行。两者命令行区别在于前者默认路径为当前目录,没有则识别返回,
并且不会自动添加扩展名 LPSECURITY_ATTRIBUTES lpProcessAttributes, //指定进程对象安全性 LPSECURITY_ATTRIBUTES lpThreadAttributes, //指定线程对象安全性 BOOL bInheritHandles, //指定子进程是否能继承父进程句柄 DWORD dwCreationFlags, //指定控件优先级和进程创建的附加标记 LPVOID lpEnvironment, //指向环境块指针,NULL LPCTSTR lpCurrentDirectory, //指向空终止的字符串,用来指定子进程当前路径,必须包含完成路径名。该参数为空,新的子进程与父进程拥有相同路径 LPSTARTUPINFO lpStartupInfo, //指定新进程窗口显示方式指针 LPPROCESS_INFORMATION lpProcessInformation //返回新进程标识指针 PROCESS_INFORMATION结构体 );
窗口显示方式结构体STARTUPINFO
typedef struct _STARTUPINFO { DWORD cb; //包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,它可用作版本控制手段.应用程序必须将cb初始化为sizeof ( STARTUPINFO ) PSTR lpReserved; //保留。必须初始化为N U L L PSTR lpDesktop; //用于标识启动应用程序所在的桌面的名字。如果该桌面存在,新进程便与指定的桌面相关联。如果桌面不存在,便创建一个带有默认属性的桌面,并使用为新进程指定的名字。
如果lpDesktop是NULL(这是最常见的情况 ),那么该进程将与当前桌面相关联 PSTR lpTitle; //用于设定控制台窗口的名称。如果l p Ti t l e 是N U L L ,则可执行文件的名字将用作窗口名 DWORD dwX; //用于设定应用程序窗口在屏幕上应该放置的位置的x 和y 坐标(以像素为单位)。 DWORD dwY; //只有当子进程用CW_USEDEFAULT作为CreateWindow的x参数来创建它的第一个重叠窗口时, 才使用这两个坐标。若是创建控制台窗口的应用程序,这些成员用于指明控制台
窗口的左上角 DWORD dwXSize; //用于设定应用程序窗口的宽度和长度(以像素为单位)只有dwYsize DWORD dwYSize; // 当子进程将CW_USEDEFAULT 用作CreateWindow 的nWidth参数来创建它的第一个重叠窗口时,才使用这些值。若是创建控制台窗口的应用程序,这些成员将用于指明控制台窗
口的宽度 DWORD dwXCountChars; //用于设定子应用程序的控制台窗口的宽度和高度(以字符为单位) DWORD dwYCountChars; DWORD dwFillAttribute; //用于设定子应用程序的控制台窗口使用的文本和背景颜色 DWORD dwFlags; //请参见下一段和表4 - 7 的说明 WORD wShowWindow; //用于设定如果子应用程序初次调用的ShowWindow 将SW_SHOWDEFAULT 作为 nCmdShow 参数传递时,该应用程序的第一个重叠窗口应该如何出现。本成员可以是
通常用于ShowWindow 函数的任何一个SW_*标识符 WORD cbReserved2; //保留。必须被初始化为0 PBYTE lpReserved2; //保留。必须被初始化为N U L L HANDLE hStdInput; //用于设定供控制台输入和输出用的缓存的句柄。按照默认设置,hStdInput 用于标识键盘缓存,hStdOutput 和hStdError用于标识控制台窗口的缓存 HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO;
PROCESS_INFORMATION结构体:
typedef struct _PROCESS_INFORMATTON{ HANDLE hProcess; //新进程句柄 HANDLE hThread; //新线程句柄 DWORD dwProcessId; //全局进程标识符 DWORD dwThreadId; //全局线程标识符 }PROCESS_INFORMATION;
匿名管道配置: 父子进程,父进程中创建匿名管道,然后创建子进程,将匿名管道和子进程链接。(父子进程同级目录)
父进程实现: 创建匿名管道>>启动子进程>>写入读取数据
创建匿名管道: 定义读写句柄 >> 定义安全结构体SECURITY_ATTRIBUTES >>设置子进程继承父进程句柄 >>安全描述符为NULL >>长度sizeof()结构体SECURITY_ATTRIBUTES的长度 >>创建匿名管道
启动子进程: 创建匿名管道 >>定义窗口显示结构体STARTUPINFO >>设置所有变量为0,ZeroMemory() >>长度.cb赋值sizeof() >>标识成员.dwFlags赋值STARTF_USESTDHANDLES标准输入输出和错误
>>输入句柄输出句柄设为匿名管道的输入输出句柄(子进程继承父进程所有可继承句柄后需要进行特殊设置才能区分读写句柄) >>通过GetStdHandle(STD_ERROR_HANDLE) 获取错误句柄 >>定义新进程标识结构体 >>创建子进程
写入数据: 创建字符数组存放数据>>创建字变量存放字节数>>通过ReadFile函数读取管道数据>>成功返回1,失败返回0
读取数据: 创建字符数组写入数据>>创建字变量存放字节数>>通过WriteFile函数写入管道数>>成功返回1,失败返回0
子进程实现: 创建匿名管道>>写入读取数据
创建匿名管道: 获取输入输出句柄(管道创建由父进程进行)
写入读取同上;
方法三:通过命名管道实现通信
同时实现本机通信以及跨网络通信的网络编程方案,围绕Windows文件系统的设计机制,采用“命名管道文件系统”接口实现。客户机和服务器采用标准的Win32文件系统函数进行数据收发。
服务器和客户机区别: 服务器能够创建命名管道,接收管道客户机的连接请求;客户机只能在现有命名管道服务器上连接。
基本通信模式: 字节模式:连续字节流的形式在客户机和服务器间流动
消息模式:一系列不连续的数据单位进行收发,每次发出一条消息必须作为完整消息读入
创建命名管道函数:
HANDLE CreateNamedPipe( LPCTSTR lpName, // \\.\pipe\pipename 双反斜杠开头;.表示本地、远程服务器则指定服务器名;固定字符串;命名管道名 c编码中通过\\表示\ DWORD dwOpenMode, // 指定访问方式、重叠方式、写直通方式、句柄安全访问方式 DWORD dwPipeMode, // 指定管道句柄类型、读取和等待方式(同一管道所有实例均为相同类型) DWORD nMaxInstances, // 最大实例数 DWORD nOutBufferSize, // 指定输出缓存区保留字节数 DWORD nInBufferSize, // 指定输入缓存区保留字节数 DWORD nDefaultTimeOut, // 默认超时值 LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD );
服务器程序: 声明句柄用于保存管道实例>创建命名管道>保存返回句柄,判断是否创建成功>声明OVERLAPPED结构指针,仅设置hEvent值>等待客户端请求,判断返回值>返回0时判断是否未决
>未决操作进行等待>判断等待结果>连接成功,关闭句柄>读写数据(方式同匿名管道)
等待客户端请求: BOOLWINAPIConnectNamedPipe(服务器创建命名管道句柄,OVERLAPPED结构指针);
判断是否未决操作: GetLastError(),返回值不为ERROR_IO_PENDING时为等待接失败(非未决操作)
等待事件对象变为有信号: WaitForSingleObject(管道句柄,等待时间) 返回值为WAIT_FAILED时为等待失败
客户端程序: 声明句柄用于保存管道实例>等待可利用管道创建>打开命名管道>读写数据
等待可利用管道创建: BOOL WaitNamedPipe(\\.\pipe\pipename,超时值)
打开命名管道: CreateFile(\\.\pipe\pipename,访问方式,共享?(0),安全(NULL),创建标记,文件属性,模板(NULL))
方法四:通过邮槽实现通信
基于广播通信体系设计,采用无连接不可靠的数据传输,一种单向通信机制,服务器创建读取,客户机打开写入,长度限制424字节以下
创建邮槽:
HANDLE CreateMailslos( LPCTSTR lpName, // \\.\mailslot\[path]name 同命名管道 DWORD nMaxMessageSize, // 最大单一消息字节 DWORD lReadTimeout, // 超时 LPSECURITY_ATTURIBUTES lpSecurityAttributes // SD )
服务器程序: 定义句柄>创建邮槽>返回值判断>读取数据>关闭邮槽
客户机程序: 定义句柄>打开邮槽(CreateFile)>返回值判断>写入数据>关闭邮槽
方法总结:
剪贴板和匿名管道只能实现本地通信
命名管道和邮槽能实现网络通信
命名管道只能点对点通信但数据量大
邮槽能一对多通信,但数据量小