C语言 使用管道输入/输出的Java进程; ReadFile块程序

gcmastyq  于 5个月前  发布在  Java
关注(0)|答案(1)|浏览(58)

我想写一个程序来执行cmd或powershell命令。为了实现这一点,我创建了一个进程,并使用pipes作为输入/输出。我的代码工作正常,但在命令(例如:“ipconfig”)执行后,我的程序不会停止,因为ReadFile阻止了它。
我尝试使用ShellExecute,但我不知道如何重定向输出,所以我更喜欢使用CashProcess和我现有的代码的解决方案。
我的代码:

#include "execute_cmdpws.h"
#include <stdio.h>

bool create_pipe(PHANDLE stdInWrite, PHANDLE stdInRead, PHANDLE stdOutWrite, PHANDLE stdOutRead){   
    bool status = false;
    int iResult;
    
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;
    
    iResult = CreatePipe(stdInRead, stdInWrite, &sa, 0);
    
    if (!iResult){
        CloseHandle(*stdInRead);
        CloseHandle(*stdInWrite);
        goto end;
    }
    
    iResult = CreatePipe(stdOutRead, stdOutWrite, &sa, 0);
    if (!iResult) {
        CloseHandle(*stdInRead);
        CloseHandle(*stdInWrite);
        CloseHandle(*stdOutRead);
        CloseHandle(*stdOutWrite);
        goto end;
    }
    
    status = true;
    
end:
    return status;
}

bool execute_command(char* shelltype, char* command, PHANDLE stdInWrite, PHANDLE stdInRead, PHANDLE stdOutWrite, PHANDLE stdOutRead){
    bool status = false;
    int iResult;
    
    int buffer_size = 1024;
    char buffer[buffer_size];
    DWORD dwBytesRead;

    STARTUPINFO sinfo;
    memset(&sinfo, 0, sizeof(sinfo));
    sinfo.cb = sizeof(sinfo);
    sinfo.dwFlags = (STARTF_USESTDHANDLES);
    sinfo.hStdInput = *stdInRead;
    sinfo.hStdOutput = sinfo.hStdError = *stdOutWrite;
    
    PROCESS_INFORMATION pinfo;
    
    iResult = CreateProcessA(NULL, shelltype, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo);
        
    if (!iResult){
        goto end;
    }
    
    // Wait for the process to start
    WaitForInputIdle(pinfo.hProcess, INFINITE);
    
    if (!iResult){
        goto end;
    }
    
    DWORD dwBytesWritten = 0;
    iResult = WriteFile(*stdInWrite, command, strlen(command), &dwBytesWritten, NULL);
    
    if (!iResult){
        goto end;
    }
        
    // Close the write end of the pipes in the parent process, as they are not needed
    CloseHandle(*stdInWrite);
    
    // Read the output in a loop until there is no more data
    while (true) {
        iResult = ReadFile(*stdOutRead, buffer, buffer_size - 1, &dwBytesRead, NULL);

        if (!iResult || dwBytesRead == 0) {
            break;  // No more data or error reading
        }

        buffer[dwBytesRead] = '\0';  // Null-terminate the buffer
        
        printf("Output: %s", buffer);
        memset(buffer, 0, buffer_size);
    }

    if (!iResult){
        goto end;
    }
    
    CloseHandle(*stdOutRead);
    
    status = true;
    
end:
    TerminateProcess(pinfo.hProcess, 0);
    CloseHandle(pinfo.hProcess);
    CloseHandle(pinfo.hThread);
    
    // Close the read end of the pipes in the parent process
    CloseHandle(*stdInRead);
    CloseHandle(*stdOutWrite);
    
    return status;
}

int main(){
    HANDLE
        stdInWrite, 
        stdInRead, 
        stdOutWrite, 
        stdOutRead;
    bool res;
    
    res = create_pipe(&stdInWrite, &stdInRead, &stdOutWrite, &stdOutRead);
    if (!res) return 1;
    
    res = execute_command("cmd.exe", "ipconfig\n", &stdInWrite, &stdInRead, &stdOutWrite, &stdOutRead);
    if (!res) return 2;
    
    return 0;
}

字符串
这是我得到的输出的最后一行(它似乎需要输入,但我不能输入任何东西):
输出:C:\path\to\my\folder>
有没有解决这个问题的方法或者其他方法来执行cmd / powershell命令并重定向输出?
编辑:
我现在使用这个代码,它的工作,但它不是理想的。如果有人有其他的想法,我仍然在寻找解决方案。

// Read the output in a loop until there is no more data    
DWORD dwTotalBytesAvail;
DWORD dwBytesRead;

DWORD timeout = 1000;
DWORD start_time = GetTickCount();

while (true){
    iResult = PeekNamedPipe(stdOutRead, NULL, 0, NULL, &dwTotalBytesAvail, NULL);
    
    if (!iResult){
        return 3;
    }
    
    if (dwTotalBytesAvail > 0){
        iResult = ReadFile(stdOutRead, buffer, buffer_size - 1, &dwBytesRead, NULL);

        if (!iResult || dwBytesRead == 0){
            break;  // No more data or error reading
        }

        buffer[dwBytesRead] = '\0';  // Null-terminate the buffer
        
        printf("Output: %s", buffer);
        memset(buffer, 0, buffer_size);
        
        start_time = GetTickCount();
    } else {
        DWORD elapsed_time = GetTickCount() - start_time;
        
        if (elapsed_time >= timeout){
            break;
        }
        
        Sleep(100);
    }
}

smdncfj3

smdncfj31#

程序不会停止,因为ReadFile阻止了它。
但是为什么ReadFile必须返回?如果我们使用异步句柄-它总是返回。但是如果同步句柄- API调用可以等待无限长的输入。
如果我们使用管道句柄- I/O操作将在链接管道末端的last句柄关闭的情况下完成。如果子进程中的链接管道-当进程退出时-所有句柄都关闭,如果没有其他句柄-这将是last句柄,ReadFile返回(错误将是STATUS_PIPE_BROKEN- * 管道操作失败,因为管道的另一端已经关闭 )。但是如果我们忘记关闭self进程中链接的管道端-最后一个句柄将不会关闭,ReadFile永远不会返回。
其他致命错误-启动 cmd.exe .什么意义启动 cmd.exe 当你需要启动 ipconfig.exe?启动它直接没有任何cmd.
当我们用继承的句柄启动进程时,总是需要使用PROC_THREAD_ATTRIBUTE_HANDLE_LIST属性,以精确控制什么将被继承
下一个-对于什么是需要2管对?!真正单个管道对总是足够的,管道可以同时用于读和写,而不需要额外的管道对,但是这里存在一个问题-如果使用同步文件-所有的操作都是顺序的,直到前面没有完成.这可能导致死锁.只是因为这经常使用2个单独的管道对-读和写,为了避免可能的死锁.但如果我们使用异步管道(如何最小化从自己的一边,另一端可以同步)我们从来没有得到死锁,可以使用单管道对
在许多情况下,像 ipconfig.exe 我们只需要读输出。我们不需要写。所以可能(并且必须)在这里只使用单个管道对,即使在完全同步管道的情况下(两端都是同步的)
因此,
ipconfig.exe * 的最小示例

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}

ULONG Start(_In_ PCWSTR lpApplicationName, 
            _In_ PWSTR lpCommandLine,
            _Out_ PHANDLE hReadPipe)
{
    STARTUPINFOEXW si = { { sizeof(si) } };

    SIZE_T s = 0;
    ULONG dwError;
    while (ERROR_INSUFFICIENT_BUFFER == (dwError = BOOL_TO_ERROR(InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &s))))
    {
        if (si.lpAttributeList)
        {
            break;
        }

        si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(s);
    }

    if (NOERROR == dwError && NOERROR == (dwError = BOOL_TO_ERROR(
        UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, 
        &si.StartupInfo.hStdOutput, sizeof(si.StartupInfo.hStdOutput), 0, 0))))
    {
        SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };

        if (NOERROR == (dwError = BOOL_TO_ERROR(CreatePipe(hReadPipe, &si.StartupInfo.hStdOutput, &sa, 0))))
        {
            si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
            si.StartupInfo.hStdError = si.StartupInfo.hStdOutput;

            PROCESS_INFORMATION pi;
            if (NOERROR == (dwError = BOOL_TO_ERROR(CreateProcessW(lpApplicationName, lpCommandLine, 0, 0, TRUE,
                EXTENDED_STARTUPINFO_PRESENT|DETACHED_PROCESS|CREATE_NO_WINDOW, 
                0, 0, &si.StartupInfo, &pi))))
            {
                CloseHandle(pi.hThread);
                CloseHandle(pi.hProcess);
                CloseHandle(si.StartupInfo.hStdOutput);

                return NOERROR;
            }

            CloseHandle(si.StartupInfo.hStdOutput);
            CloseHandle(*hReadPipe);
        }
    }

    return dwError;
}

void IpconfigDemo()
{
    HANDLE hReadPipe;

    union {
        WCHAR ApplicationName[MAX_PATH];
        char buf[0x100];
    };

    if (SearchPathW(0, L"ipconfig.exe", 0, _countof(ApplicationName), ApplicationName, 0))
    {
        SetEnvironmentVariableW(L"OutputEncoding", L"Ansi");//Unicode  // UTF-8

        if (NOERROR == Start(ApplicationName, 0, &hReadPipe))
        {
            OVERLAPPED ov{};
            ULONG n;

            while (ReadFile(hReadPipe, buf, sizeof(buf), &n, &ov))
            {
                buf[n] = 0;
                DbgPrint(buf);
            }

            RtlGetLastNtStatus();
            GetLastError();

            CloseHandle(hReadPipe);
        }
    }
}

字符串
这里我们只使用单管道对进行读取(两端是同步的)
如果我们需要更通用的代码,在需要写命令的地方,在同步管道的情况下,代码可以是下一个:

ULONG Start(_In_ PCWSTR lpApplicationName, 
            _In_ PWSTR lpCommandLine,
            _Out_ PHANDLE hReadPipe,
            _Out_ PHANDLE hWritePipe)
{
    STARTUPINFOEXW si = { { sizeof(si) } };

    SIZE_T s = 0;
    ULONG dwError;
    while (ERROR_INSUFFICIENT_BUFFER == (dwError = BOOL_TO_ERROR(InitializeProcThreadAttributeList(si.lpAttributeList, 2, 0, &s))))
    {
        if (si.lpAttributeList)
        {
            break;
        }

        si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(s);
    }

    HANDLE h[2];

    if (NOERROR == dwError && NOERROR == (dwError = BOOL_TO_ERROR(
        UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, h, sizeof(h), 0, 0))))
    {
        SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };

        if (NOERROR == (dwError = BOOL_TO_ERROR(CreatePipe(&si.StartupInfo.hStdInput, hWritePipe, &sa, 0))))
        {
            if (NOERROR == (dwError = BOOL_TO_ERROR(CreatePipe(hReadPipe, &si.StartupInfo.hStdOutput, &sa, 0))))
            {
                si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
                si.StartupInfo.hStdError = si.StartupInfo.hStdOutput;
                h[0] = si.StartupInfo.hStdInput, h[1] = si.StartupInfo.hStdOutput;

                PROCESS_INFORMATION pi;
                if (NOERROR == (dwError = BOOL_TO_ERROR(CreateProcessW(lpApplicationName, lpCommandLine, 0, 0, TRUE,
                    EXTENDED_STARTUPINFO_PRESENT|DETACHED_PROCESS|CREATE_NO_WINDOW, 
                    0, 0, &si.StartupInfo, &pi))))
                {
                    CloseHandle(pi.hThread);
                    CloseHandle(pi.hProcess);
                    CloseHandle(si.StartupInfo.hStdOutput);
                    CloseHandle(si.StartupInfo.hStdInput);

                    return NOERROR;
                }

                CloseHandle(si.StartupInfo.hStdOutput);
                CloseHandle(*hReadPipe);
            }

            CloseHandle(si.StartupInfo.hStdInput);
            CloseHandle(*hWritePipe);
        }
    }

    return dwError;
}

void CmdDemo()
{
    HANDLE hReadPipe, hWritePipe;

    union {
        WCHAR ApplicationName[MAX_PATH];
        char buf[0x100];
    };

    if (SearchPathW(0, L"cmd.exe", 0, _countof(ApplicationName), ApplicationName, 0))
    {
        if (NOERROR == Start(ApplicationName, 0, &hReadPipe, &hWritePipe))
        {
            OVERLAPPED ov{};
            ULONG n;
            
            static const char cmd[] = "dir\r\nexit\r\n";

            if (WriteFile(hWritePipe, cmd, sizeof(cmd) - 1, &n, &ov))
            {
                while (ReadFile(hReadPipe, buf, sizeof(buf), &n, &ov))
                {
                    buf[n] = 0;
                    DbgPrint(buf);
                }

                RtlGetLastNtStatus();
                GetLastError();
            }

            CloseHandle(hWritePipe);
            CloseHandle(hReadPipe);
        }
    }
}


最后,以异步管道为例(实际上我们的端是异步的,客户端是同步的)
对于使用异步I/O,我们几乎总是需要一些lib/类来进行带句柄和I/O的 Package 操作
对于当前的演示-非常小的代码:

struct __declspec(novtable) uObject 
{
    HANDLE _M_hFile = 0;
    LONG _M_dwRefCount = 1;

    virtual ~uObject()
    {
        if (_M_hFile)
        {
            CloseHandle(_M_hFile);
        }
    }

    virtual void OnIoComplete(ULONG dwError, ULONG dwBytes, ULONG code, PVOID buf) = 0;

    void AddRef()
    {
        InterlockedIncrementNoFence(&_M_dwRefCount);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_M_dwRefCount))
        {
            delete this;
        }
    }
};

struct uIRP : OVERLAPPED
{
    ULONG _M_code;
    uObject* _M_pObj;
    UCHAR _M_buf[];

    uIRP(ULONG code, uObject* pObj) : _M_code(code), _M_pObj(pObj)
    {
        RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
        pObj->AddRef();
    }

    ~uIRP()
    {
        _M_pObj->Release();
    }

    VOID OnIoComplete(
        _In_    DWORD dwErrorCode,
        _In_    DWORD dwNumberOfBytesTransfered
        )
    {
        _M_pObj->OnIoComplete(dwErrorCode, dwNumberOfBytesTransfered, _M_code, _M_buf);
        delete this;
    }

    static VOID WINAPI _S_OnIoComplete(
        _In_    DWORD dwErrorCode,
        _In_    DWORD dwNumberOfBytesTransfered,
        _Inout_ LPOVERLAPPED lpOverlapped
        )
    {
        static_cast<uIRP*>(lpOverlapped)->OnIoComplete(RtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
    }

    static ULONG Bind(HANDLE hFile)
    {
        return BOOL_TO_ERROR(BindIoCompletionCallback(hFile, _S_OnIoComplete, 0));
    }

    void* operator new(size_t s, ULONG cb)
    {
        return LocalAlloc(LMEM_FIXED, s + cb);
    }

    void operator delete(void* p)
    {
        LocalFree(p);
    }
};


uObject是抽象的。需要为具体对象实现OnIoComplete。对于管道(这种简单的情况),我们可以做下一个实现:

struct uPipe : public uObject 
{
    enum { opWrite = 'wwww', opRead = 'rrrr' };

    HANDLE _M_hEvent = CreateEvent(0, 0, 0, 0);

    ~uPipe()
    {
        CloseHandle(_M_hEvent);
    }

    void Write(
        _In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
        _In_ DWORD nNumberOfBytesToWrite)
    {
        if (uIRP* irp = new(0) uIRP(opWrite, this))
        {
            switch (ULONG dwError = BOOL_TO_ERROR(WriteFile(_M_hFile, lpBuffer, nNumberOfBytesToWrite, 0, irp)))
            {
            case NOERROR:
            case ERROR_IO_PENDING:
                break;
            default:
                irp->OnIoComplete(dwError, 0);
            }
        }
    }

    void Read(ULONG cb)
    {
        if (uIRP* irp = new(cb+1) uIRP(opRead, this))
        {
            switch (ULONG dwError = BOOL_TO_ERROR(ReadFile(_M_hFile, irp->_M_buf, cb, 0, irp)))
            {
            case NOERROR:
            case ERROR_IO_PENDING:
                break;
            default:
                irp->OnIoComplete(dwError, 0);
            }
        }
    }

    void OnRead(PSTR buf, ULONG dwBytes)
    {
        buf[dwBytes] = 0;
        DbgPrint(buf);
    }

    virtual void OnIoComplete(ULONG dwError, ULONG dwBytes, ULONG code, PVOID buf)
    {
        if (dwError)
        {
            SetEvent(_M_hEvent);
            return;
        }

        switch (code)
        {
        case opRead:
            OnRead((PSTR)buf, dwBytes);
            Read(0x100);
            break;
        case opWrite:
            break;

        default:
            __debugbreak();
        }
    }
};


然后我们需要自己实现CreatePipe API。标准API创建完全同步的管道对。但是我们需要异步。

enum {
    FLAG_PIPE_CLIENT_SYNCHRONOUS = 0x01,
    FLAG_PIPE_CLIENT_INHERIT = 0x02,
    FLAG_PIPE_SERVER_SYNCHRONOUS = 0x04,
    FLAG_PIPE_SERVER_INHERIT = 0x8,
};

#ifndef FILE_SHARE_VALID_FLAGS
#define FILE_SHARE_VALID_FLAGS (FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE)
#endif

NTSTATUS CreatePipeAnonymousPair(PHANDLE phServerPipe, PHANDLE phClientPipe, ULONG Flags, DWORD nInBufferSize)
{
    HANDLE hFile;

    IO_STATUS_BLOCK iosb;

    UNICODE_STRING NamedPipe;
    RtlInitUnicodeString(&NamedPipe, L"\\Device\\NamedPipe\\");

    OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &NamedPipe, OBJ_CASE_INSENSITIVE };

    NTSTATUS status;

    if (0 <= (status = NtOpenFile(&hFile, SYNCHRONIZE, &oa, &iosb, FILE_SHARE_VALID_FLAGS, 0)))
    {
        oa.RootDirectory = hFile;

        static const UNICODE_STRING empty = {};

        oa.Attributes = Flags & FLAG_PIPE_SERVER_INHERIT ? OBJ_INHERIT : 0;
        oa.ObjectName = const_cast<PUNICODE_STRING>(&empty);
        LARGE_INTEGER time = { 0, (LONG)MINLONG };

        if (0 <= (status = ZwCreateNamedPipeFile(phServerPipe,
            FILE_READ_ATTRIBUTES|FILE_READ_DATA|
            FILE_WRITE_ATTRIBUTES|FILE_WRITE_DATA|
            FILE_CREATE_PIPE_INSTANCE, 
            &oa, &iosb, FILE_SHARE_READ|FILE_SHARE_WRITE,
            FILE_CREATE, 
            Flags & FLAG_PIPE_SERVER_SYNCHRONOUS ? FILE_SYNCHRONOUS_IO_NONALERT : 0, 
            FILE_PIPE_BYTE_STREAM_TYPE, FILE_PIPE_BYTE_STREAM_MODE,
            FILE_PIPE_QUEUE_OPERATION, 1, nInBufferSize, nInBufferSize, &time)))
        {
            oa.RootDirectory = *phServerPipe;
            oa.Attributes = Flags & FLAG_PIPE_CLIENT_INHERIT ? OBJ_INHERIT : 0;

            if (0 > (status = NtOpenFile(phClientPipe, SYNCHRONIZE|FILE_READ_ATTRIBUTES|FILE_READ_DATA|
                FILE_WRITE_ATTRIBUTES|FILE_WRITE_DATA, &oa, &iosb, FILE_SHARE_VALID_FLAGS, 
                Flags & FLAG_PIPE_CLIENT_SYNCHRONOUS ? FILE_SYNCHRONOUS_IO_NONALERT : 0)))
            {
                NtClose(oa.RootDirectory);
                *phServerPipe = 0;
            }
        }

        NtClose(hFile);
    }

    return status;
}


现在我们可以使用下一个代码:

ULONG StartA(_In_ PCWSTR lpApplicationName, 
            _In_ PWSTR lpCommandLine,
            _Out_ PHANDLE hPipe)
{
    STARTUPINFOEXW si = { { sizeof(si) } };

    SIZE_T s = 0;
    ULONG dwError;
    while (ERROR_INSUFFICIENT_BUFFER == (dwError = BOOL_TO_ERROR(InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &s))))
    {
        if (si.lpAttributeList)
        {
            break;
        }

        si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(s);
    }

    if (NOERROR == dwError && NOERROR == (dwError = BOOL_TO_ERROR(
        UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, 
        &si.StartupInfo.hStdOutput, sizeof(si.StartupInfo.hStdOutput), 0, 0))))
    {
        SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };

        if (STATUS_SUCCESS == (dwError = CreatePipeAnonymousPair(hPipe, &si.StartupInfo.hStdError, 
            FLAG_PIPE_CLIENT_SYNCHRONOUS|FLAG_PIPE_CLIENT_INHERIT, 0)))
        {
            if (NOERROR == (dwError = uIRP::Bind(*hPipe)))
            {
                si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
                si.StartupInfo.hStdInput = si.StartupInfo.hStdOutput = si.StartupInfo.hStdError;

                PROCESS_INFORMATION pi;
                if (NOERROR == BOOL_TO_ERROR(CreateProcessW(lpApplicationName, lpCommandLine, 0, 0, TRUE,
                    EXTENDED_STARTUPINFO_PRESENT|DETACHED_PROCESS|CREATE_NO_WINDOW, 
                    0, 0, &si.StartupInfo, &pi)))
                {
                    CloseHandle(pi.hThread);
                    CloseHandle(pi.hProcess);
                    CloseHandle(si.StartupInfo.hStdOutput);

                    return NOERROR;
                }
            }

            CloseHandle(si.StartupInfo.hStdOutput);
            CloseHandle(*hPipe);
        }
        else
        {
            dwError = RtlNtStatusToDosError(dwError);
        }
    }

    return dwError;
}

void CmdDemoA()
{
    WCHAR ApplicationName[MAX_PATH];

    if (SearchPathW(0, L"cmd.exe", 0, _countof(ApplicationName), ApplicationName, 0))
    {
        if (uPipe* pipe = new uPipe)
        {
            if (NOERROR == StartA(ApplicationName, 0, &pipe->_M_hFile))
            {
                static const char cmd[] = "dir\r\nexit\r\n";

                pipe->Write(cmd, sizeof(cmd) - 1);

                pipe->Read(0x100);

                //... any code ...

                WaitForSingleObject(pipe->_M_hEvent, INFINITE);
            }

            pipe->Release();
        }
    }
}

相关问题