windows 服务中启动交互式程序

最近项目中需要在windows服务中启动交互式程序,还是费了点劲,记录一下

交互式程序也就是需要与人交互的程序,比如带有界面的程序,需要接收用户鼠标键盘消息的程序。比如 notepad.exe,cmd.exe

非交互式程序也就是跑在后台,不需要人工干预,自己能跑得欢的程序 比如各种服务端程序,类似sshd.exe,各种bat脚本等

 

windows程序中启动第三方程序通常有几种方式

1)UINT Win Exec(LPCSTR lpCmdLine, UINT uCmdShow); 用得很少

    WinExec("Notepad.exe",SW_SHOW);//启动Notepad.exe

2)int system(char const* _Command);

    system("dir >dir.txt");//把当前目录的内容写入dir.txt

3)HINSTANCE ShellExecute(HWND hwnd, LPCTSTR lpOperation, LPCTSTR lpFile, LPCTSTR lpParameters, LPCTSTR lpDirectory, INT nShowCmd);

   ShellExecute (NULL,”open”,”readme.txt”,NULL,NULL,SW_SHOW);//打开readme.txt

4)BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

lpApplicationName,要启动的应用程序名,如果为NULL,则取lpCommandLine以空格分隔的第一个参数为程序名

lpCOmmandLine,命令行参数,如果为NULL,则用lpApplicationName为命令行参数

lpProcessAttributes,指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承,如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。

lpThreadAttributes,同lpProcessAttribute,不过这个参数决定的是线程是否被继承.通常置为NULL.

bInheritHandles,指示新进程是否从调用进程处继承了句柄

dwCreationFlags,指定附加的、用来控制优先类和进程的创建的标志。

lpEnvironment,指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。

lpCurrentDirectory,指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包含驱动器名的绝对路径。如果这个参数为空,新进程将使用与调用进程相同的驱动器和目录。

lpStartupInfo,指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。

lpProcessInformation,指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。

 

以下这段解释来自于 https://www.jb51.net/article/82757.htm,解释为什么服务里无法启动带交互式界面的程序

Windows提供了三类对象:用户界面对象(User Interface)、GDI对象和内核对象。内核对象有安全性,而前两者没有。为了对前两者提供安全性,通过工作站对象(Window station)和桌面对象(Desktop)来管理用户界面对象,因为工作站对象和桌面对象有安全特性。简单说来,工作站是一个带有安全特性的对象,它与进程相关联,包含了一个或多个桌面对象。当工作站对象被创建时,它被关联到调用进程上,并且被赋给当前Session。交互式工作站WinSta0,是唯一一个可以显示用户界面,接受用户输入的工作站。它被赋给交互式用户的登录Session,包含了键盘、鼠标和显示设备。所有其他工作站都是非交互式的,这就意味着它们不能显示用户界面,不能接受用户的输入。当用户登录到一台启用了终端服务的计算机上时,每个用户都会启动一个Session。每个Session都会与自己的交互式工作站相联系。桌面是一个带有安全特性的对象,被包含在一个窗口工作站对象中。一个桌面对象有一个逻辑的显示区域,包含了诸如窗口、菜单、钩子等等这样的用户界面对象。

在Vista之前,之所以可以通过打开Winsta0和缺省桌面显示对话框,是因为不管是服务还是第一个登录的交互式用户,都是登录到Session 0中。因此,服务程序可以通过强制打开WinSta0和桌面来获得交互能力。

然而,在Vista和Windows2008中,Session 0专用于服务和其他不与用户交互的应用程序。第一个登录进来,可以进行交互式操作的用户被连到Session 1上。第二个登录进行的用户被分配给Session 2,以此类推。Session 0完全不支持要与用户交互的进程。如果采取在服务进程中启动子进程来显示对话框,子对话框将无法显示;如果采取用OpenWindowStation系统API打开WinSta0的方法,函数调用会失败。总之,Vista和Windows2008已经堵上了在Session 0中产生界面交互的路。这就是原因所在。

那么,是否真的没法在服务中弹出对话框了呢?对于服务进程自身来说,确实如此,操作系统已经把这条路堵上了。但是,我们想要的并不是“在服务进程中弹出对话框”,我们想要的不过是“当服务出现某些状况的时候,在桌面上弹出对话框”。既然在Session 0中无法弹出对话框,而我们看到的桌面是Session X,并非Session 0,很自然的一个想法是:能不能让Session 0通知其他的Session,让当前桌面正显示着的Session弹一个对话框呢?

事实上是可以的,那么,就需要获取当前登录进来的用户session X

代码如下

BOOL CreateMyProcess3(LPWSTR command_line, DWORD *exit_code)
{
	PROCESSENTRY32 proc_entry;
    
	DWORD session_id ;

    HANDLE hToken = NULL;
    HANDLE hTokenDup = NULL;
    LPVOID pEnv = NULL;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

	 HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snap == INVALID_HANDLE_VALUE) {
        printf("CreateToolhelp32Snapshot() failed %lu", GetLastError());
        return FALSE;
    }
    ZeroMemory(&proc_entry, sizeof(proc_entry));
    proc_entry.dwSize = sizeof(PROCESSENTRY32);
    if (!Process32First(snap, &proc_entry)) {
        printf("Process32First() failed %lu", GetLastError());
        CloseHandle(snap);
        return FALSE;
    }
    do {
        //    找出explorer进程所属用户的sessionID
        if (_tcsicmp(proc_entry.szExeFile, _T("explorer.exe")) == 0) {
		
             DWORD explorer_session_id = 0;
		 if (ProcessIdToSessionId(proc_entry.th32ProcessID, &explorer_session_id) )             
           {
                session_id = explorer_session_id;
                break;
            }

        }
    } while (Process32Next(snap, &proc_entry));
    CloseHandle(snap);

    //    根据sessionID获取Token
	if (!WTSQueryUserToken(session_id , &hToken))
    {
        DWORD nCode = GetLastError();
        printf("WTSQueryUserToken error [%d]", nCode);

        CloseHandle(hToken);
        return FALSE;
    }

    //复制新的Token
    if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTokenDup))
    {
        DWORD nCode = GetLastError();
        printf("DuplicateTokenEx error [%d]", nCode);
		*exit_code = nCode;

        CloseHandle(hToken);
        return FALSE;
    }

    //创建环境信息
    if (!CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE))
    {
        DWORD nCode = GetLastError();
		printf("CreateEnvironmentBlock error [%d]", nCode);
		*exit_code = nCode;

        CloseHandle(hTokenDup);
        CloseHandle(hToken);
        return FALSE;
    }

    //设置启动参数
    ZeroMemory(&si, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    si.lpDesktop = _TEXT("winsta0\\default");

    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

    //开始创建进程
    DWORD dwCreateFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;
    if (!CreateProcessAsUser(hToken, NULL, command_line,  NULL, NULL, FALSE, dwCreateFlag, pEnv, NULL, &si, &pi))
    {
        DWORD nCode = GetLastError();
        printf("CreateProcessAsUser error [%d]", nCode);
		*exit_code = nCode;

        DestroyEnvironmentBlock(pEnv);
        CloseHandle(hTokenDup);
        CloseHandle(hToken);
        return FALSE;
    }

    DestroyEnvironmentBlock(pEnv);
    CloseHandle(hTokenDup);
    CloseHandle(hToken);

	return TRUE;
}

这样,新启动的程序就可以接收键盘鼠标输入,来进行交互了

 

 

你可能感兴趣的:(windows开发)