C++ 在Windows、C/C+中,通过管道(作为伪终端替换)与另一个进程进行通信,无需阻塞和缓冲+;

C++ 在Windows、C/C+中,通过管道(作为伪终端替换)与另一个进程进行通信,无需阻塞和缓冲+;,c++,windows,pipe,pty,C++,Windows,Pipe,Pty,我想要实现的是: 一个应用程序(一个使用SDL2但与问题无关的“游戏”),可以在屏幕上显示多个东西,其中一个可以是用户/玩家可以交互的“计算机终端”。但是游戏中的计算机将在真正的计算机上运行一个实际的程序,其输入和输出将重定向到游戏。因此,基本上,“游戏”的行为必须像“来宾”程序的终端一样,比如xterm或其他类似的程序。我希望它能在GNU/Linux和Windows上运行 所以我开始制作一个演示,只做这个,其他什么都不做。 获取键盘操作并将其转换为发送到程序的文本,从程序接收文本并在屏幕上绘制

我想要实现的是: 一个应用程序(一个使用SDL2但与问题无关的“游戏”),可以在屏幕上显示多个东西,其中一个可以是用户/玩家可以交互的“计算机终端”。但是游戏中的计算机将在真正的计算机上运行一个实际的程序,其输入和输出将重定向到游戏。因此,基本上,“游戏”的行为必须像“来宾”程序的终端一样,比如xterm或其他类似的程序。我希望它能在GNU/Linux和Windows上运行

所以我开始制作一个演示,只做这个,其他什么都不做。 获取键盘操作并将其转换为发送到程序的文本,从程序接收文本并在屏幕上绘制

在GNU/Linux上,我成功地做到了这一点。 这是使用一个伪终端完成的,它正是用于此目的的工具。 我用openpty()创建一个伪终端,使其非阻塞,然后执行fork(),在fork中我将IO重定向到preudoterminal,将其设置为控制终端,最后执行execl()以运行来宾程序。 在主机程序中,我定期执行read()和write()。 这个很好用

接下来是Windows。 Windows不支持fork exec,所以我使用CreateProcess()以Windows方式执行。 Windows也没有伪终端,所以我改用命名管道。 我知道管道不具备伪终端的所有功能,但为了满足我的需要,这没关系。 因此,在Windows上,我使用CreateNamedPipe()创建两个管道,使用CreateFile()打开另一侧,句柄可继承,最后使用CreateProcess()运行来宾程序,将管道句柄设置为输入和输出。 在宿主程序中,我定期执行ReadFile()和WriteFile()

这也行,但效果不好。 两件不想要的事情正在发生。 首先,在(来宾程序)管道的输出没有更多可用字节后,ReadFile()函数将不会返回,即使管道是使用pipe_NOWAIT标志创建的。 但后来我发现,阅读只在葡萄酒中受阻。在真实的窗口中,读取是非阻塞的,就像它应该的那样。 但还有第二件事。 系统正在缓冲(来宾程序)管道的输出。 当来宾程序写入其输出(因此写入管道)时,宿主程序看不到任何要读取的字节。只有当来宾写入大量字节时,所有字节才会一次全部发送到主机。即使管道在来宾端以文件\u FLAG\u NO\u缓冲方式打开,也会发生这种情况。 这是不可接受的,因为我需要实时交互

以下是设置流程和管道/伪终端的代码:

    bool LemlTerm::runCommand() {
#ifdef LEML_WIN32
    char ptyName[256];
    HANDLE ptyInM = NULL;
    HANDLE ptyInS = NULL;
    HANDLE ptyOutM = NULL;
    HANDLE ptyOutS = NULL;
    SECURITY_ATTRIBUTES sAtr;

    LPCSTR applName = "load.exe";
    LPSTR cmdLine = (LPSTR)"load.exe test";
    STARTUPINFO stInfo;
    PROCESS_INFORMATION prInfo; 

    if (commandRunning)
        return false;

    fail=false;

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

    snprintf(ptyName,256,"\\\\.\\pipe\\%08lx_LEML_PTY_IN",(unsigned long)GetCurrentProcessId());
    printf ("ptyIn: %s\n",ptyName);

    ptyInM = CreateNamedPipe (
        (LPCSTR) ptyName,
        PIPE_ACCESS_OUTBOUND,
        PIPE_NOWAIT,
        1,
        TERMINAL_W,
        TERMINAL_W,
        0, //timeout
        NULL);

    if (ptyInM == INVALID_HANDLE_VALUE) {
        fail=true;
        snprintf(errorText,256,"Could not open ptyM: %s.",ptyName);
        return false;
    }

    ptyInS = CreateFile (
        (LPCSTR) ptyName,
        GENERIC_READ,
        0,
        &sAtr,
        OPEN_EXISTING,
        FILE_FLAG_NO_BUFFERING,
        NULL);

    if (ptyInS == INVALID_HANDLE_VALUE) {
        fail=true;
        snprintf(errorText,256,"Could not open ptyS: %s.",ptyName);
        CloseHandle(ptyInM);
        return false;
    }

    snprintf(ptyName,256,"\\\\.\\pipe\\%08lx_LEML_PTY_OUT",(unsigned long)GetCurrentProcessId());
    printf ("ptyOut: %s\n",ptyName);

    ptyOutM = CreateNamedPipe (
        (LPCSTR) ptyName,
        PIPE_ACCESS_INBOUND,
        PIPE_NOWAIT,
        1,
        TERMINAL_W,
        TERMINAL_W,
        0,  //timeout
        NULL);

    if (ptyOutM == INVALID_HANDLE_VALUE) {
        fail=true;
        snprintf(errorText,256,"Could not open ptyM: %s.",ptyName);
        CloseHandle(ptyInS);
        CloseHandle(ptyInM);
        return false;
    }

    ptyOutS = CreateFile (
        (LPCSTR) ptyName,
        GENERIC_WRITE,
        0,
        &sAtr,
        OPEN_EXISTING,
        FILE_FLAG_NO_BUFFERING,
        NULL);

    if (ptyOutS == INVALID_HANDLE_VALUE) {
        fail=true;
        snprintf(errorText,256,"Could not open ptyS: %s.",ptyName);
        CloseHandle(ptyOutM);
        CloseHandle(ptyInS);
        CloseHandle(ptyInM);
        return false;
    }

    ptyOpen = true;


    ZeroMemory (&prInfo, sizeof(PROCESS_INFORMATION));
    ZeroMemory (&stInfo, sizeof(STARTUPINFO));
    stInfo.cb = sizeof(stInfo); 
    stInfo.hStdError = ptyOutS;
    stInfo.hStdOutput = ptyOutS;
    stInfo.hStdInput = ptyInS;
    stInfo.dwXCountChars = (DWORD)width;
    stInfo.dwYCountChars = (DWORD)((mode==modeHR)?heightHR:heightLR);
    stInfo.wShowWindow = SW_HIDE;
    stInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USECOUNTCHARS |  STARTF_USESHOWWINDOW;

    if (!CreateProcess (
        applName,
        cmdLine,
        NULL,
        NULL,
        TRUE,
        // CREATE_NEW_CONSOLE,
        DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
        NULL, //ENV!!
        NULL, //CWD!!
        &stInfo,
        &prInfo
    )) {
        fail = true;
        snprintf(errorText,256,"Could not CreateProcess.");
        CloseHandle(ptyOutS);
        CloseHandle(ptyOutM);
        CloseHandle(ptyInS);
        CloseHandle(ptyInM);
        ptyOpen = false;
        return false;
    }

    CloseHandle(ptyOutS);
    CloseHandle(ptyInS);
    CloseHandle(prInfo.hThread);
    ptyIn = ptyInM;
    ptyOut = ptyOutM;
    pidHandle = prInfo.hProcess;
    pid = prInfo.dwProcessId;
    commandRunning = true;

    return true;
#else
    int ptyM, ptyS;
    int i;
    struct termios termp;
    int flags;

    if (commandRunning)
        return false;

    fail=false;

    i=openpty(&ptyM,&ptyS,NULL,NULL,NULL);
    if (i<0) {
        fail = true;
        snprintf(errorText,256,"Could not open pty.");
        return false;
    }
    i=tcgetattr(ptyM, &termp);
    // cfmakeraw(&termp);
    i|=tcsetattr(ptyM, TCSANOW, &termp);
    flags=fcntl(ptyM, F_GETFL);
    flags|=O_NONBLOCK;
    i|=fcntl(ptyM, F_SETFL, flags);
    if (i<0) {
        fail = true;
        snprintf(errorText,256,"Could not modify pty mode");
        close(ptyM);
        close(ptyS);
        return false;
    }
    ptyOpen = true;

    switch (pid=fork()) {
    case -1:
        fail = true;
        snprintf(errorText,256,"Could not fork");
        close(ptyM);
        close(ptyS);
        ptyOpen = false;
        // printf("LOL FAIL FORK\n");
        return false;
        break;
    case 0:
        setsid();
        dup2(ptyS, STDIN_FILENO);
        dup2(ptyS, STDOUT_FILENO);
        dup2(ptyS, STDERR_FILENO);
        if (ioctl(ptyS, TIOCSCTTY, NULL) < 0)
            exit(1);
        close(ptyM);
        close(ptyS);
        clearenv();
        // printf("I AM FORK\n");
        execl("./load","./load","test",NULL);
        printf("LOL FAIL EXEC");
        exit(1);
        break;
    default:
        commandRunning = true;
        close(ptyS);
        pty=ptyM;
        // printf("SUCCESS FORK\n");
        break;
    }
    return true;
#endif
}
bool-LemlTerm::runCommand(){
#ifdef LEML_WIN32
char ptyName[256];
句柄ptyInM=NULL;
句柄ptyInS=NULL;
句柄ptyOutM=NULL;
句柄ptyOutS=NULL;
安全属性sAtr;
LPCSTR applName=“load.exe”;
LPSTR cmdLine=(LPSTR)“load.exe测试”;
STARTUPINFO;
处理信息prInfo;
如果(命令运行)
返回false;
失败=错误;
sAtr.nLength=sizeof(安全属性);
sAtr.bInheritHandle=真;
sAtr.lpSecurityDescriptor=NULL;
snprintf(ptyName,256,“\\\.\\pipe\\%08lx\u LEML\u PTY\u IN”,(无符号长)GetCurrentProcessId();
printf(“ptyIn:%s\n”,ptyName);
ptyInM=CreateNamedPipe(
(LPCSTR)ptyName,
管道进出口,
派普·努瓦特,
1.
码头西,
码头西,
0,//超时
无效);
if(ptyInM==无效的句柄值){
失败=正确;
snprintf(errorText,256,“无法打开ptyM:%s.”,ptyName);
返回false;
}
ptyInS=CreateFile(
(LPCSTR)ptyName,
泛读,
0,
&萨特,
开放式,
文件\u标志\u无缓冲,
无效);
if(ptyInS==无效的句柄值){
失败=正确;
snprintf(errorText,256,“无法打开ptyS:%s.”,ptyName);
闭合手柄(PTYIM);
返回false;
}
snprintf(ptyName,256,“\\\.\\pipe\\%08lx\u LEML\u PTY\u OUT”,(无符号长)GetCurrentProcessId());
printf(“ptyOut:%s\n”,ptyName);
ptyOutM=CreateNamedPipe(
(LPCSTR)ptyName,
管道入口入口入口,
派普·努瓦特,
1.
码头西,
码头西,
0,//超时
无效);
if(ptyOutM==无效的句柄值){
失败=正确;
snprintf(errorText,256,“无法打开ptyM:%s.”,ptyName);
闭合手柄(ptyInS);
闭合手柄(PTYIM);
返回false;
}
ptyOutS=CreateFile(
(LPCSTR)ptyName,
你写什么,
0,
&萨特,
开放式,
文件\u标志\u无缓冲,
无效);
if(ptyOutS==无效的句柄值){
失败=正确;
snprintf(errorText,256,“无法打开ptyS:%s.”,ptyName);
关闭手柄(PTYOTM);
闭合手柄(ptyInS);
闭合手柄(PTYIM);
返回false;
}
ptyOpen=true;
零内存(&prInfo,sizeof(进程信息));
零内存(&stInfo,sizeof(STARTUPINFO));
stInfo.cb=sizeof(stInfo);
stInfo.hStdError=ptyOutS;
stInfo.hStdOutput=ptyOutS;
stInfo.hStdInput=ptyInS;
stInfo.dwXCountChars=(DWORD)宽度;
stInfo.dwYCountChars=(DWORD)((mode==modeHR)?heightHR:heightLR);
stInfo.wShowWindow=SW_HIDE;
stInfo.dwFlags |=STARTF_USESTDHANDLES | STARTF_USECOUNTCHARS | STARTF_USESHOWWINDOW;
如果(!CreateProcess(
applName,
cmdLine,
无效的
无效的
是的,
//创建新的控制台,
分离流程|创建新流程|团队,
NULL,//ENV!!
NULL,//CWD!!
&斯丁福,
&普林福
)) {
失败=正确;
snprintf(errorText,256,“无法创建进程”)
bool LemlTerm::perform() {
    bool nextline;
    unsigned short canRead1, canRead2, canWrite1, canWrite2;
#ifdef LEML_WIN32
    DWORD n;
#else
    ssize_t n;
#endif

    fail = false;

    ++blinkCount;
    if(ringCount)
        --ringCount;

    if (commandRunning) {
#ifdef LEML_WIN32
        if (WaitForSingleObject(pidHandle,0) != WAIT_TIMEOUT) {
            CloseHandle(pidHandle);
            commandRunning = false;
        }
#else
        if (waitpid (pid, NULL, WNOHANG) > 0 )
            commandRunning = false;
#endif
    }
    //TODO: how and when to close pty!!

    canWrite1 = (outRd - outWr - 1)&0xff;
    if(canWrite1 > (256-outWr)) {
        canWrite2 = canWrite1 + outWr - 256;
        canWrite1 -= canWrite2;
    } else {
        canWrite2 = 0;
    }

    printf("rd:%hu wr:%hu cw:%hu+%hu=%hu\n",outRd, outWr, canWrite1, canWrite2, canWrite1+canWrite2);

    if (canWrite1) {
#ifdef LEML_WIN32
        ReadFile(ptyOut, outputBuffer+outWr, (DWORD)canWrite1, &n, NULL);
#else       
        n=read(pty, outputBuffer+outWr, (size_t)canWrite1);
#endif
        printf("%hu\n",(unsigned short*)n);
        if (n>0) {
            outWr += n;
            outWr &= 0xff;
            if ((n==canWrite1) && canWrite2) {
#ifdef LEML_WIN32
                ReadFile(ptyOut, outputBuffer+outWr, (DWORD)canWrite2, &n, NULL);
#else       
                n=read(pty, outputBuffer+outWr, (size_t)canWrite2);
#endif
                printf("%hu\n",(unsigned short*)n);
                if (n>0) {
                    outWr += n;
                    outWr &= 0xff;
                }
            }
        }
    }

    for (; outWr!=outRd; outRd=(outRd+1)&0xff) {
        screenChanged = true;
        nextline=false;

        switch (outputBuffer[outRd]) {
        case 7: //bell
            ringCount += 0x10;
            break;
        case 8: //backspace
            outputBuffer[outRd]=' ';
            if (cursorX>0) {
                --cursorX;
            } else if (cursorY>0) {
                --cursorY;
                cursorX = width;
            }
            textBuffer[(bufferY+cursorY)%heightHR][cursorX] = ' ';
            break;
        case 9: //tab
            cursorX &= ~0x7;
            cursorX += 8;
            if(cursorX >= width) {
                cursorX = 0;
                nextline = true;
            }
            break;
        case 10: //newline
            cursorX = 0;
        case 11: //vt
        case 12: //ff
            nextline = true;
            break;
        case 13: //cr
            cursorX = 0;
            break;
        default:
            textBuffer[(bufferY+cursorY)%heightHR][cursorX] = outputBuffer[outRd];
            ++cursorX;
            if(cursorX >= width) {
                cursorX = 0;
                nextline = true;
            }
        }

        if (nextline) {
            // cursorX = 0;
            if (cursorY < ((mode==modeHR)?heightHR:heightLR) - 1) {
                ++cursorY;
            } else {
                ++bufferY;
                bufferY %= heightHR;
            }
            // memset(textBuffer[(bufferY+cursorY)%heightHR],(bufferY+cursorY)%heightHR,width);
            memset(textBuffer[(bufferY+cursorY)%heightHR],' ',width);

            //optional! for simulate scrolling delay...
            outRd=(outRd+1)&0xff;
            break; 
        }
        // //SUPER SLOW
        // outRd=(outRd+1)&0xff;
        // break; 
    }

    canRead1 = (inWr-inRd)&0x1f;
    if(canRead1 > (32-inRd)) {
        canRead2 = canRead1 + inRd - 32;
        canRead1 -= canRead2;
    } else {
        canRead2 = 0;
    }


    if (canRead1) {
        printf("rd:%hu wr:%hu cr:%hu+%hu=%hu\n",inRd, inWr, canRead1, canRead2, canRead1+canRead2);
#ifdef LEML_WIN32
        WriteFile(ptyIn, inputBuffer+inRd, (DWORD)canRead1, &n, NULL);
#else       
        n=write(pty, inputBuffer+inRd, (size_t)canRead1);
#endif
        printf("%hu\n",(unsigned short*)n);
        if (n>0) {
            inRd += n;
            inRd &= 0x1f;
            if ((n==canRead1) && canRead2) {
#ifdef LEML_WIN32
                WriteFile(ptyIn, inputBuffer+inRd, (DWORD)canRead2, &n, NULL);
#else
                n=write(pty, inputBuffer+inRd, (size_t)canRead2);
#endif
                printf("%hu\n",(unsigned short*)n);
                if (n>0) {
                    inRd += n;
                    inRd &= 0x1f;
                }
            }
        }
    }

    return updateScreen();
}