SQLite

Ticket Change Details
Login
Overview

Artifact ID: 385fc2cfff2f2e1c60ab54e6929294023795f511a2946208dd699110d6098899
Ticket: accfef33172b956d4e6c01ac047e046ea02dd0a3
Unicode output from the CLI subprocess not displayed correctly on Windows.
User & Date: drh 2024-04-29 17:44:43
Changes

  1. icomment:
    Repro:  Compile and run the source code below, altering the path to sqlite3.exe as appropriate for your system.
    This code is based on the public sample for executing a child process with redirected output.
    
    Expected output: The Unicode text provided
    
    Actual output: Corrupted text at the end
    
    ~~~
    #include <io.h>
    #include <fcntl.h>
    #include <windows.h>
    #include <tchar.h>
    #include <stdio.h>
    #include <strsafe.h>
    #define BUFSIZE 8000 // Increased to show this isn't the error
     
    HANDLE g_hChildStd_IN_Rd = NULL;
    HANDLE g_hChildStd_IN_Wr = NULL;
    HANDLE g_hChildStd_OUT_Rd = NULL;
    HANDLE g_hChildStd_OUT_Wr = NULL;
    void CreateChildProcess(void);
    void ReadFromPipe(void);
    void ErrorExit(PCTSTR);
    int _tmain(int /*argc*/, TCHAR* /*argv*/[])
    {
        _setmode(_fileno(stdin), _O_U8TEXT);
        _setmode(_fileno(stdout), _O_U8TEXT);
        _setmode(_fileno(stderr), _O_U8TEXT);
        SECURITY_ATTRIBUTES saAttr;
        wprintf(L"\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();
        // Close the child process's input pipe so it stops reading.
        if (!CloseHandle(g_hChildStd_IN_Wr))
            ErrorExit(TEXT("StdInWr CloseHandle"));
        // Read from pipe that is the standard output for child process.
        wprintf(L"\n->Contents of child process STDOUT:\n\n");
        ReadFromPipe();
        wprintf(L"\n->End of parent execution.\n");
        // The remaining open handles are cleaned up when this process terminates.
        // To avoid resource leaks in a larger application, close handles explicitly.
        return 0;
    }
    
    void CreateChildProcess()
    // Create a child process that uses the previously created pipes for STDIN and STDOUT.
    {
        TCHAR szCmdline[] = TEXT(
            "\"C:\\Program Files\\SQLite\\sqlite3.exe\" --cmd \"SELECT "
            "'aAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAA "
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAA';\"");
        // Normal output works:
         //TCHAR szCmdline[] = TEXT(
         //    "\"C:\\Windows\\System32\\cmd.exe\" /c echo hi");
        PROCESS_INFORMATION piProcInfo;
        STARTUPINFO siStartInfo;
        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
            NULL, // 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.
            CloseHandle(piProcInfo.hProcess);
            CloseHandle(piProcInfo.hThread);
            // Close handles to the stdin and stdout pipes no longer needed by the child process.
            // If they are not explicitly closed, there is no way to recognize that the child process
    has ended.
            CloseHandle(g_hChildStd_OUT_Wr);
            CloseHandle(g_hChildStd_IN_Rd);
        }
    }
    
    void ReadFromPipe(void)
    // Read output from the child process's pipe for STDOUT
    // and write to the parent process's STDOUT.
    // Stop when there is no more data.
    {
        DWORD dwRead, dwWritten;
        CHAR chBuf[BUFSIZE];
        BOOL bSuccess = FALSE;
        for (;;)
        {
            bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
            if (!bSuccess || dwRead == 0)
                break;
            WCHAR outBuf[BUFSIZE];
            dwWritten = MultiByteToWideChar(CP_UTF8, 0, chBuf, dwRead, outBuf, BUFSIZE);
            outBuf[dwWritten] = 0;
            wprintf(L"%s", outBuf);
        }
    }
    
    void ErrorExit(PCTSTR 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);
    }
    ~~~
    
  2. login: "drh"
  3. mimetype: "text/x-markdown"
  4. private_contact changed to: "5840dffc1663561cf96cb9eb1563447ad11d8bbe"
  5. severity changed to: "Cosmetic"
  6. status changed to: "Open"
  7. title changed to:
    Unicode output from the CLI subprocess not displayed correctly on Windows.
    
  8. type changed to: "Support_Request"