进程间通信最简单的方式就是发送WM_COPYDATA消息。本文提供C++及C#程序相互通信的二种实现方式。这样消息的接收端可以用C++实现,发送端可以用C++或C#实现。
发送WM_COPYDATA消息:
SendMessage(接收窗口句柄, WM_COPYDATA, (WPARAM)发送窗口句柄, (LPARAM)&CopyData);
其中的CopyData为COPYDATASTRUCT结构类型,该结构定义如下:
typedef struct tagCOPYDATASTRUCT {
DWORD dwData; // Specifies data to be passed to the receiving application.
DWORD cbData; //Specifies the size, in bytes, of the data pointed to by the lpData member.
PVOID lpData; // Pointer to data to be passed to the receiving application. can be NULL.
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
注意:该消息只能由SendMessage()来发送,而不能使用PostMessage()。因为系统必须管理用以传递数据的缓冲区的生命期,如果使用了PostMessage(),数据缓冲区会在接收方(线程)有机会处理该数据之前,就被系统清除和回收。此外如果lpData指向一个带有指针或某一拥有虚函数的对象时,也要小心处理。
如果传入的句柄不是一个有效的窗口或当接收方进程意外终止时,SendMessage()会立即返回,因此发送方在这种情况下不会陷入一个无穷的等待状态中。
返回值问题,MSDN上说如果接收方处理了,返回TRUE,否则返回FALSE,但是本人在实验时,都是返回0(接收方已经处理)。
接收WM_COPYDATA消息:
只要用COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;就可以了。接收方应认为这些数据是只读的。
由于发送方在接收方处理WM_COPYDATA消息完毕前都是处于等待中,所以接收方应当尽快处理WM_COPYDATA消息。
以一个简单的例子来说明如何使用WM_COPYDATA消息,有二个程序,一个用来发送表示当前时间信息的字符串,另一个接收数据后显示到编辑框中。例子中有几点要注意:
1.如何得到当前控制台窗口句柄?VS2008下可以直接使用HWND GetConsoleWindow(void);函数。
2.使用char *ctime(const time_t *timer);将一个time_t类型转化成一个字符串时,函数会在字符串末尾加下'\n',因为发送前要将这个'\n'去掉。
发送消息的程序代码(VS2008下编译通过):
- #include <windows.h>
- #include <time.h>
- #include <conio.h>
- #include <stdio.h>
- int main()
- {
- const char szDlgTitle[] = "RecvMessage";
- HWND hSendWindow = GetConsoleWindow ();
- if (hSendWindow == NULL)
- return -1;
- HWND hRecvWindow = FindWindow(NULL, szDlgTitle);
- if (hRecvWindow == NULL)
- return -1;
- char szSendBuf[100];
- time_t timenow;
- COPYDATASTRUCT CopyData;
- for (int i = 0; i < 10; i++)
- {
- time(&timenow);
- sprintf(szSendBuf, "%s", ctime(&timenow));//注意,ctime()返回的字符串后面带了'\n'
- CopyData.dwData = i;
- CopyData.cbData = strlen(szSendBuf);
- szSendBuf[CopyData.cbData - 1] = '\0';
- CopyData.lpData = szSendBuf;
- SendMessage(hRecvWindow, WM_COPYDATA, (WPARAM)hSendWindow, (LPARAM)&CopyData);
- printf("%s\n", szSendBuf);
- Sleep(1000);
- }
- return 0;
- }
接收消息程序代码(VC6.0下编译通过):
程序中的IDC_EDIT_RECVMESSAGE为编辑框的ID。
- #include "stdafx.h"
- #include "resource.h"
- #include <stdio.h>
- BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
- int APIENTRY WinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPSTR lpCmdLine,
- int nCmdShow)
- {
- // TODO: Place code here.
- DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
- return 0;
- }
- BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
- {
- const char szDlgTitle[] = "RecvMessage";
- static HWND s_hEditShowRecv;
- switch (message)
- {
- case WM_INITDIALOG:
- SetWindowText(hDlg, szDlgTitle);
- s_hEditShowRecv = GetDlgItem(hDlg, IDC_EDIT_RECVMESSAGE);
- return TRUE;
- case WM_COMMAND:
- switch (LOWORD(wParam))
- {
- case IDOK:
- case IDCANCEL:
- EndDialog(hDlg, LOWORD(wParam));
- return TRUE;
- }
- break;
- case WM_COPYDATA:
- {
- COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;
- char szBuffer[300];
- memset(szBuffer, 0, sizeof(szBuffer));
- sprintf(szBuffer, "dwData:%d cbData:%d\r\nlpData:0x%08x = %s\r\n\r\n",
- pCopyData->dwData, pCopyData->cbData,
- (PVOID)pCopyData->lpData, (char*)pCopyData->lpData);
- //在编辑框中追加数据
- SendMessage(s_hEditShowRecv, EM_SETSEL, (WPARAM)-1, (LPARAM)-1); // (0, -1)表示全选, (-1,任意)表示全不选
- SendMessage(s_hEditShowRecv, EM_REPLACESEL, FALSE, (LPARAM)szBuffer);
- SendMessage(s_hEditShowRecv, EM_SCROLLCARET, 0, 0);
- }
- return TRUE;
- }
- return FALSE;
- }
运行结果如下 (先启动接收消息程序再运行发送消息程序):
有的时候,发送消息程序用C#实现起来更加方便,因此在这也提供了用C#实现的例子发送消息程序供大家参考:
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Threading;
- using System.Runtime.InteropServices; //[DllImport("user32.dll")]中DllImport的命名空间
- namespace UseWMCOPYDATA
- {
- class Program
- {
- static void Main(string[] args)
- {
- string strDlgTitle = "RecvMessage";
- //接收端的窗口句柄
- IntPtr hwndRecvWindow = ImportFromDLL.FindWindow(null, strDlgTitle);
- if (hwndRecvWindow == IntPtr.Zero)
- {
- Console.WriteLine("请先启动接收消息程序");
- return;
- }
- //自己的窗口句柄
- IntPtr hwndSendWindow = ImportFromDLL.GetConsoleWindow();
- if (hwndSendWindow == IntPtr.Zero)
- {
- Console.WriteLine("获取自己的窗口句柄失败,请重试");
- return;
- }
- for (int i = 0; i < 10; i++)
- {
- string strText = DateTime.Now.ToString();
- //填充COPYDATA结构
- ImportFromDLL.COPYDATASTRUCT copydata = new ImportFromDLL.COPYDATASTRUCT();
- copydata.cbData = Encoding.Default.GetBytes(strText).Length; //长度 注意不要用strText.Length;
- copydata.lpData = strText; //内容
- ImportFromDLL.SendMessage(hwndRecvWindow, ImportFromDLL.WM_COPYDATA, hwndSendWindow, ref copydata);
- Console.WriteLine(strText);
- Thread.Sleep(1000);
- }
- }
- }
- public class ImportFromDLL
- {
- public const int WM_COPYDATA = 0x004A;
- //启用非托管代码
- [StructLayout(LayoutKind.Sequential)]
- public struct COPYDATASTRUCT
- {
- public int dwData; //not used
- public int cbData; //长度
- [MarshalAs(UnmanagedType.LPStr)]
- public string lpData;
- }
- [DllImport("User32.dll")]
- public static extern int SendMessage(
- IntPtr hWnd, // handle to destination window
- int Msg, // message
- IntPtr wParam, // first message parameter
- ref COPYDATASTRUCT pcd // second message parameter
- );
- [DllImport("User32.dll", EntryPoint = "FindWindow")]
- public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
- [DllImport("Kernel32.dll", EntryPoint = "GetConsoleWindow")]
- public static extern IntPtr GetConsoleWindow();
- }
- }
运行结果如下 (先启动接收消息程序再运行发送消息程序):
下一篇《进程通信之二 管道技术第一篇 输入输出的重定向》示范了程序输入输出的重定向,以及如何用管道来完成进程之间的通信。
继上一篇《进程通信之一使用WM_COPYDATA C++及C#实现》,进程通信之二将分为三篇文章讲解如何使用管道技术来完成进程通信功能。三篇文章目录如下:
本篇将介绍输入输出的重定向问题,先来看一个小小的实例,设有一个程序,该程序的输入输出为标准输入输出即从键盘上输入,输出到屏幕。现在要重定向输入法输出,使程序从文件中读取数据,处理后输出到文件。程序代码如下(称此程序为示例程序):
- #include <stdio.h>
- int main()
- {
- int n;
- while (scanf("%d", &n) != EOF) //标准输入时,可按ctrl+z来输入EOF
- {
- n *= 2;
- printf("%d\n", n);
- }
- }
试给出几种不同的实现方法,另外如果没有程序代码,只有可执行文件,又应该如何做了?
实现方法一使用C语言的freopen()函数
函数功能:重定向控制台的输入输出
函数原型:
FILE *freopen(
const char *path,
const char *mode,
FILE *stream
);
函数说明:
第一个参数为文件指针(也可以用来指向标准输入输出)。
第二个参数为打开方式,"w"表示写,"r"表示读,"a"表示追加。其它设置可以参考MSDN。
第三个参数为FILE类型的指针,传入stdin表示标准输入,传入stdout表示标准输出。
这样用只要用简单一句freopen("infile.txt", "r", stdin);就可以使程序中的scanf()函数从文件中读取数据作为输入,同样freopen("outfile.txt", "w", stdout);可以使程序中的printf()函数将输出由标准输出改成输出到文件。现在有个问题,将程序的输入输出重定向到文件后,还能改回到标准输入输出吗?答案是可以的,对第一个参数传入"CON"这个字符串就可以了(linux为"/dev/console")。
下面就给出修改后的代码:
- //直接使用freopen()函数 来改变控制台的标准输入输出
- #include <stdio.h>
- int main()
- {
- //将控制台的标准输入输出改成从文件中读取写入
- FILE *pFileRead = freopen("infile.txt", "r", stdin);
- FILE *pFileWrite = freopen("outfile.txt", "w", stdout);
- int n;
- while (scanf("%d", &n) != EOF)
- {
- n *= 2;
- printf("%d\n", n);
- }
- fclose(pFileRead);
- fclose(pFileWrite);
- //回到到控制台的标准输入输出 windows为"CON" linux为"/dev/console"
- freopen("CON", "r", stdin);
- freopen("CON", "w", stdout);
- printf("Finish 输入0表示结束:\n");
- do{
- scanf("%d", &n);
- }while (n != 0);
- return 0;
- }
运行结果如下图所示:
可以看出该程序的输出输入已经完成了重定向。
实现方法2 使用C++的ifstream和ofstream类
有些场合使用类来完成输入输出的重定向任务会更加方便和习惯一些。所幸C++中就有ifstream和ofstream这二个类来帮助我们完成这一任务。这个二类的详细功能就不细说了。下面介绍下如何使用这二个类来重定向程序的输入输出(看的时候看慢点喔,不会会被很多类名给搞晕去^_^)。
这二个类可以以读的方式和写的方法打开一个文件(ifstream的首字母i就表示in,而ofstream的首字母o就表示out),在iosfwd文件中找到:
typedef basic_ifstream<char, char_traits<char> > ifstream;
typedef basic_ofstream<char, char_traits<char> > ofstream;
然后可以在fstream文件中找到basic_ifstream类是继承于basic_istream类,basic_ofstream类是继承于basic_ostream类。
然后再来看看C++中大家平常使用的cin和cout,可以在iostream文件中找到cin和cout的定义,这二个实际是istream类型和ostream类型的变量:
extern _CRTIMPistream cin;
extern _CRTIMPostream cout;
然后在iosfwd文件中可以找到:
typedef basic_istream<char, char_traits<char> > istream;
typedef basic_ostream<char, char_traits<char> > ostream;
明显cin和cout与ifstream类和ofstream类有着非常密切关系——cin是basic_istream类的变量,而ifstream则是basic_istream类的派生类。cout是basic_ostream类的变量,而ofstream则是basic_ostream类的派生类。
有了这个后,猜测很可能会有某个成员函数能将它们联系到一起,从而让cin和cout由标准输入输出重定向到从文件中读取,输出到文件。事实上在basic_istream类实际是虚继承于basic_ioso类,basic_ostream类实际是虚继承于basic_ios类。这二个类都有个rdbuf()成员函数,这个函数允许我们访问和修改类中一个类型为basic_streambuf类的成员变量。改动这个变量就能重定向输入输出。因此对cin和cout调用这个rdbuf()函数并传入ifstream和ofstream的rdbuf()就可以将控制台的标准输入输出改成从文件中读取和输出到文件。
OK,方法既然找到了,那下面就使用C++的方法来重定向输入输出:
- //使用ifstream和ofstream及cin和cout的rdbuf()
- #include <iostream>
- #include <fstream>
- using namespace std;
- int main()
- {
- printf(" 使用ifstream和ofstream及cin和cout的rdbuf()来改变控制台的标准输入输出\n");
- printf(" --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");
- //将控制台的标准输入输出改成从文件中读取写入
- ifstream inFile("infile.txt");
- ofstream outFile("outfile.txt");
- //保存原来的输入输出方式 streambuf类就是basic_streambuf类
- streambuf *strmin_buf = cin.rdbuf();
- streambuf *strmout_buf = cout.rdbuf();
- printf("开始处理文件\n....\n");
- //重定向到文件
- cin.rdbuf(inFile.rdbuf());
- cout.rdbuf(outFile.rdbuf());
- //原程序代码
- int n;
- while (cin>>n)
- {
- n *= 2;
- cout<<n<<endl;
- }
- inFile.close();
- outFile.close();
- //回到控制台的标准输入输出
- cin.rdbuf(strmin_buf);
- cout.rdbuf(strmout_buf);
- cout<<"文件已经处理完毕 输入0表示结束:"<<endl;
- do{
- cin>>n;
- }while (n != 0);
- return 0;
- }
运行结果如下图所示:
同样,这个程序也完成了输入输出的重定向。
上面的方法都是建立在修改源代码的基础上,如果只有程序文件即.exe文件那应该怎么做了?请参阅下一篇《进程通信之二 管道技术第二篇 匿名管道》
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7390350
如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。
上一篇《进程通信之二 管道技术第一篇 输入输出的重定向》示范了增加若干程序代码来完成程序输入输出的重定向,并提出了如果没有程序源代码,只有程序文件如何来完成重定向。本篇就介绍如何使用匿名管道来完成这一任务。
计算机中管道pipe类似于现实世界中的水管道,在一端放入水流,另一端就会流出来。在计算机机中水流自然被数据流所代替了。计算机中管道分为匿名管道和命名管道,本篇将主要介绍用匿名管道来完成这一重定向输出任务,命名管道就留给下一篇来介绍了。
先来看看如何创建和使用匿名管道。
第一个 CreatePipe
函数功能:创建管道
函数原型:
BOOLWINAPICreatePipe(
PHANDLEhReadPipe,
PHANDLEhWritePipe,
LPSECURITY_ATTRIBUTESlpPipeAttributes,
DWORDnSize
);
函数说明:
第一个参数返回新创建的管道的读取端句柄。
第二个参数返回新创建的管道的写入端句柄。
注意不能在管道的读取端写入数据也不能在写入端读取数据。
第三个参数表示管道的安全属性,通常可以作如下设置:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
第四个参数表示管道的缓冲区容量,为0表示使用默认大小。
函数执行成功返回TRUE,否则返回FALSE。
第二个 ReadFile
函数功能:从管道中读取数据
函数原型:
BOOLReadFile(
HANDLEhFile,
LPVOIDlpBuffer,
DWORDnNumberOfBytesToRead,
LPDWORDlpNumberOfBytesRead,
LPOVERLAPPEDlpOverlapped
);
函数说明:
第一个参数为句柄,可以是创建文件函数CreateFile()的返回值也可以是管道。
第二个参数是一个指向缓冲区的指针,函数将读取的数据写入该缓冲区。
第三个参数的表达非常好,光从名字上就可以知道这是用来指定读取的字节数。
第四个参数将返回实际读取到的字节数。
第五个参数是用于异步操作方面,一般传入NULL即可。
第三个 WriteFile
函数功能:向管道写入数据
函数原型:
BOOLWriteFile(
HANDLEhFile,
LPCVOIDlpBuffer,
DWORDnNumberOfBytesToWrite,
LPDWORDlpNumberOfBytesWritten,
LPOVERLAPPEDlpOverlapped
);
函数说明:
第一个参数为句柄,可以是创建文件函数CreateFile()的返回值也可以是管道。
第二个参数是一个指针,该指针指向待写入管道的数据。
第三个参数表示要写入的字节数。
第四个参数将返回实际写入管道的字节数。
第五个参数是用于异步操作方面,一般传入NULL即可。
第四个CloseHandle
函数功能:关闭管道的一端
函数原型:BOOLCloseHandle(HANDLEhObject);
函数说明:当读取和写入端都关闭后,系统会关闭管道并回收资源。
从后面三个函数可以看出,向管道中读取和写入数据就和向文件中读取和写入数据是一样的(事实上管道也是一种特殊的文件——内存映射文件)。
使用管道要注意的一个地方是:读取和写入数据时,一定要注意顺序,MSDN上说,如果管道中没有数据,调用ReadFile()会造成阻塞,直到有其它线程将数据写入管道。同样,当有线程正在管道中读取数据时,其它试图将数据写入管道的的线程也会被阻塞。
因此对上一篇的示例程序进行重定向时,可以先创建二个管道,一个用来存放输入数据,称为数据输入管道,另一个用来存放输出数据,称为数据输出管道。然后从输入文件中读取数据并写入数据输入管道。再启动示例程序作为子进程,子进程的输入输出已经改成从数据输入管道中读取和输出到数据输出管道。子进程运行结束后,从数据输出管道中将数据写入到输出文件即可。整个流程图如下所示:
下面给出使用管道的示例代码:
- //用管道来完成子进程的重定向。
- //流程如下:
- // infile.txt -> Input管道 -> 标准程序.exe -> Output管道 -> outfile.txt
- #include <windows.h>
- #include <stdio.h>
- int main()
- {
- printf(" 使用管道来重定向子进程的输入输出\n");
- printf(" --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");
- char sz[3][50] = {"示例程序.exe", "infile.txt", "outfile.txt"};
- HANDLE hPipeInputRead, hPipeInputWrite, hPipeOutputRead, hPipeOutputWrite;
- //创建两个管道
- SECURITY_ATTRIBUTES sa;
- sa.nLength = sizeof(SECURITY_ATTRIBUTES);
- sa.lpSecurityDescriptor = NULL;
- sa.bInheritHandle = TRUE;
- //数据输入管道
- CreatePipe(&hPipeInputRead, &hPipeInputWrite, &sa, 0);
- //数据输出管道
- CreatePipe(&hPipeOutputRead, &hPipeOutputWrite, &sa, 0);
- printf("创建数据输入管道和数据输出管道完毕\n");
- //从文件中读取数据,写入管道ReadFile中.
- const int BUFSIZE = 4096;
- CHAR chBuf[BUFSIZE] = {0};
- DWORD dwRead, dwWritten;
- BOOL fSuccess;
- HANDLE hInputFile = CreateFile(sz[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
- while (true)
- {
- //从文件中读取数据
- fSuccess = ReadFile(hInputFile, chBuf, BUFSIZE, &dwRead, NULL);
- if (!fSuccess || dwRead == 0)
- break;
- //将数据写入管道
- fSuccess = WriteFile(hPipeInputWrite, chBuf, dwRead, &dwWritten, NULL);
- if (!fSuccess)
- break;
- }
- //关闭输入数据管道
- CloseHandle(hInputFile);
- hInputFile = NULL;
- CloseHandle(hPipeInputWrite);
- hPipeInputWrite = NULL;
- printf("已经从文件中读取数据并写入数据输入管道\n");
- printf("启动示例程序并重定向到管道中\n....\n");
- //启动示例程序作为子进程
- STARTUPINFO si;
- si.cb = sizeof(STARTUPINFO);
- GetStartupInfo(&si);
- si.hStdInput = hPipeInputRead; //输入由标准输入 -> 从管道中读取
- si.hStdOutput = hPipeOutputWrite; //输出由标准输出 -> 输出到管道
- si.wShowWindow = SW_HIDE;
- si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
- PROCESS_INFORMATION pi;
- CreateProcess( sz[0], NULL, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);
- WaitForSingleObject(pi.hProcess, INFINITE);
- //关闭输入数据管道
- CloseHandle(hPipeInputRead);
- hPipeInputRead = NULL;
- CloseHandle(hPipeOutputWrite);
- hPipeOutputWrite = NULL;
- printf("示例程序完成处理,现在将数据输出管道中的数据写入文件\n");
- //将输出数据管道中的数据写入到文件中
- HANDLE hOutputFile = CreateFile(sz[2], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
- while (true)
- {
- //从管道中读取
- fSuccess = ReadFile(hPipeOutputRead, chBuf, BUFSIZE, &dwRead, NULL);
- if( !fSuccess || dwRead == 0)
- break;
- //写入输出文件
- fSuccess = WriteFile(hOutputFile, chBuf, dwRead, &dwWritten, NULL);
- if (!fSuccess)
- break;
- }
- //关闭输出数据管道
- CloseHandle(hOutputFile);
- hOutputFile = NULL;
- CloseHandle(hPipeOutputRead);
- hPipeOutputRead = NULL;
- printf("数据输出管道中的数据写入文件完毕\n");
- return 0;
- }
运行结果如下图:
结果完全正确,说明我们的程序已经完成了启动其它程序并对它进行重定向这一功能。
对匿名管道总结一下:匿名管道有读取端和写入端。匿名管道创建(CreatePipe)后就可以像读写文件一样的对管道中的数据读写(ReadFile与WriteFile),但要注意读写顺序。匿名管道在关闭两端后会由系统销毁并回收资源。
匿名管道的使用比较常见,下面是二个安装程序的截图。
QQ游戏的安装过程截图:
五笔编码及时查的安装过程截图:
对比下这二个截图,显示的内容都差不多,都是解压缩文件并移动到指定地方。唯一不同的是一个是控制台界面,另一个是图形界面。联想上面的程序,不难得知QQ游戏的安装实际也是使用管道将一个控制台程序的输出内容显示到图形界面,这样既美观又便于维护。
下一篇《进程通信之二 管道技术第三篇 命名管道》将介绍命名管道的使用,欢迎参阅。
注:不知道程序代码的情况下还可以使用批处理来完成。批处理使用>和<来重定向,>为输出到文件,如果文件不存在就创建,已存在就清空原文件后再写入,<为从文件读取。批处理文件的内容可以这样写:
@echo off
<infile.txt 标准程序.exe >outfile.txt
也可以这样写:
@echo off
标准程序.exe <infile.txt >outfile.txt
批处理重定向的内部实现原理当然也是使用匿名管道。
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7390441
如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。
进程通信之二 管道技术第三篇 命名管道
上一篇《进程通信之二管道技术第二篇匿名管道》中讲解了匿名管道,匿名管道有读取端和写入端,在创建匿名管道(CreatePipe)后就可以像读写文件一样的对管道中进行读写(ReadFile与WriteFile,注意读写顺序)。在关闭匿名管道两端后会由系统负责销毁并回收资源。文章中还示范了父进程如何使用匿名管道来改变子进程的输入输出。
本篇将讲解管道技术中的命名管道(Named Pipes),顾名思义,这个管道肯定是有名字的,联想到秒杀多线程面试题中的事件、互斥量、信号量(见附1),它们的名字主要是用于确保多个进程访问同一个对象。因此肯定也可以通过管道的名字来确保多个进程访问同一个管道。事实上,命名管道不仅可在同一台计算机的不同进程之间传输数据,甚至能在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。
先来看看如何创建和使用命名管道。
第一个CreateNamedPipe
函数功能:创建命名管道
函数原型:
HANDLEWINAPICreateNamedPipe(
LPCTSTRlpName,
DWORDdwOpenMode,
DWORDdwPipeMode,
DWORDnMaxInstances,
DWORDnOutBufferSize,
DWORDnInBufferSize,
DWORDnDefaultTimeOut,
LPSECURITY_ATTRIBUTESlpSecurityAttributes
);
参数说明:
第一个参数LPCTSTRlpName
表示管道名称,采用的形式是:\\.\pipe\pipename。最多可达256个字符的长度,而且不区分大小写。如果已经有同名管道,则会创建那个管道的一个新实例。
第二个参数DWORDdwOpenMode
表示管道的打开方式。下面列出最常用的三种,更多请参阅MSDN。
1.PIPE_ACCESS_DUPLEX
该管道是双向的,服务器和客户端进程都可以从管道读取或者向管道写入数据。
2.PIPE_ACCESS_INBOUND
该管道中数据是从客户端流向服务端,即客户端只能写,服务端只能读。
3.PIPE_ACCESS_OUTBOUND
该管道中数据是从服务端流向客户端,即客户端只能读,服务端只能写。
第三个参数DWORDdwPipeMode
表示管道的模式,下面是一些常用模式介绍,更多请参阅MSDN。
1.PIPE_TYPE_BYTE
数据作为一个连续的字节数据流写入管道。
2.PIPE_TYPE_MESSAGE
数据用数据块(名为“消息”或“报文”)的形式写入管道。
3.PIPE_READMODE_BYTE
数据以单独字节的形式从管道中读出。
4.PIPE_READMODE_MESSAGE
数据以名为“消息”的数据块形式从管道中读出(要求指定PIPE_TYPE_MESSAGE)。
5.PIPE_WAIT
同步操作在等待的时候挂起线程。
6.PIPE_NOWAIT
同步操作立即返回。
第四个参数DWORDnMaxInstances
表示该管道所能够创建的最大实例数量。必须是1到常数PIPE_UNLIMITED_INSTANCES间的一个值。
在WINBASE.H中有#define PIPE_UNLIMITED_INSTANCES 255
第五个参数DWORDnOutBufferSize
表示管道的输出缓冲区容量,为0表示使用默认大小。
第六个参数DWORDnInBufferSize
表示管道的输入缓冲区容量,为0表示使用默认大小。
第七个参数DWORDnDefaultTimeOut
表示管道的默认等待超时。
第八个参数LPSECURITY_ATTRIBUTESlpSecurityAttributes
表示管道的安全属性。
函数返回值:
函数执行成功返回命名管道的句柄,否则返回INVALID_HANDLE_VALUE。
第二个ConnectNamedPipe
函数功能:等待客户端连接命名管道
函数原型:
BOOLWINAPIConnectNamedPipe(
HANDLEhNamedPipe,
LPOVERLAPPEDlpOverlapped
);
函数说明:
第一个参数表示命名管道的句柄。
第二个参数是一个指向OVERLAPPED结构的指针,一般置为NULL就可以了。
第三个WaitNamedPipe
函数功能:客户端连接命名管道
函数原型:
BOOLWINAPIWaitNamedPipe(
LPCTSTRlpNamedPipeName,
DWORDnTimeOut
);
函数说明:
第一个参数LPCTSTRlpNamedPipeName
表示管道名称,采用的形式是:\\servername\pipe\pipename。如果是本机管道,servername用“.”来表示。
第二个参数DWORDnTimeOut
表示等待命名管道的一个实例有效的超时时间,单位毫秒。也可以用NMPWAIT_USE_DEFAULT_WAIT表示使用命名管道的设定值(在调用CreateNamedPipe创建命名管道时指定的),NMPWAIT_WAIT_FOREVER表示无限等待。
函数返回值:
在指定时间内连接成功返回TRUE,否则返回FALSE。
注意
1:如果指定名称的命名管道还没创建,函数立即返回,返回值为FALSE。
2:如果函数执行成功返回TRUE,表示至少有一个命名管道的实例有效,接下来应该使用CreateFile函数打开命名管道的一个句柄,但是CreateFile可能会打开管道失败,因为该实例有可能被服务端关闭或被已经被其他客户端打开。
下面给出使用命名管道的实例,该实例分为命名管道的服务端和客户端。服务端和客户端的主要步骤如下所示:
1. 服务端用CreateNamedPipe创建一个命名管道并使用ConnectNamedPipe等待客户端的连接。
2. 客户端使用WaitNamedPipe连接成功后,用CreateFile打开管道并使用WriteFile向管道中写入一段数据(即向服务端发送消息)。
3. 服务端使用ReadFile从管道中读取数据后(即收到消息)再向管道中写入确认信息表明已经收到客户端传输的数据(即通知客户端已收到)。
4. 客户端收到确认信息后结束,调用CloseHandle关闭管道(该管道是CreateFile打开的)。
5.服务端使用DisconnectNamedPipe和CloseHandle关闭管道。
代码中的CreateFile,WriteFile,ReadFile请参见上一篇《进程通信之二管道技术第二篇匿名管道》中的介绍。
服务端代码如下:
- #include <stdio.h>
- #include <windows.h>
- #include <conio.h>
- const char *pStrPipeName = "\\\\.\\pipe\\NamePipe_MoreWindows";
- int main()
- {
- printf(" 命名管道 服务器\n");
- printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
- printf("创建命名管道并等待连接\n");
- HANDLE hPipe = CreateNamedPipe(pStrPipeName, PIPE_ACCESS_DUPLEX,
- PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
- PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, 0);
- if (ConnectNamedPipe(hPipe, NULL) != NULL)//等待连接。
- {
- printf("连接成功,开始接收数据\n");
- const int BUFFER_MAX_LEN = 256;
- char szBuffer[BUFFER_MAX_LEN];
- DWORD dwLen;
- //接收客户端发送的数据
- ReadFile(hPipe, szBuffer, BUFFER_MAX_LEN, &dwLen, NULL);//读取管道中的内容(管道是一种特殊的文件)
- printf("接收到数据长度为%d字节\n", dwLen);
- printf("具体数据内容如下:%s\n", szBuffer);
- //确认已收到数据
- printf("向客户端发送已经收到标志\n");
- strcpy(szBuffer, "服务器已经收到");
- WriteFile(hPipe, szBuffer, strlen(szBuffer) + 1, &dwLen, NULL);
- }
- DisconnectNamedPipe(hPipe);
- CloseHandle(hPipe);//关闭管道
- return 0;
- }
客户端代码如下:
- #include <stdio.h>
- #include <windows.h>
- #include <conio.h>
- const char *pStrPipeName = "\\\\.\\pipe\\NamePipe_MoreWindows";
- int main()
- {
- printf(" 命名管道 客户端\n");
- printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
- printf("按任意键以开始连接命名管道\n");
- getch();
- printf("开始等待命名管道\n");
- if (WaitNamedPipe(pStrPipeName, NMPWAIT_WAIT_FOREVER) == FALSE)
- {
- printf("Error! 连接命名管道失败\n");
- return 0;
- }
- printf("打开命名管道\n");
- HANDLE hPipe = CreateFile(pStrPipeName, GENERIC_READ | GENERIC_WRITE, 0,
- NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
- printf("向服务端发送数据\n");
- const int BUFFER_MAX_LEN = 256;
- char szBuffer[BUFFER_MAX_LEN];
- DWORD dwLen = 0;
- //向服务端发送数据
- sprintf(szBuffer,"进程%d说\"%s\"", GetCurrentProcessId(), "Hello World!");
- WriteFile(hPipe, szBuffer, strlen(szBuffer) + 1, &dwLen, NULL);
- printf("数据写入完毕共%d字节\n", dwLen);
- //接收服务端发回的数据
- ReadFile(hPipe, szBuffer, BUFFER_MAX_LEN, &dwLen, NULL);//读取管道中的内容(管道是一种特殊的文件)
- printf("接收服务端发来的确认信息长度为%d字节\n", dwLen);
- printf("具体数据内容如下:%s\n", szBuffer);
- CloseHandle(hPipe);
- return 0;
- }
运行结果如下所示,运行时先启动服务器,然后再运行客户端:
命名管道就先介绍到这里了,管道技术上、中、下三篇到此也就全部结束下,下面给出目录,方便大家查看。
后面将有文章介绍进程通信中最底层,最高效的方法——共享内存。欢迎继续浏览。
附1 其实进程线程同步除了使用秒杀多线程面试题系列中的介绍的关键段、事件、互斥量、信号量、读写锁。管道也可以用于线程的同步。
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8260087
Windows编程系列文章地址:http://blog.csdn.net/morewindows/article/category/862060
欢迎关注新浪微博:http://weibo.com/MoreWindows
---------------------------------------------------华丽的分割线-----------------------------------------------------------------
CSDN博客之星评选活动正在进行,觉得本博客对您有帮助,麻烦投我一票,非常感谢。您的支持就是我写作的最大动力。
投票地址:http://vote.blog.csdn.net/item/blogstar/MoreWindows
谢谢大家!
本文配套程序下载地址为:http://download.csdn.net/detail/morewindows/5165733
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8646902
欢迎关注微博:http://weibo.com/MoreWindows
前面已经写了使用WM_COPYDATA消息来完成进程之间的通信
1.《进程通信之一使用WM_COPYDATA C++及C#实现》
http://blog.csdn.net/morewindows/article/details/6804157
然后用了三篇文章来讲解如何使用管道技术来完成进程通信功能。
http://blog.csdn.net/morewindows/article/details/7390350
http://blog.csdn.net/morewindows/article/details/7390441
http://blog.csdn.net/morewindows/article/details/8260087
本篇介绍一个简单而又实用的进程通信方式——父进程向子进程传入参数并获取子进程返回值。这个非常简单:
1.父进程向子进程传入参数可以由CreateProcess()函数来完成,注意子进程是通过GetCommandLine()来获取这个参数而且不是char *argv[]。
2.父进程要获取子进程的返回值可以在等待子进程结束后通过GetExitCodeProcess并传入子进程句柄来获取子进程中main或WinMain函数的返回值。
下面就结出实例代码,首先来看子进程的程序代码:
- //进程通信之三 父进程传参数与子进程返回值
- //http://blog.csdn.net/morewindows/article/details/8683830
- //By MoreWindows( http://blog.csdn.net/MoreWindows )
- #include <windows.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <time.h>
- #include <string.h>
- int main(int argc, char *argv[])
- {
- // 通过GetCommandLine()获取父进程通过CreateProcess传给子进程的参数
- // 注意这与argv[]不同如strlen(argv[1])会出错
- srand((unsigned int)time(NULL));
- return strlen(GetCommandLine()) + rand() % 10;
- }
然后是父进程的程序代码,代码中的AdjustProcessCurrentDirectory();函数可以参考《Windows VC++ 调整进程当前目录为程序可执行文件所在目录》(http://blog.csdn.net/morewindows/article/details/8683519):
- //进程通信之三 父进程传参数与子进程返回值
- //http://blog.csdn.net/morewindows/article/details/8683830
- //By MoreWindows( http://blog.csdn.net/MoreWindows )
- #include <windows.h>
- #include <stdio.h>
- #include <string.h>
- #include <conio.h>
- // 启动子进程并传入参数,等待子进程结束后获取其返回值。
- BOOL GetChildProcessExitCode(const char *pstrChildProcessExeFileName,
- char *pstrConmandLine,
- DWORD *pdwExitCode, BOOL fHide = TRUE)
- {
- //子进程启动信息设置
- STARTUPINFO si;
- si.cb = sizeof(STARTUPINFO);
- GetStartupInfo(&si);
- si.wShowWindow = fHide ? SW_HIDE : SW_SHOW;
- si.dwFlags = STARTF_USESHOWWINDOW;
- // 运行子进程并等待其结束
- PROCESS_INFORMATION pi;
- CreateProcess(pstrChildProcessExeFileName, pstrConmandLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);
- WaitForSingleObject(pi.hProcess, INFINITE);
- // 获取子进程返回值
- BOOL flag = GetExitCodeProcess(pi.hProcess, pdwExitCode);
- CloseHandle(pi.hProcess);
- CloseHandle(pi.hThread);
- return flag;
- }
- int main()
- {
- printf(" 进程通信之三 父进程传参数与子进程返回值\n");
- printf(" - http://blog.csdn.net/morewindows/article/details/8683830 -\n");
- printf(" -- By MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
- AdjustProcessCurrentDirectory();
- const char *pstrChildProcessExeFileName = "ChildProcess.exe";
- const int RUN_CHILDPROCESS_NUMBER = 5;
- char szCommandLine[30] = "MoreWindows";
- int nCommandLineLen = strlen(szCommandLine);
- for (int i = 0; i < RUN_CHILDPROCESS_NUMBER; i++)
- {
- // 运行子进程并获取返回值
- DWORD dwExitCode;
- if (GetChildProcessExitCode(pstrChildProcessExeFileName, szCommandLine, &dwExitCode, TRUE))
- printf("子进程返回值为 %d\n", dwExitCode - nCommandLineLen);
- else
- printf("GetExitCodeProcess()失败 %d\n", GetLastError());
- Sleep(1000);
- }
- getch();
- return 0;
- }
程序运行结果如下:
本文配套程序下载地址为:http://download.csdn.net/detail/morewindows/5165733
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8646902
欢迎关注微博:http://weibo.com/MoreWindows