gcc Bad File Descriptor Error When Piping Input Into It

Viewed 240

I figured out that you can compile c code with gcc from stdin with gcc -x c - and the command cat main.c | gcc -x c - sucesfully compiles it.

So, i have a vb6 application that has c code in a string, and i want to pipe that c code into gcc with win32 api, i have mingw installed btw.

The following basic code creates the input pipe and invokes gcc, However immediatly after CreateProcess the following is printed to the console:

cc1.exe: fatal error: stdout: Bad file descriptor
compilation terminated.

That is, that error message is printed before i put anything into the input pipe

Private Declare Function CreatePipe Lib "kernel32" (phReadPipe As Long, phWritePipe As Long, lpPipeAttributes As SECURITY_ATTRIBUTES, ByVal nSize As Long) As Long
Private Declare Function SetHandleInformation Lib "kernel32" (ByVal hObject As Long, ByVal dwMask As Long, ByVal dwFlags As Long) As Long
Private Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessW" (ByVal lpApplicationName As Long, ByVal lpCommandLine As Long, ByVal lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, ByVal lpEnvironment As Long, ByVal lpCurrentDriectory As Long, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, ByVal lpBuffer As Long, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, Optional ByVal lpOverlapped As Long = 0) As Long
Private Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, ByVal lpBuffer As Long, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, Optional ByVal lpOverlapped As Long = 0) As Long
Private Declare Function GetStdHandle Lib "kernel32" (ByVal nStdHandle As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function AllocConsole Lib "kernel32" () As Long
Private Declare Function FreeConsole Lib "kernel32" () As Long

Private Declare Function GetLastError Lib "kernel32" () As Long

Private Type STARTUPINFO
        cb As Long
        lpReserved As String
        lpDesktop As String
        lpTitle As String
        dwX As Long
        dwY As Long
        dwXSize As Long
        dwYSize As Long
        dwXCountChars As Long
        dwYCountChars As Long
        dwFillAttribute As Long
        dwFlags As Long
        wShowWindow As Integer
        cbReserved2 As Integer
        lpReserved2 As Long
        hStdInput As Long
        hStdOutput As Long
        hStdError As Long
End Type
Private Type PROCESS_INFORMATION
        hProcess As Long
        hThread As Long
        dwProcessId As Long
        dwThreadId As Long
End Type
Private Type SECURITY_ATTRIBUTES
        nLength As Long
        lpSecurityDescriptor As Long
        bInheritHandle As Long
End Type

Const HANDLE_FLAG_INHERIT As Long = 1
Const STARTF_USESTDHANDLES As Long = &H100

Public Function Execute(CmdLine As String) As Boolean
ExecuteWithStdIn "gcc -x c -", "#error if gcc receives this code it shoud complain about this error directive"
End Function

'Pipes String StdIn into The Stdin Of The Child Process
'Output Of Child Process Is Redirected Into My StdOut
Public Function ExecuteWithStdIn(CmdLine As String, StdIn As String) As Boolean
Dim sAttr As SECURITY_ATTRIBUTES
Dim E As Long
Dim stdin_read As Long
Dim stdin_write As Long
AllocConsole



sAttr.nLength = LenB(sAttr)
sAttr.bInheritHandle = 1

E = CreatePipe(stdin_read, stdin_write, sAttr, 0)
E = SetHandleInformation(stdin_read, HANDLE_FLAG_INHERIT, 0)

Dim CmdLine As String

Dim Proci As PROCESS_INFORMATION 'all 0s
Dim Sti As STARTUPINFO
Sti.cb = LenB(Sti)
Sti.hStdError = GetStdHandle(-11)
Sti.hStdOutput = GetStdHandle(-11) 'Piping gcc output to my output
Sti.hStdInput = stdin_read         'Capturing the input so we may pipe the c code
Sti.dwFlags = STARTF_USESTDHANDLES

E = CreateProcess(0, StrPtr(CmdLine), 0, 0, 1, 0, 0, 0, Sti, Proci)
'### When The Code Reaches Here, E = 1 (Success) And gcc has already
'### outputted to stdout that Bad File Descriptor error message

E = GetLastError

Dim Buffer() As Byte

Dim BufferPtr As Long
Dim BufferSize As Long
Dim BytesWritten As Long

Let Buffer = StrConv(StdIn, vbFromUnicode)

BufferPtr = VarPtr(Buffer(0))
BufferSize = UBound(Buffer)

Do
    If BufferSize > 4096 Then
        E = WriteFile(stdin_write, BufferPtr, 4096, BytesWritten)
    Else
        E = WriteFile(stdin_write, BufferPtr, BufferSize, BytesWritten)
    End If
    If E = 0 Then Exit Do
    BufferPtr = BufferPtr + BytesWritten
    BufferSize = BufferSize - BytesWritten
Loop While BufferSize > 0

E = CloseHandle(stdin_read)

FreeConsole

End Function
2 Answers

Can your stdout be inherited by child process ? Actually, it needs. the same as stderr.

Sti.hStdOutput = GetStdHandle(-11) 'Piping gcc output to my output

You can refer to the following C++ code which works for me:

// 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.
SECURITY_ATTRIBUTES s;
s.nLength = sizeof(SECURITY_ATTRIBUTES);
s.bInheritHandle = TRUE;
s.lpSecurityDescriptor = NULL;
HANDLE hin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, &s, OPEN_EXISTING, 0, 0);
SetStdHandle(STD_INPUT_HANDLE, hin);
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
//siStartInfo.hStdInput = hin;
//siStartInfo.hStdInput = NULL;
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"));

Thanks so much @YangXiaoPro for your answer, your comment on handle inheritance led me to figure out that this line:

SetHandleInformation(stdin_read, HANDLE_FLAG_INHERIT, 0)

that stops the pipe reader from being inherited, is wrong. It should instead stop the pipe writer from being inherited

SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0) 'fixed

Related