如何利用Win32服务进程去创建一个GUI用户进程?

最近遇到一个小问题,我需要在一个服务进程中去启动一个用户GUI进程。按常理来说这很简单,通常情况下调用ShellExecute这个API即可。这种方法在XP的年代似乎是完美的,但由于现在大多已经到了Win7,这个方法似乎已经不好用了。追溯原因要谈到微软给XP以后的操作系统添加了Session隔离机制。网络上有很多介绍Windows Session机制的资料,这里就不再多余讨论,反正这个机制的出现让很多人为之头疼!

当我遇到这个问题时,我第一时间在网上搜索了资料,资料很多但也很乱,其中大部分资料都是乱拷贝粘贴的。里面充斥了很多错误,而这些发布者似乎从来没有去验证过他所拷贝的文章、代码是否正确!

出于以上的原因,在我解决这个问题后,我思考着将我的代码用一个简单的示例展示出来,用以帮助以后遇到该问题的朋友。我本着尽可能严谨的态度来书写这篇文章,但尽管这样,可能还是有所疏漏,如果您发现了,请告诉我,谢谢!

本文我将分两个部分介绍。首先,我们先创建一个足够简单的服务程序。接着,在此服务程序的基础上,实现用它来启动一个用户GUI程序。温馨提示,在本文我不会讲到什么是Session隔离,因为能力有限,我想我无法讲解清楚这个问题,但我会在本文末尾提供一些相关的链接供读者参考。另外,我会简单提及一下如何构建一个基本的Win32服务程序,以帮助没有任何服务程序开发经验的读者能更好的阅读,谢谢。

1.创建一个简单的服务进程
首先,我们搭建一个简单Win32服务进程框架,它的工作足够简单:在服务启动后,向指定文件输出一行”Hello Win32 Service”即可!
一个服务程序其实有它较为固定的框架,通常它包含一个main函数和两个回调函数。显而易见main函数还是唯一入口,另外两个回调函数会被自动调用而无需我们关心。因此,一个服务程序大概看起来就是这样的:

void WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
void WINAPI Handler(DWORD fdwControl);

int main(int argc, char const *argv[])
{

    return 0;
}

让我们先从main函数开始。先定义几个全局变量,因为即将用到它们:

//全局数据
TCHAR                               ServiceName[] = _TEXT("ServiceDemo");       //服务名称
SERVICE_STATUS                      ServiceStatus;                              //服务状态
SERVICE_STATUS_HANDLE               hStatus;                                    //服务状态句柄

接着改写main函数:

int main(int argc, char const *argv[])
{
    SERVICE_TABLE_ENTRY ServiceTable[] = {
        {ServiceName, ServiceMain},
        {
  NULL, NULL},
    };

    return StartServiceCtrlDispatcher(ServiceTable);
}

这里我们定义了一个SERVICE_TABLE_ENTRY类型的数组,通常称它为分派表。一个服务进程可以包含若干个子服务,每一个服务都必须在专门的分派表中注册。这个表的每一项都是一个 SERVICE_TABLE_ENTRY 结构对象。它有两个域:
  lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,这个域必须指定;
  lpServiceProc: 指向服务主函数的指针(服务入口点);
这里,我们传入了ServiceName和ServiceMain作为参数构造了第一个SERVICE_TABLE_ENTRY对象。无需构造太多,因为该服务进程只需一个服务就足够了。值得注意的是,分派表的最后一项必须是服务名和服务主函数的 NULL 指针,用以表示该数组的结束。

紧接着,调用StartServiceCtrlDispatcher方法,并传入了ServiceTable分派表作为其参数。服务控制管理器(SCM:Services Control Manager)是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待该服务进程的主线程来调用 StartServiceCtrlDispatcher 函数,并将分派表传递给 StartServiceCtrlDispatcher。这一调用将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数。另外分派器还监视程序中所有服务的执行情况,然后分派器将控制请求从 SCM 传给各个服务,至于各个服务将如何响应这些控制请求,稍后再来关注。
另一个值得注意的是,如果 StartServiceCtrlDispatcher 函数30秒没有被调用,便会报错,为了避免这种情况,我们必须在 ServiceMain 函数中或在非主函数的单独线程中初始化服务分派表。而在本文所描述的服务中不需要防范这样的情况。

分派表中所有的服务执行完之后(例如,用户通过“服务”控制面板程序停止它们),或者发生错误时,StartServiceCtrlDispatcher 调用返回,然后服务主进程终止。

ServiceMain函数:

void WINAPI ServiceMain(DWORD argc, LPTSTR* argv)
{
    ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
    ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
    ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
    ServiceStatus.dwWin32ExitCode = NO_ERROR;
    ServiceStatus.dwServiceSpecificExitCode = NO_ERROR;
    ServiceStatus.dwCheckPoint = 0;
    ServiceStatus.dwWaitHint = 0;

    hStatus = RegisterServiceCtrlHandler(ServiceName, Handler);
    if(!hStatus)
    {
        DWORD dwError = GetLastError();
        WriteErrorToLog(dwError, "RegisterServiceCtrlHandler");
        return ;
    }

    InitService();

    //设置服务状态
    ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    SetServiceStatus(hStatus, &ServiceStatus);

    Run();
}

该函数是一个服务的入口点。ServiceMain 应该尽可能早的为服务注册控制处理器。这要通过调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数传递给此函数:服务名和指向 ControlHandlerfunction 的指针。控制处理器函数,它指示控制分派器调用 Handler 函数处理 SCM 控制请求。注册完控制处理器之后,获得状态句柄(hStatus)。通过调用 SetServiceStatus 函数,用 hStatus 向 SCM 报告服务的状态。
另外,在ServiceMain 函数中,还对ServiceStatus结构体的成员设置了一些

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值