Search code examples
javajnacreateprocessnative-methods

how to get the process output when using jna and CreateProcessW


I'm trying to figure out how to read the standard out/err from the process I've created with CreateProcessW. I looked at the docs, googled and searched this list but I didn't find good pointers/samples yet :)

Here's what I came up with so far (it's working fine on windows, it's a relevant snippet from my java code):

Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class); 
Kernel32.StartupInfo startupInfo = new Kernel32.StartupInfo(); 
Kernel32.ProcessInfo processInformation = new Kernel32.ProcessInfo(); 

if (!kernel32.CreateProcessW(null, new WString(command), null, null, false, 
  DETACHED_PROCESS, null, new WString(dir.getAbsolutePath()), startupInfo,     
  processInformation)) { 
        throw new IOException("Could not start process. Errno: " +    
            kernel32.GetLastError()); 
} 

kernel32.CloseHandle(processInformation.hProcess); 
kernel32.CloseHandle(processInformation.hThread); 

So... how can I grab the output from that process? Anyone has done that already and care sharing a sample?

Thanks guys for any help in advance.


Solution

  • To write to a console for a process created with CreateProcess function, MSDN suggests to create a child process and use anonymous pipes to redirect child process's standard input and output handles.

    Creating a Child Process with Redirected Input and Output

    Since JNA 3.3.0 Platform hasn't include all the Kernel32 functions we need, we need to provide the required JNA interfaces as follows: (NOTE JNA 4.0 provides Kernel32 for you)

    Kernel32.java:

    import java.util.HashMap;
    import java.util.Map;
    
    import com.sun.jna.Library;
    import com.sun.jna.Native;
    import com.sun.jna.Pointer;
    import com.sun.jna.win32.StdCallLibrary;
    import com.sun.jna.win32.W32APIFunctionMapper;
    import com.sun.jna.win32.W32APITypeMapper;
    import com.sun.jna.platform.win32.WinBase.SECURITY_ATTRIBUTES;
    import com.sun.jna.platform.win32.WinBase.STARTUPINFO;
    import com.sun.jna.platform.win32.WinDef.DWORD;
    import com.sun.jna.platform.win32.WinBase.PROCESS_INFORMATION;
    import com.sun.jna.platform.win32.WinNT.HANDLE;
    
    public interface Kernel32 extends StdCallLibrary {
    
        final static Map<String, Object> WIN32API_OPTIONS = new HashMap<String, Object>() {
    
            private static final long serialVersionUID = 1L;
    
            {
                put(Library.OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
                put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
            }
        };
    
        public Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("Kernel32", Kernel32.class, WIN32API_OPTIONS);
    
    /*
        BOOL WINAPI CreateProcess(
                __in_opt     LPCTSTR lpApplicationName,
                __inout_opt  LPTSTR lpCommandLine,
                __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
                __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
                __in         BOOL bInheritHandles,
                __in         DWORD dwCreationFlags,
                __in_opt     LPVOID lpEnvironment,
                __in_opt     LPCTSTR lpCurrentDirectory,
                __in         LPSTARTUPINFO lpStartupInfo,
                __out        LPPROCESS_INFORMATION lpProcessInformation
                );    
    */
        public boolean CreateProcess(
                String lpApplicationName, 
                String lpCommandLine, 
                SECURITY_ATTRIBUTES lpProcessAttributes, 
                SECURITY_ATTRIBUTES lpThreadAttributes,
                boolean bInheritHandles,
                DWORD dwCreationFlags,
                Pointer lpEnvironment,
                String lpCurrentDirectory,
                STARTUPINFO lpStartupInfo,
                PROCESS_INFORMATION lpProcessInformation
                );
    
        public HANDLE GetStdHandle(DWORD nStdHandle);
    
        public int GetLastError();
    }
    

    Then, the main part:

    RunTest.java:

    import java.nio.ByteBuffer;
    
    import com.sun.jna.Native;
    import com.sun.jna.Pointer;
    import com.sun.jna.platform.win32.WinBase.PROCESS_INFORMATION;
    import com.sun.jna.platform.win32.WinBase.SECURITY_ATTRIBUTES;
    import com.sun.jna.platform.win32.WinBase.STARTUPINFO;
    import com.sun.jna.platform.win32.WinDef.DWORD;
    import com.sun.jna.platform.win32.WinNT.HANDLE;
    import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
    import com.sun.jna.ptr.IntByReference;
    
    
    public class RunTest {
    
        static HANDLEByReference childStdInRead = new HANDLEByReference();
        static HANDLEByReference childStdInWrite = new HANDLEByReference();
        static HANDLEByReference childStdOutRead = new HANDLEByReference();
        static HANDLEByReference childStdOutWrite = new HANDLEByReference();
    
        static final int HANDLE_FLAG_INHERIT = 0x00000001;
        static final int HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002;
    
    
        static final int BUFSIZE = 4096;
        static final int GENERIC_READ = 0x80000000;
        static final int FILE_ATTRIBUTE_READONLY = 1;
        private static final int OPEN_EXISTING = 3;
        private static final DWORD STD_OUTPUT_HANDLE = new DWORD(-11);
        private static final int STARTF_USESTDHANDLES = 0x00000100;
    
        static HANDLE inputFile = null;
    
        static void createChildProcess(String cmd){
            String szCmdline = cmd;
    
            PROCESS_INFORMATION processInformation = new PROCESS_INFORMATION();
            STARTUPINFO startupInfo = new STARTUPINFO();
            startupInfo.cb = new DWORD(processInformation.size());
            startupInfo.hStdError = childStdOutWrite.getValue();
            startupInfo.hStdOutput = childStdOutWrite.getValue();
            startupInfo.hStdInput = childStdInRead.getValue();
            startupInfo.dwFlags |= STARTF_USESTDHANDLES;
    
            // Create the child process. 
            if (!Kernel32.INSTANCE.CreateProcess(
                    null, 
                    szCmdline, 
                    null, 
                    null, 
                    true, 
                    new DWORD(0x00000020), 
                    null, 
                    null, 
                    startupInfo, 
                    processInformation)){
                System.err.println(Kernel32.INSTANCE.GetLastError());
            }
            else {
                com.sun.jna.platform.win32.Kernel32.INSTANCE.WaitForSingleObject(processInformation.hProcess, 0xFFFFFFFF);
    
                com.sun.jna.platform.win32.Kernel32.INSTANCE.CloseHandle(processInformation.hProcess);
                com.sun.jna.platform.win32.Kernel32.INSTANCE.CloseHandle(processInformation.hThread);
            }
        }
    
        static void WriteToPipe() 
    
        // Read from a file and write its contents to the pipe for the child's STDIN.
        // Stop when there is no more data. 
        { 
            IntByReference dwRead = new IntByReference();
            IntByReference dwWritten = new IntByReference(); 
            ByteBuffer buf = ByteBuffer.allocateDirect(BUFSIZE);
            Pointer data = Native.getDirectBufferPointer(buf);
            boolean bSuccess = true;
    
            for (;;) 
            { 
                bSuccess = com.sun.jna.platform.win32.Kernel32.INSTANCE.ReadFile(inputFile, buf, BUFSIZE, dwRead, null);
                if ( ! bSuccess || dwRead.getValue() == 0 ) break; 
    
                bSuccess = com.sun.jna.platform.win32.Kernel32.INSTANCE.WriteFile(childStdInWrite.getValue(), data.getByteArray(0, BUFSIZE), dwRead.getValue(), dwWritten, null);
                if ( ! bSuccess ) break; 
            } 
    
            // Close the pipe handle so the child process stops reading. 
    
            if (!com.sun.jna.platform.win32.Kernel32.INSTANCE.CloseHandle(childStdInWrite.getValue())){ 
                System.err.println(Kernel32.INSTANCE.GetLastError()); 
            }
        }
    
        static void ReadFromPipe() 
    
        // 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. 
        { 
            IntByReference dwRead = new IntByReference();
            IntByReference dwWritten = new IntByReference(); 
            ByteBuffer buf = ByteBuffer.allocateDirect(BUFSIZE);
            Pointer data = Native.getDirectBufferPointer(buf);
            boolean bSuccess = true;
            HANDLE hParentStdOut = Kernel32.INSTANCE.GetStdHandle(STD_OUTPUT_HANDLE);
    
            // Close the write end of the pipe before reading from the 
            // read end of the pipe, to control child process execution.
            // The pipe is assumed to have enough buffer space to hold the
            // data the child process has already written to it.
    
            if (!com.sun.jna.platform.win32.Kernel32.INSTANCE.CloseHandle(childStdOutWrite.getValue())){ 
                System.err.println(Kernel32.INSTANCE.GetLastError()); 
            }
    
            for (;;) 
            { 
                bSuccess = com.sun.jna.platform.win32.Kernel32.INSTANCE.ReadFile( childStdOutRead.getValue(), buf, BUFSIZE, dwRead, null);
                if( ! bSuccess || dwRead.getValue() == 0 ) break; 
    
                bSuccess = com.sun.jna.platform.win32.Kernel32.INSTANCE.WriteFile(hParentStdOut, data.getByteArray(0, BUFSIZE), dwRead.getValue(), dwWritten, null);
                if (! bSuccess ) break; 
            } 
        }   
        /**
         * {@link http://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx}
         */
        public static void main(String[] args) {
    
            if (args.length < 1) {
                  System.err.println("Please specify a command.\n");
                  System.exit(1);
            }
    
            if (args.length < 2) {
                  System.err.println("Please specify an input file.\n");
                  System.exit(1);
            }
    
            SECURITY_ATTRIBUTES saAttr = new SECURITY_ATTRIBUTES();
            saAttr.dwLength = new DWORD(saAttr.size());
            saAttr.bInheritHandle = true;
            saAttr.lpSecurityDescriptor = null;
    
            // Create a pipe for the child process's STDOUT. 
            if (!com.sun.jna.platform.win32.Kernel32.INSTANCE.CreatePipe(childStdOutRead, childStdOutWrite, saAttr, 0)){
                System.err.println(Kernel32.INSTANCE.GetLastError());
            }
    
            // Ensure the read handle to the pipe for STDOUT is not inherited.
            if (!com.sun.jna.platform.win32.Kernel32.INSTANCE.SetHandleInformation(childStdOutRead.getValue(), HANDLE_FLAG_INHERIT, 0)){
                System.err.println(Kernel32.INSTANCE.GetLastError());;
            }
    
            // Create a pipe for the child process's STDIN. 
            if (!com.sun.jna.platform.win32.Kernel32.INSTANCE.CreatePipe(childStdInRead, childStdInWrite, saAttr, 0)){
                System.err.println(Kernel32.INSTANCE.GetLastError());
            }
    
            // Ensure the write handle to the pipe for STDIN is not inherited.
            if (!com.sun.jna.platform.win32.Kernel32.INSTANCE.SetHandleInformation(childStdInWrite.getValue(), HANDLE_FLAG_INHERIT, 0)){
                System.err.println(Kernel32.INSTANCE.GetLastError());;
            }
    
            createChildProcess(args[0]);
    
            inputFile = com.sun.jna.platform.win32.Kernel32.INSTANCE.CreateFile(
                    args[1], 
                    GENERIC_READ, 
                    0, 
                    null, 
                    OPEN_EXISTING, 
                    FILE_ATTRIBUTE_READONLY, 
                    null);
    
            // 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.
    
               WriteToPipe(); 
               System.out.println( "\n->Contents of \""+args[1]+"\" written to child STDIN pipe.\n");
    
            // Read from pipe that is the standard output for child process. 
    
               System.out.println( "\n->Contents of child process STDOUT:\n\n" + args[1]);
               ReadFromPipe(); 
    
               System.out.println("\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. 
    
    
        }
    
    }
    

    The original MSDN program only asks for one argument. But, the modified Runtest java program will need two arguments: (1) the command line; (2) the input file.

    Example usage:

    java -jar RunTest.jar "C:\\Program Files\\Java\\jre6\\bin\\java.exe -version" "C:\\Documents and Settings\\Administrator\\Desktop\\test.txt"
    

    Example output of the program:

    ->Contents of "C:\\Documents and Settings\\Administrator\\Desktop\\test.txt" written to child STDIN pipe.
    
    
    ->Contents of child process STDOUT:
    
    C:\\Documents and Settings\\Administrator\\Desktop\\test.txt
    java version "1.6.0_29"
    Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
    Java HotSpot(TM) Client VM (build 20.4-b02, mixed mode, sharing)
    
    ->End of parent execution.
    

    If you want to see an elaborate version... WindowsXPProcess.java