WindowsAPI 之 CreatePipe、CreateProcess


  • MSDN介紹

CreatePipe

A pipe is a section of shared memory that processes use for communication. The process that creates a pipe is the pipe server. A process that connects to a pipe is a pipe client. One process writes information to the pipe, then the other process reads the information from the pipe. This overview describes how to create, manage, and use pipes.

管 道(Pipe)實際是用於進程間通信的一段共享內存,創建管道的進程稱為 管道服務器,連接到一個管道的進程為 管道客戶機。一個進程在向管道寫入數據后,另 一進程就可以從管道的另一端將其讀取出來。匿名管道(Anonymous Pipes)是在父進程和子進程間單向傳輸數據的一種未命名的管道,只能在本地計算機中使用,而不可用於網絡間的通信。
BOOL WINAPI CreatePipe(
  _Out_    PHANDLE               hReadPipe,
  _Out_    PHANDLE               hWritePipe,
  _In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes,
  _In_     DWORD                 nSize
);

Return value

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Remarks

CreatePipe creates the pipe, assigning the specified pipe size to the storage buffer. CreatePipe also creates handles that the process uses to read from and write to the buffer in subsequent calls to the ReadFile and WriteFile functions.

To read from the pipe, a process uses the read handle in a call to the ReadFile function. ReadFile returns when one of the following is true: a write operation completes on the write end of the pipe, the number of bytes requested has been read, or an error occurs.

When a process uses WriteFile to write to an anonymous pipe, the write operation is not completed until all bytes are written. If the pipe buffer is full before all bytes are written, WriteFile does not return until another process or thread uses ReadFile to make more buffer space available.

Anonymous pipes are implemented using a named pipe with a unique name. Therefore, you can often pass a handle to an anonymous pipe to a function that requires a handle to a named pipe.

If CreatePipe fails, the contents of the output parameters are indeterminate. No assumptions should be made about their contents in this event.

To free resources used by a pipe, the application should always close handles when they are no longer needed, which is accomplished either by calling the CloseHandle function or when the process associated with the instance handles ends. Note that an instance of a pipe may have more than one handle associated with it. An instance of a pipe is always deleted when the last handle to the instance of the named pipe is closed.

CreateProcess
Creates a new process and its primary thread. The new process runs in the security context of the calling process.
BOOL WINAPI CreateProcess(
  _In_opt_    LPCTSTR               lpApplicationName,
  _Inout_opt_ LPTSTR                lpCommandLine,
  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_        BOOL                  bInheritHandles,
  _In_        DWORD                 dwCreationFlags,
  _In_opt_    LPVOID                lpEnvironment,
  _In_opt_    LPCTSTR               lpCurrentDirectory,
  _In_        LPSTARTUPINFO         lpStartupInfo,
  _Out_       LPPROCESS_INFORMATION lpProcessInformation
);
  • 先簡單介紹一下重定向

stdin是標准輸入,stdout是標准輸出,stderr是標准錯誤輸出。大多數的命令行程序從stdin輸入,輸出到stdout或 stderr,有時我們需要重定向stdout,stderr,stdin。比如:將輸出寫入文件,又或者我們要將命令行程序輸出結果顯示到 Windows對話框中。

在Windows編程中,重定向需要用到管道(Pipe)的概念。管道是一種用於在進程間共享數據的機制。一個管道類似於一個管子的兩端,一端是寫入的,一端是讀出的。由一個進程從寫入端寫入、另一個進程從讀出端讀出,從而實現通信,就向一個“管道”一樣。

重定向的原理是:

首先聲明兩個概念:主程序(重定向的操縱者)、子進程(被重定向的子進程)

如果要重定位stdout的話,先生成一個管道, 管道的寫入端交給子進程去寫,主程序從管道的讀出端讀數據,然后可以把數據寫成文件、顯示等等。重定向stderr和stdout是相同的。

同理,要重定向stdin的話,生成一個管道, 管道的寫入端由主程序寫,子進程從管道的讀出端讀數據。

其中需要用到幾個Windows API :  CreatePipe, DuplicateHandle, CreateProcess, ReadFile, WriteFile 等,函數詳解可參見MSDN.

比如一個控制台程序打印一行文字:

會在windows彈出的對話框中輸出:

為什么會輸出到這里而不是別的地方呢?因為這里就是所說的StdOut(標准輸出)的地方。如果你想輸出到別的地方,那就得把stdout重定向到別的地方才行。

 

比如,某網友寫了一個重定向程序將stdout重定向到自己寫的一個窗口中,就會產生如下的效果:

 

  • 先詳細介紹一下管道,這里以匿名管道為例:

第一:匿名管道只能實現本地進程之間的通信,不能實現跨網絡之間的進程間的通信。

第二:匿名管道只能實現父進程和子進程之間的通信,而不能實現任意兩個本地進程之間的通信。

匿名管道主要用於本地父進程和子進程之間的通信,在父進程中的話,首先是要創建一個匿名管道,在創建匿名管道成功后,可以獲取到對這個匿名管道的讀寫句柄,然后父進程就可以向這個匿名管道中寫入數據和讀取數據了,但是如果要實現的是父子進程通信的話,那么還必須在父進程中創建一個子進程,同時,這個子進程必須能夠繼承和使用父進程的一些公開的句柄,因為在子進程中必須要使用父進程創建的匿名管道的讀寫句柄,通過這個匿名管道才能實現父子進程的通信,所以必須繼承父進程的公開句柄。同時在創建子進程的時候,必須將子進程的標准輸入句柄設置為父進程中創建匿名管道時得到的讀管道句柄,將子進程的標准輸出句柄設置為父進程中創建匿名管道時得到的寫管道句柄。然后在子進程就可以讀寫匿名管道了。

 

  • 下面來講CreatePipe:

CreatePipe時會獲取兩個句柄,一個是讀句柄,一個是寫句柄(這里的讀句柄表示要從哪里讀取數據,寫句柄表示要把數據寫到哪里)。

父進程可以調用進程創建函數CreateProcess()生成子進程。如果父進程要發送數據到子進程,父進程可調用WriteFile()將數據寫入到管 道(傳遞管道寫句柄給函數),子進程則調用GetStdHandle()取得管道的讀句柄,將該句柄傳入ReadFile()后從管道讀取數據。(如果是父進程從子進程讀取數據,那么由子進程調用GetStdHandle()取得管道的寫入句柄,並調用WriteFile()將數據寫入到管道。然后,父進程調用ReadFile()從管道讀取出數據(傳遞管道讀句柄給函數))//GetStdHandle()是由子進程調用

在 用WriteFile()函數向管道寫入數據時,只有在向管道寫完指定字節的數據后或是在有錯誤發生時函數才會返回。如管道緩沖已滿而數據還沒有寫 完,WriteFile()將要等到另一進程對管道中數據讀取以釋放出更多可用空間后才能夠返回。管道服務器在調用CreatePipe()創建管道時以 參數nSize對管道的緩沖大小作了設定。
   匿名管道並不支持 異步讀、寫操作,這也就意味着不能在匿名管道中使用ReadFileEx()和WriteFileEx()(它只能用於異步讀寫文件操作,異步操作完成后會調用指定的回調函數),而且ReadFile() 和WriteFile()中的lpOverLapped參數也將被忽略。匿名管道將在讀、寫句柄都被關閉后退出,也可以在進程中調用 CloseHandle()函數來關閉此句柄(個人理解就是,匿名管道,只能是你全部往管道中讀寫完之前,就不能干別的事,只能寫或等待(管道滿的時候處在等待狀態);而子進程在全部接收完管道的數據之前也只能讀或等待(沒數據時等待),也不能去干其它的事)。
根據上邊API的原型,通過hReadPipe和hWritePipe所指向的句柄可分別以只讀、只寫的方式去訪問管道。在使用匿名管道通信時,服務器進程(父進程)必須將其中的一個句柄傳送給客戶機進程。句柄的傳遞多通過 繼承來完成(如何繼承?請往下看),服務器進程也允許這些句柄為子進程所繼承。

在調用CreatePipe()函數時,如果管道服務器將lpPipeAttributes 指向的SECURITY_ATTRIBUTES數據結構的數據成員bInheritHandle設置為TRUE,那么CreatePipe()創建的管道讀、寫句柄將會被繼承(管道服務器可調用DuplicateHandle()函數改變管道句柄的繼承。管道服務器可以為一個可繼承的管道句柄創建一個不可 繼承的副本或是為一個不可繼承的管道句柄創建一個可繼承的副本。CreateProcess()函數還可以使管道服務器有能力決定子進程對其可繼承句柄是 全部繼承還是不繼承)。

在生成子進程之前,父進程首先調用Win32 API SetStdHandle()使子進程、父進程可共用標准輸入、標准輸出和標准錯誤句柄(StdOut、StdIn、StdErr)。當父進程向子進程發送數據時,用SetStdHandle()將 管道的讀句柄賦予標准輸入句柄(這樣就不會從標准輸入讀入數據,而從讀句柄所表示的位置讀取數據);在從子進程接收數據時,則用SetStdHandle()將管道的寫句柄賦予標准輸出(或標准錯誤)句柄。然后,父進程可以調用進程創建函數CreateProcess()生成子進程。如果父進程要發送數據到子進程,父進程可調用WriteFile()將數據寫入到管道(傳 遞管道寫句柄給函數),子進程則調用GetStdHandle()取得管道的讀句柄,將該句柄傳入ReadFile()后從管道讀取數據。//SetStdHandle()是由父進程調用

  • 舉例:
#include <iostream>
#include <windows.h>
#include <Shlwapi.h>

using namespace std;

#define BUFSIZE 4096


int main()
{
    BOOL bRet = FALSE;
    DWORD dwRead = 0;
    DWORD dwAvail = 0;
    char cbBuf[4096] = { 0 };
    HANDLE hReadPipe = NULL;
    HANDLE hWritePipe = NULL;
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;
    char *pCommandLine = new TCHAR[0x200];//
    char szPath[] = "C:\\Windows\\System32\\calc.exe";
    CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
    STARTUPINFO si = { 0 };
    si.cb = sizeof(STARTUPINFO);
    GetStartupInfo(&si);
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.hStdError = hWritePipe;
    si.hStdOutput = hWritePipe;
    PROCESS_INFORMATION   pi = { 0 };

    memset(pCommandLine, 0, sizeof(szPath));
    lstrcpy(pCommandLine, szPath);

    if (!CreateProcess(NULL, pCommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi))//創建子進程
    {
        if (pCommandLine)
            delete pCommandLine;

        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
        CloseHandle(hReadPipe);
        CloseHandle(hWritePipe);

        return 1;
    }
    std::string strResult;
    do
    {
        cout << "test.." << endl;
        if (!PeekNamedPipe(hReadPipe, NULL, NULL, &dwRead, &dwAvail, NULL) || dwAvail <= 0)//PeekNamePipe用來預覽一個管道中的數據,用來判斷管道中是否為空
        {
             break;
         }
        if (ReadFile(hReadPipe, cbBuf, BUFSIZE, &dwRead, NULL))//這里是讀管道,即便已經沒有數據,仍然會等待接收數據,因為,子進程會認為父進程仍有數據要發送,只是暫時沒法送,
        {                                                        //所以,會“卡”在這里。所以才需要PeekNamePipe
            if (dwRead == 0)
                break;
            cout << dwRead << endl;
            cout << cbBuf << endl;
        }
    } while (TRUE);


    if (pCommandLine)
        delete pCommandLine;
    cout << "delete" << endl;
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    CloseHandle(hReadPipe);
    CloseHandle(hWritePipe);

    return 0;
}

 

  • 參考:
 一篇比較好的參考文章:
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM