在VC中使用Windows管道技术编程

 

不知你是否用过这样的程序,他们本身并没有解压缩的功能,而是调用DOS程序PKZIP完成ZIP包的解压缩。但是在程序运行时又没有DOS控制台的窗口出现而且一切本应该在DOS下显示的信息都出现在了那个安装程序的一个文本框里。这种设计既美观又可以防止少数眼疾手快的用户提前关了你的DOS窗口。

  现在就来讨论一下,如何用匿名管道技术实现这个功能。

  管道技术由来已久,相信不少人对DOS命令里的管道技术最为熟悉。当我们type一个文件的时候如果想让他分页现实可以输入

  C:\>type autoexec.bat|more

  这里“|”就是管道操作符。他以type输出的信息为读取端,以more的输入端为写入端建立的管道。

  Windows中使用较多的管道也是匿名管道,它通过API函数CreatePipe创建。

BOOL CreatePipe(
 PHANDLE hReadPipe, // 指向读端句柄的指针
 PHANDLE hWritePipe, // 指向写端句柄的指针
 LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全属性结构的指针
 DWORD nSize // 管道的容量
);


  上面几个参数中要注意hReadPipe,hWritePipe是指向句柄的指针,而不是句柄(我第一次用的时候就搞错了)。nSize一般指定为0,以便让系统自己决定管道的容量。现在来看安全属性结构,SECURITY_ATTRIBUTES

typedef struct _SECURITY_ATTRIBUTES { // sa
 DWORD nLength;
 LPVOID lpSecurityDescriptor;
 BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;


  nLength 是结构体的大小,自然是用sizeof取得了。lpSecurityDescriptor是安全描述符(一个C-Style的字符串)。 bInheritHandle他指出了安全描述的对象能否被新创建的进程继承。先不要管他们的具体意义,使用的时候自然就知道了。

  好,现在我们来创建一个管道

HANDLE hReadPipe, hWritePipe;
SECURITY_ATTRIBUTES sa;

sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL; file://
使用系统默认的安全描述符
sa.bInheritHandle = TRUE; file://
一定要为TRUE,不然句柄不能被继承。
CreeatePipe(&hReadPipe,&hWritePipe,&sa,0);


  我们的管道建好了。当然这不是最终目的,我们的目的是把DOS上的一个程序输出的东西重定向到一个Windows程序的Edit控件。所以我们还需要先启动一个DOS的程序,而且还不能出现DOS控制台的窗口(不然不就露馅了吗)。我们用CreateProcess创建一个DOS程序的进程。

BOOL CreateProcess(
 LPCTSTR lpApplicationName, // C-style字符串:应用程序的名称
 LPTSTR lpCommandLine, // C-style字符串:执行的命令
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程安全属性
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性
 BOOL bInheritHandles, // 是否继承句柄的标志
 DWORD dwCreationFlags, // 创建标志
 LPVOID lpEnvironment, // C-Style字符串:环境设置
 LPCTSTR lpCurrentDirectory, // C-Style字符串:执行目录
 LPSTARTUPINFO lpStartupInfo, // 启动信息
 LPPROCESS_INFORMATION lpProcessInformation // 进程信息
);


  先别走,参数是多了点,不过大部分要不不用自己填要不填个NULL就行了。lpApplication随便一点就行了。lpCommandLine可是你要执行的命令一定要认真写好。来,我们瞧瞧lpProcessAttributeslpThreadAttributes怎么设置。哎?这不就是刚才那个吗。对阿,不过可比刚才简单。由于我们只是创建一个进程,他是否能在被继承不敢兴趣所以这两个值全为NULLbInHeritHandles也是一定要设置为TRUE的,因为我们既然要让新的进程能输出信息到调用他的进程里,就必须让新的进程继承调用进程的句柄。我们对创建的新进程也没什么别的苛求,所以dwCreationFlags就为NULL了。lpEnvironmentlpCurrentDirectory根据你自己的要求是指一下就行了,一般也是NULL。接下来的lpStartupInfo可是关键,我们要认真看一下。

typedef struct _STARTUPINFO { // si
 DWORD cb;
 LPTSTR lpReserved;
 LPTSTR lpDesktop;
 LPTSTR lpTitle;
 DWORD dwX;
 DWORD dwY;
 DWORD dwXSize;
 DWORD dwYSize;
 DWORD dwXCountChars;
 DWORD dwYCountChars;
 DWORD dwFillAttribute;
 DWORD dwFlags;
 WORD wShowWindow;
 WORD cbReserved2;
 LPBYTE lpReserved2;
 HANDLE hStdInput;
 HANDLE hStdOutput;
 HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;


  这么多参数,一个一个写肯定累死了。没错,MS早就想到会累死人。所以提供救人一命的API函数GetStartupInfo

VOID GetStartupInfo(
 LPSTARTUPINFO lpStartupInfo
);


  这个函数用来取得当前进程的StartupInfo,我们新建的进程基本根当前进程的StartupInfo差不多,就借用一下啦。然后再小小修改一下即可。

我们要改的地方有这么几个:cb,dwFlagshStdOutputhStdErrorwShowWindow。先说cb,他指的是 STARTUPINFO的大小,还是老手法sizeof。再说wShowWindow,他制定了新进程创建时窗口的现实状态,这个属性当然给为 SW_HIDE了,我们不是要隐藏新建的DOS进程吗。哈哈,看到hStdOutputhStdError,标准输出和错误输出的句柄。关键的地方来了,只要我们把这两个句柄设置为hWrite,我们的进程一旦有标准输出,就会被写入我们刚刚建立的匿名管道里,我们再用管道的hReadPipe句柄把内容读出来写入Edit控件不就达到我们的目的了吗。呵呵,说起来也真是听容易的阿。这几个关键参数完成了以后,千万别忘了dwFlags。他是用来制定 STARTUPINFO里这一堆参数那个有效的。既然我们用了hStdOutput,hStdErrorwShowWindowdwFlags就给为 STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES

  现在回到CreateProcess的最后一个参数lpProcessInformation(累!)。呵呵,这个参数不用自己填了,他是CreateProcess返回的信息,只要给他一个PROCESS_INFORMATION结构事例的地址就行了。

  大功高成了,我们管道一端连在了新进程的标准输出端了,一端可以自己用API函数ReadFile读取了。等等,不对,我们的管道还有问题。我们把 hWrite给了hStdOutputhStdError,那么在新的进程启动时就会在新进程中打开一个管道写入端,而我们在当前进程中使用了 CreatePipe创建了一个管道,那么在当前进程中也有这个管道的写入端hWrite。好了,这里出现了一个有两个写入端和一个读出端的畸形管道。这样的管道肯定是有问题的。由于当前进程并不使用写端,因此我们必须关闭当前进程的写端。这样,我们的管道才算真正的建立成功了。来看看VC++写的源程序:

/*
*
通过管道技术,将dir /?的帮助信息输入到MFC应用程序的一个CEdit控件中。
* VC++6.0 + WinXP
通过
*
* detrox, 2003
*/

void CPipeDlg::OnButton1()
{
 SECURITY_ATTRIBUTES sa;
 HANDLE hRead,hWrite;

 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
 sa.lpSecurityDescriptor = NULL;
 sa.bInheritHandle = TRUE;
 if (!CreatePipe(&hRead,&hWrite,&sa,0)) {
  MessageBox("Error On CreatePipe()");
  return;
 }
 STARTUPINFO si;
 PROCESS_INFORMATION pi;
 si.cb = sizeof(STARTUPINFO);
 GetStartupInfo(&si);
 si.hStdError = hWrite;
 si.hStdOutput = hWrite;
 si.wShowWindow = SW_HIDE;
 si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
 if (!CreateProcess(NULL,"cmd.exe/c dir"
,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi)) {
  MessageBox("Error on CreateProcess()");
  return;
 }
 CloseHandle(hWrite);

 char buffer[4096] = {0};
 DWORD bytesRead;
 while (true) {
  if (ReadFile(hRead,buffer,4095,&bytesRead,NULL) == NULL)
   break;
  m_Edit1 += buffer;
  UpdateData(false);
  Sleep(200);
 }
}

 

 

 

 

 

 

 

 

 

Visual C++下对匿名管道的编程实现

摘要 本文主要对匿名管道这种网络通信技术进行了介绍,并对其VC++的实现方法作了介绍。

  关键词 管道;匿名管道;Visual C++

 

概述

  管道(Pipe)实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。匿名管道(Anonymous Pipes)是在父进程和子进程间单向传输数据的一种未命名的管道,只能在本地计算机中使用,而不可用于网络间的通信。

  匿名管道实施细则

  匿名管道由CreatePipe()函数创建,该函数在创建匿名管道的同时返回两个句柄:管道读句柄和管道写句柄。CreatePipe()的函数原型为:

BOOL CreatePipe(PHANDLE hReadPipe, // 指向读句柄的指针
 PHANDLE hWritePipe, // 指向写句柄的指针
 LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全属性的指针
 DWORD nSize // 管道大小
);


  通过hReadPipehWritePipe所指向的句柄可分别以只读、只写的方式去访问管道。在使用匿名管道通信时,服务器进程必须将其中的一个句柄传送给客户机进程。句柄的传递多通过继承来完成,服务器进程也允许这些句柄为子进程所继承。除此之外,进程也可以通过诸如DDE或共享内存等形式的进程间通信将句柄发送给与其不相关联的进程。

  在调CreatePipe()函数时,如果管道服务器将lpPipeAttributes 指向的SECURITY_ATTRIBUTES数据结构的数据成员bInheritHandle设置为TRUE,那么CreatePipe()创建的管道读、写句柄将会被继承。管道服务器可调用DuplicateHandle()函数改变管道句柄的继承。管道服务器可以为一个可继承的管道句柄创建一个不可继承的副本或是为一个不可继承的管道句柄创建一个可继承的副本。CreateProcess()函数还可以使管道服务器有能力决定子进程对其可继承句柄是全部继承还是不继承。

  在生成子进程之前,父进程首先调用Win32 API SetStdHandle()使子进程、父进程可共用标准输入、标准输出和标准错误句柄。当父进程向子进程发送数据时,用SetStdHandle()将管道的读句柄赋予标准输入句柄;在从子进程接收数据时,则用SetStdHandle()将管道的写句柄赋予标准输出(或标准错误)句柄。然后,父进程可以调用进程创建函数CreateProcess()生成子进程。如果父进程要发送数据到子进程,父进程可调用WriteFile()将数据写入到管道(传递管道写句柄给函数),子进程则调用GetStdHandle()取得管道的读句柄,将该句柄传入ReadFile()后从管道读取数据。

  如果是父进程从子进程读取数据,那么由子进程调用GetStdHandle()取得管道的写入句柄,并调用WriteFile()将数据写入到管道。然后,父进程调用ReadFile()从管道读取出数据(传递管道读句柄给函数)。

  在用WriteFile()函数向管道写入数据时,只有在向管道写完指定字节的数据后或是在有错误发生时函数才会返回。如管道缓冲已满而数据还没有写完,WriteFile()将要等到另一进程对管道中数据读取以释放出更多可用空间后才能够返回。管道服务器在调用CreatePipe()创建管道时以参数nSize对管道的缓冲大小作了设定。

  匿名管道并不支持异步读、写操作,这也就意味着不能在匿名管道中使用ReadFileEx()和WriteFileEx(),而且ReadFile()和WriteFile()中的lpOverLapped参数也将被忽略。匿名管道将在读、写句柄都被关闭后退出,也可以在进程中调用CloseHandle()函数来关闭此句柄。

匿名管道程序示例

  总的来说,匿名管道程序是比较简单的。在下面将要给出的程序示例中,将由父进程(管道服务器)创建一个子进程(管道客户机),子进程回见个其全部的标准输出发送到匿名管道中,父进程再从管道读取数据,一直到子进程关闭管道的写句柄。其中,匿名管道服务器程序的实现清单如下:

STARTUPINFO si;
PROCESS_INFORMATION pi;
char ReadBuf[100];
DWORD ReadNum;
HANDLE hRead; //
管道读句柄

HANDLE hWrite; //
管道写句柄
BOOL bRet = CreatePipe(&hRead, &hWrite, NULL, 0); //
创建匿名管道
if (bRet == TRUE)
 printf("成功创建匿名管道!\n");
else
 printf("创建匿名管道失败,错误代码
:%d\n", GetLastError());
 // 得到本进程的当前标准输出

 HANDLE hTemp = GetStdHandle(STD_OUTPUT_HANDLE);
 // 设置标准输出到匿名管道

 SetStdHandle(STD_OUTPUT_HANDLE, hWrite);
 GetStartupInfo(&si); // 获取本进程的STARTUPINFO结构信息

 bRet = CreateProcess(NULL, "Client.exe", NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi); // 创建子进程
 SetStdHandle(STD_OUTPUT_HANDLE, hTemp); // 恢复本进程的标准输出
 if (bRet == TRUE) // 输入信息
  printf("成功创建子进程!\n");
 
else
  printf("创建子进程失败,错误代码
:%d\n", GetLastError());
  CloseHandle(hWrite); // 关闭写句柄

  // 读管道直至管道关闭
  while (ReadFile(hRead, ReadBuf, 100, &ReadNum, NULL))
  
{
   
ReadBuf[ReadNum] = '\0';
   printf("从管道[%s]读取%d字节数据
\n", ReadBuf, ReadNum);
  
}
  if (GetLastError() == ERROR_BROKEN_PIPE) // 输出信息

   printf("管道被子进程关闭\n");
  
else
   printf("读数据错误,错误代码:%d\n", GetLastError());


  在本示例中,将当前进程的标准输出设置为使用匿名管道,再创建子进程,子进程将继承父进程的标准输出,然后再将父进程的标准输出恢复为其初始状态。于是父进程便可从管道读取数据,直到有错误发生或关闭管道写入端的所有句柄。创建的子进程只是向标准输出和标准错误发送一些文本信息,其中发送给标准输出的文本将重定向输出到管道,发送给标准错误的文本将不改变输出。下面给出子进程的实现代码:

int main(int argc, char* argv[])
{
 for (int i = 0; i 100; i++) // 发送一些数据到标准输出和标准错误

 {
  printf("i = %d\n", i); // 打印提示

  cout << "标准输出:" << i << endl; // 打印到标准输出
  cerr << "标准错误:" << i << endl; // 打印到标准错误
 }
 
return 0;
}

 

 

 

 

 

 

 

 

 

 

CreateProcess使用心得

原文:CreateProcess使用心得

 

1我们用CreateProcess执行一个外部程序时,怎样才能得到这个程序的输入输出呢?CreateProcess已经替我们准备好了,在CreateProcessSTARTUPINFO参数里有这样几个hStdInputhStdOutputhStdError东东,用来为创建的进程指定输入输出,例如CreateFile创建一个文件,接着把得到的文件句柄指定给hStdOutput,并且把dwFlags的值设为USESTDHANDLES,这样外部程序的输出就会输到这个文件里。注意:CreateFileSECURITY_ATTRIBUTES.bInheritHandle参数要设为TRUE

2Create系列函数中通常都会有一个SECURITY_ATTRIBUTES的参数,

SECURITY_ATTRIBUTES sa;

      sa.nLength = sizeof(SECURITY_ATTRIBUTES);

      sa.lpSecurityDescriptor = NULL;

     sa.bInheritHandle = TRUE;

?如果把bInheritHandle的值设为TRUE意思就是它所创建出来的东西是可以被其他子进程使用,例如用CreatePipe创建的管道可以用在CreateProcess创建的进程中。

3、用CreateProcess创建子进程时通过lpCurrentDirectory参数指定子进程运行的路径。

posted @ 2007-09-03 11:45  ahuo  阅读(3056)  评论(1编辑  收藏  举报