简体   繁体   中英

Send command to pipe in C++

(Do the Z snap if you want, it'll lighten the mood)

I'm very far out of my comfort zone on this new project I decided to dive into, at least with parts of it.

The entire project will be a DLL that can be loaded into TeamSpeak 3, and allow people (via a small set of commands) to control Pianobar (a Pandora command line player).

The answer here guided me enough to get Pianobar (a console application) https://stackoverflow.com/a/17502224/1733365 up and running, I can get its STDOUT and it displays up until it the position where it displays the song's current time, and also where it accepts user input. The entire process locks at that point, I'm guessing because the ReadFromPipe() command thinks there is more to read as that line keeps getting refreshed.

I also took a stab at overriding the initial WriteToPipe(void) to WriteToPipe(char *cmd) in order to allow me to call it from an outside thread. (The one that listens to the chat of the TeamSpeak 3 server for specific commands.)

Right now my code is a giant mess, but I cleaned it up a bit so hopefully someone can help me understand.

Really this is just a summer project I decided to attempt while I'm out of school, and my first experience with creating a DLL.

Pianobar for Windows

Much of the code below was taken from Creating a Child Process with Redirected Input and Output

#include "pianobar.h"
//#include <windows.h> 
//#include <tchar.h>
//#include <stdio.h> 
#include <strsafe.h>
//#include <stdlib.h>
//#include <sys/types.h>
//#include <string.h>

#define BUFSIZE 4096 

HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;

HANDLE g_hInputFile = NULL;
PROCESS_INFORMATION piProcInfo; 
STARTUPINFO siStartInfo;
SECURITY_ATTRIBUTES saAttr; 

void CreateChildProcess(void); 
void WriteToPipe(char *command); 
void ReadFromPipe(void); 
void ErrorExit(PTSTR); 

int pianobar (struct TS3Functions ts3Functions) {
    int iFound = 0;

    printf("\n->Start of parent execution.\n");

    // Set the bInheritHandle flag so pipe handles are inherited. 

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
    saAttr.bInheritHandle = TRUE; 
    saAttr.lpSecurityDescriptor = NULL; 

    // Create a pipe for the child process's STDOUT. 

    if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) ) 
        ErrorExit(TEXT("StdoutRd CreatePipe")); 

    // Ensure the read handle to the pipe for STDOUT is not inherited.

    if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) )
        ErrorExit(TEXT("Stdout SetHandleInformation")); 

    // Create a pipe for the child process's STDIN. 

    if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) 
        ErrorExit(TEXT("Stdin CreatePipe")); 

    // Ensure the write handle to the pipe for STDIN is not inherited. 

    if ( ! SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) )
        ErrorExit(TEXT("Stdin SetHandleInformation")); 

    // Create the child process. 

    CreateChildProcess(); 

    // Write to the pipe that is the standard input for a child process. 
    // Data is written to the pipe's buffers, so it is not necessary to wait
    // until the child process is running before writing data.

    // This should cause a help menu to be displayed on the next ReadFromPipe()
    // However, ReadFromPipe() doesn't show help commands
    //WriteToPipe("?\r\n"); 

    // Read from pipe that is the standard output for child process. 
    // Reading causes a lock.
    //ReadFromPipe(); 


    printf("\n->End of parent execution.\n");
    printf("\n->Pianobar started.\n");
    iFound = 1;
    return iFound;
}

void CloseChildProcess() {
    //CloseHandle(piProcInfo.hProcess);
    CloseHandle(piProcInfo.hThread);
    TerminateProcess(piProcInfo.hProcess,0);
}

void CreateChildProcess()
    // Create a child process that uses the previously created pipes for STDIN and STDOUT.
{ 
    TCHAR szCmdline[]=TEXT("c:\\pianobar\\pianobar.exe");
    BOOL bSuccess = FALSE; 

    // Set up members of the PROCESS_INFORMATION structure. 

    ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

    // Set up members of the STARTUPINFO structure. 
    // This structure specifies the STDIN and STDOUT handles for redirection.
    ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
    siStartInfo.cb = sizeof(STARTUPINFO); 
    siStartInfo.hStdError = g_hChildStd_OUT_Wr;
    siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
    siStartInfo.hStdInput = g_hChildStd_IN_Rd;
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    // Create the child process. 

    bSuccess = CreateProcess(NULL, 
        szCmdline,     // command line 
        NULL,          // process security attributes 
        NULL,          // primary thread security attributes 
        TRUE,          // handles are inherited 
        0,             // creation flags 
        NULL,          // use parent's environment 
        TEXT("c:\\pianobar\\"),          // use parent's current directory 
        &siStartInfo,  // STARTUPINFO pointer 
        &piProcInfo);  // receives PROCESS_INFORMATION 

    // If an error occurs, exit the application. 
    if ( ! bSuccess ) 
        ErrorExit(TEXT("CreateProcess"));
    else 
    {
        // Close handles to the child process and its primary thread.
        // Some applications might keep these handles to monitor the status
        // of the child process, for example. 

        // I think I need these while I'm running...
        //CloseHandle(piProcInfo.hProcess);
        //CloseHandle(piProcInfo.hThread);
    }
}

void WriteToPipe(char *command) 

    // Read from a file and write its contents to the pipe for the child's STDIN.
    // Stop when there is no more data. 
{ 

    DWORD dwRead, dwWritten; 
    DWORD dw;
    CHAR chBuf[BUFSIZE];
    BOOL bSuccess = FALSE;
    LPTSTR lpTStr;

    printf("\n-> In WriteToPipe()\n");
    bSuccess = WriteFile(g_hChildStd_IN_Wr, command, sizeof(command), &dwWritten, NULL);
        if(bSuccess) {
            printf("bSuccess was TRUE\n->Sent: ");
            printf(command);
        } else {
            printf("bSuccess was FALSE\n");
        }

        // Close the pipe handle so the child process stops reading. 
        // my 2nd call to WriteToPipe results in a "The handle is invalid" error
        if ( ! CloseHandle(g_hChildStd_IN_Wr) ) {

        dw = GetLastError(); 
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | 
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpTStr,
            0, NULL );
            printf(lpTStr);
        }
        if(command == "q\r\n") {
            printf("Quit received.\n");
            // this should have killed the process if it was received correctly...
            CloseChildProcess();
        }
} 

void ReadFromPipe(void) 
    // Read output from the child process's pipe for STDOUT
    // and write to the parent process's pipe for STDOUT. 
    // Stop when there is no more data. 
{ 
    DWORD dwRead, dwWritten; 
    CHAR chBuf[BUFSIZE]; 
    BOOL bSuccess = FALSE;
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

    printf("\n-> In ReadFromPipe()\n");
    for (;;) 
    { 
        bSuccess = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
        if( ! bSuccess || dwRead == 0 ) break; 
        printf("In ReadFromPipe loop\n");
        bSuccess = WriteFile(hParentStdOut, chBuf, 
            dwRead, &dwWritten, NULL);
        if (! bSuccess ) { 
            // we never get to this, it just waits...
            printf("Leaving loop\n");
            break; 
        }
    } 
} 

void ErrorExit(PTSTR lpszFunction) 

    // Format a readable error message, display a message box, 
    // and exit from the application.
{ 
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    ExitProcess(1);
}

I didn't really understand your setup, but regarding to this:

...and also where it accepts user input. The entire process locks at that point, I'm guessing because the ReadFromPipe() command thinks there is more to read as that line keeps getting refreshed.

That's very likely. If there's nothing to read from the pipe, then your process blocks, ie gets stuck inside the ReadFile() call. If you want to read only if there's something to read, you need async I/O or a notification mechanism. I don't really know Windows, but seems as if the're IO Completion Ports (IOCP) and async callback functions available for that matter. Maybe these links help:

What is the best epoll/kqueue/select equvalient on Windows?

IOCP and ReadFileEx usage

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM