导航:[首页]->[windows]->[Windows 创建进程经验小结]

CreateProcess

Windows创建进程的API是CreateProcess。用法见Sample

    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
    );

    #include <windows.h>
    #include <stdio.h>
    #include <tchar.h>

    void _tmain( int argc, TCHAR *argv[] )
    {
        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        ZeroMemory( &si, sizeof(si) );
        si.cb = sizeof(si);
        ZeroMemory( &pi, sizeof(pi) );

        if( argc != 2 )
        {
            printf("Usage: %s [cmdline]\n", argv[0]);
            return;
        }

        // Start the child process. 
        if( !CreateProcess( NULL,   // No module name (use command line)
            argv[1],        // Command line
            NULL,           // Process handle not inheritable
            NULL,           // Thread handle not inheritable
            FALSE,          // Set handle inheritance to FALSE
            0,              // No creation flags
            NULL,           // Use parent's environment block
            NULL,           // Use parent's starting directory 
            &si,            // Pointer to STARTUPINFO structure
            &pi )           // Pointer to PROCESS_INFORMATION structure
        ) 
        {
            printf( "CreateProcess failed (%d).\n", GetLastError() );
            return;
        }

        // Wait until child process exits.
        WaitForSingleObject( pi.hProcess, INFINITE );

        // Close process and thread handles. 
        CloseHandle( pi.hProcess );
        CloseHandle( pi.hThread );
    }

参数

若不带参数,我们可以将程序放到lpApplicationName或者lpCommandLine参数。

若带参数,可以将整个字符串放到lpCommandLine参数。

若程序和参数分开,那么程序路径放在lpApplicationName,参数放在lpCommandLine。同时必须保证lpCommandLine前面有一个空格

从其他桌面启动

为了从另一个Desktop启动,可以对lpStartupInfo的STARTUPINFO结构成员lpDesktop赋值为特定的桌面名称。线程Desktop相关见这里

GetExitCodeProcess

当我们需要获取程序返回值时,使用GetExitCodeProcess

ShellExecuteEx

对于一般的情况,ShellExecuteEx相比CreateProcess更加容易使用,特别是依赖UAC时,就必须使用ShellExecuteEx了,此时CreateProcess会返回错误码ERROR_ELEVATION_REQUIRED,具体细节见这里。它有个兄弟函数ShellExecute。Sample见这里

为了获得进程句柄,需要附带标志位SEE_MASK_NOCLOSEPROCESS

我们可以通过CreateProcess附带CREATE_SUSPENDED标志,并判断是否返回错误ERROR_ELEVATION_REQUIRED来判断某个可执行文件是否需要使用管理员启动

ShellExecuteEx不支持CreateProcess那种将程序和参数放到同一字符串中直接运行的用法,你必须将程序名称传给SHELLEXECUTEINFO结构的lpFile,参数传给lpParameters。参数无前面空格的要求

UAC提权

用过Vista或者更高版本的Windows系统的人都知道,某些程序起来的时候会弹出一个UAC的对话框,那么自己写的程序怎么让Windows弹出此确认呢?

方法一是在manifest中添加特殊的字段,见这里

    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
        <security>
            <requestedPrivileges>
                <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
            </requestedPrivileges>
        </security>
    </trustInfo>

在VS2008中只需要动手选择一下就好了,项目属性->配置属性->连接器->manifest file : 将UAC Execution Level勾选为requireAdministrator

简单绕过UAC

前面的方法大部分情况下能够很好的工作,但是有一些例外,例如开机启动的程序。因为用uac提权需要弹出一个框来用户确认,但是开机启动很显然这做不到,所以系统会直接拦掉。那怎么解决呢?

实际上我们可以用ShellExecuteEx来做到动态提权,对于开机启动的情形,我们可以不使用menifest的requireAdministrator属性,开机用普通用户权限启动,当工作的时候再动态提权。具体参考下面的runas

    SHELLEXECUTEINFO sei = {0};
    sei.cbSize = sizeof(SHELLEXECUTEINFOW);
    // ... other option
    sei.lpFile = your_path;
    sei.lpVerb = _T("runas");
    sei.lpDirectory = NULL;
    ShellExecuteEx(&sei);

当需要提权时,我们可以将自己退出,在退出之前,用runas启动一个新的自身实例

Bug

当我们用ShellExecuteEx去启动某个程序时,若需要提权,那么uac会弹出一个对话框来用户确认,在这个对话框返回之前,ShellExecuteEx函数不会返回,也就是调用ShellExecuteEx的线程会被阻塞,若该线程有其他后台逻辑,慎用

消息接管

笔者曾用一个后台线程处理各种逻辑,通过Windows的消息机制和其他线程通信(避免使用锁)。此线程平时都阻塞在MsgWaitForMultipleObjects函数中。在这些消息处理程序中,就包含ShellExecuteEx的调用,很明显,当弹出UAC对话框时,整个线程被阻塞了。

此时,其他线程还会向它发送消息,并且PostThreadMessage也是成功的,我最初认为ShellExecuteEx返回之后,MsgWaitForMultipleObjects应该能够立即返回并且收到此前被Post的消息。但实际上这些消息虽然Post成功,但就是没有收到,丢弃了还是被其他地方处理掉了?

据查证,这些消息直接被UAC的那个框给处理掉了。

解决办法就是创建一个子线程,在这个子线程中调用ShellExecuteEx,同时WaitForSingleObject这个线程。虽然在ShellExecuteEx返回之前,子线程不会结束,此线程也就是阻塞的,但是ShellExecuteEx返回之后,阻塞过程中被Post的消息是不会丢失的。MsgWaitForMultipleObjects会立即返回并收到那些消息。

参考

  1. http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
  2. http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx
  3. http://msdn.microsoft.com/en-us/library/windows/desktop/bb762154(v=vs.85).aspx
  4. http://linux.die.net/man/2/fork
  5. http://blogs.itecn.net/blogs/winvista/archive/2006/07/25/3021.aspx
  6. http://blog.csdn.net/magictong/article/details/4804862
  7. http://www.shily.net/topic/bypassing-uac-since-the-launch-on-vista-win7/