如何利用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结构体的成员设置了一些值,关于这些值的含义可以查看MSDN给出的文档,这里有详细的介绍。
接着,调用RegisterServiceCtrlHandler函数给该服务注册了一个控制信号处理函数Handler。然后使用InitService函数来做一些初始化工作。完成后,设置服务的状态为正在运行中,并且调用Run函数开始真正的“服务”。

Handler函数:
这是最后一个要说明的函数了,它看上去是这样的:

void WINAPI Handler(DWORD fdwControl)
{
    switch(fdwControl)
    {
    case SERVICE_CONTROL_STOP:
        {
            WriteMsgToLog("ServiceDemo 服务停止...");

            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            ServiceStatus.dwWin32ExitCode = 0;
            SetServiceStatus(hStatus, &ServiceStatus);
        }
        break;
    case SERVICE_CONTROL_SHUTDOWN:
        {
            WriteMsgToLog("ServiceDemo 服务终止...");

            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            ServiceStatus.dwWin32ExitCode = 0;
            SetServiceStatus(hStatus, &ServiceStatus);
        }
        break;
    default:
        break;
    }
}

这里处理了SERVICE_CONTROL_STOP和SERVICE_CONTROL_SHUTDOWN消息,因为在ServiceMain函数的开头我们注册了这两个消息,表明我们希望自己处理这些消息。

到了这里,一个基本的服务程序就简单介绍完毕,稍后将给出整个服务程序的代码,你可以编译这份代码,并使用下面两条命令去安装和删除这个服务进程,安装成功后,可以到服务管理器中去启动该服务。

#安装服务 注意“binpath=”后有一个空格
sc create ServiceDemo binpath= C:/ServiceDemo.exe

#删除服务 删除前需先停止服务
sc delete ServiceDemo

如果不出什么意外,你能在代码定义的日志文件中看到一些输出。
完整代码:

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


#define LOGFILE "C:\\Msg.log"

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

//日志记录的相关方法
void WriteMsgToLog(const char *Msg);
void WriteErrorToLog(DWORD dwErrorCode, const char *MsgInfo);

//服务相关方法
void InitService();
void Run();

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

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

    return StartServiceCtrlDispatcher(ServiceTable);
}

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

void WINAPI Handler(DWORD fdwControl)
{
    switch (fdwControl)
    {
    case SERVICE_CONTROL_STOP:
    {
        WriteMsgToLog("ServiceDemo 服务停止...");

        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ServiceStatus.dwWin32ExitCode = 0;
        SetServiceStatus(hStatus, &ServiceStatus);
    }
    break;
    case SERVICE_CONTROL_SHUTDOWN:
    {
        WriteMsgToLog("ServiceDemo 服务终止...");

        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ServiceStatus.dwWin32ExitCode = 0;
        SetServiceStatus(hStatus, &ServiceStatus);
    }
    break;
    default:
        break;
    }
}

void InitService()
{
    WriteMsgToLog("ServiceDemo 服务启动...");
}

void Run()
{
    WriteMsgToLog("Hello Win32 Service");
}

void WriteMsgToLog(const char *Msg)
{
    FILE *file = NULL;
    fopen_s(&file, LOGFILE, "a+");
    if (!file || !Msg)
    {
        return;
    }

    fprintf_s(file, "%s\n", Msg);

    fflush(file);
    fclose(file);
    file = NULL;
}

void WriteErrorToLog(DWORD dwErrorCode, const char *MsgInfo)
{
    if (!MsgInfo) return;

    char Msg[128] = { 0 };
    sprintf_s(Msg, "%s Faild: %lu\n", MsgInfo, dwErrorCode);
    WriteMsgToLog(Msg);
}

2.创建用户GUI进程

终于可以开始本文的重点了。首先,我们先试着启动一个notepad.exe,因此请在上面的源码中添加如下的定义:

TCHAR szApp[MAX_PATH] = _TEXT("notepad.exe")

接着,创建一个函数用以创建GUI进程。

void CreateMyProcess()
{
    DWORD dwSessionID = WTSGetActiveConsoleSessionId();
    HANDLE hToken = NULL;
    HANDLE hTokenDup = NULL;
    LPVOID pEnv = NULL;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    //获取当前处于活动状态用户的Token
    if (!WTSQueryUserToken(dwSessionID, &hToken))
    {
        DWORD nCode = GetLastError();
        WriteErrorToLog(nCode, "WriteErrorToLog");

        CloseHandle(hToken);
        return;
    }

    //复制新的Token
    if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTokenDup))
    {
        DWORD nCode = GetLastError();
        WriteErrorToLog(nCode, "DuplicateTokenEx");

        CloseHandle(hToken);
        return;
    }

    //创建环境信息
    if (!CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE))
    {
        DWORD nCode = GetLastError();
        WriteErrorToLog(nCode, "CreateEnvironmentBlock");

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

    //设置启动参数
    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(hTokenDup, szApp, NULL, NULL, NULL, FALSE, dwCreateFlag, pEnv, NULL, &si, &pi))
    {
        DWORD nCode = GetLastError();
        WriteErrorToLog(nCode, "CreateProcessAsUser");

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

    //附加操作,回收资源

    //等待启动的进程结束
    WaitForSingleObject(pi.hProcess, INFINITE);
    WriteMsgToLog("子进程结束,回收资源...");

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

看看CreateMyProcess()函数的部分细节。首先,我们调用WTSGetActiveConsoleSessionId() API尝试获取当前处于活动状态的用户的Session ID。为什么要这么做?因为我们需要以当前用户的权限去创建这个GUI进程,而要想这么做就得取到当前活动用户的权限令牌(Token),要取得权限令牌,首先就得找到是哪一个用户。
接着就可以使用WTSQueryUserToken() API来取得该用户的令牌了,我们将它存放到了hToken句柄中。紧接着用DuplicateTokenEx() API复制一个已经存在的令牌来产生一个新的令牌。值得注意的是,因为新的令牌将用于创建进程,即用于CreateProcessAsUser() API的参数,所以按照MSDN上面的说法,在复制令牌时,需要传入一些指定的参数,具体可以参看这里
然后,使用CreateEnvironmentBlock() API获取了指定用户的环境变量,这个环境变量会作为参数传递给CreateProcessAsUser() API。最后,简单的对STARTUPINFO和PROCESS_INFORMATION结构体的一些成员赋予了一些特定的值,就开始调用CreateProcessAsUser() API,这个API完成了主要的工作。但它的一些参数需要特别注意,不过这些MSDN上都有详细说明

最后,别忘了在服务程序的Run()函数中调用CreateMyProcess()函数,并添加一些必要的头文件和库,即可完成。

#include <UserEnv.h>
#include <WtsApi32.h>

#pragma comment(lib, "Wtsapi32.lib")
#pragma comment(lib, "Userenv.lib"

试着编译并运行一下该服务吧,如果不出意外,会看到一个记事本程序被启动起来了,看样子一切顺利!但等等,真的一点问题都没有吗?不妨换一个notepad.exe进程之外的进程试试。但在这之前,先让我们准备一个用于测试的小程序,它很简单,仅用几行代码打印出它自己当前工作的目录。

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

using namespace std;

int main()
{
    TCHAR g_strCurrentPath[MAX_PATH] = {0};
    GetCurrentDirectory(MAX_PATH, g_strCurrentPath);

    wcout << g_strCurrentPath << endl;

    system("pause");
    return 0;
}

编译这个程序,并尝试在服务中启动它,你会发现它输出了如下的内容:
这里写图片描述

看这输出,明显不对啊,它的工作目录明明是在C盘的根目录,为什么?仔细看过MSDN上CreateProcessAsUser() API文档的朋友应该知道问题的原因,CreateProcessAsUser() API有一个参数可以用来传递进程当前的工作目录,而在之前我们没有设置该参数!所以我们定义如下的宏,并改正一下CreateProcessAsUser() API的lpCurrentDirectory 参数。

TCHAR szCurrentDirectory[MAX_PATH] = _TEXT("C:\\");

最后完整源码:

#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#include <UserEnv.h>
#include <WtsApi32.h>

#pragma comment(lib, "Wtsapi32.lib")
#pragma comment(lib, "Userenv.lib")

#define LOGFILE "C:\\Msg.log"

TCHAR szApp[MAX_PATH] = _TEXT("C:\\TestApp.exe");
TCHAR szCurrentDirectory[MAX_PATH] = _TEXT("C:\\");

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

//日志记录的相关方法
void WriteMsgToLog(const char *Msg);
void WriteErrorToLog(DWORD dwErrorCode, const char *MsgInfo);

//服务相关方法
void InitService();
void Run();

//创建用户GUI进程
void CreateMyProcess();

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

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

    return StartServiceCtrlDispatcher(ServiceTable);
}

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

void WINAPI Handler(DWORD fdwControl)
{
    switch (fdwControl)
    {
    case SERVICE_CONTROL_STOP:
    {
        WriteMsgToLog("ServiceDemo 服务停止...");

        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ServiceStatus.dwWin32ExitCode = 0;
        SetServiceStatus(hStatus, &ServiceStatus);
    }
    break;
    case SERVICE_CONTROL_SHUTDOWN:
    {
        WriteMsgToLog("ServiceDemo 服务终止...");

        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ServiceStatus.dwWin32ExitCode = 0;
        SetServiceStatus(hStatus, &ServiceStatus);
    }
    break;
    default:
        break;
    }
}

void InitService()
{
    WriteMsgToLog("ServiceDemo 服务启动...");
}

void Run()
{
    WriteMsgToLog("Hello Win32 Service");
    CreateMyProcess();
}

void WriteMsgToLog(const char *Msg)
{
    FILE *file = NULL;
    fopen_s(&file, LOGFILE, "a+");
    if (!file || !Msg)
    {
        return;
    }

    fprintf_s(file, "%s\n", Msg);

    fflush(file);
    fclose(file);
    file = NULL;
}

void WriteErrorToLog(DWORD dwErrorCode, const char *MsgInfo)
{
    if (!MsgInfo) return;

    char Msg[128] = { 0 };
    sprintf_s(Msg, "%s Faild: %lu\n", MsgInfo, dwErrorCode);
    WriteMsgToLog(Msg);
}


void CreateMyProcess()
{
    DWORD dwSessionID = WTSGetActiveConsoleSessionId();
    HANDLE hToken = NULL;
    HANDLE hTokenDup = NULL;
    LPVOID pEnv = NULL;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    //获取当前处于活动状态用户的Token
    if (!WTSQueryUserToken(dwSessionID, &hToken))
    {
        DWORD nCode = GetLastError();
        WriteErrorToLog(nCode, "WriteErrorToLog");

        CloseHandle(hToken);
        return;
    }

    //复制新的Token
    if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTokenDup))
    {
        DWORD nCode = GetLastError();
        WriteErrorToLog(nCode, "DuplicateTokenEx");

        CloseHandle(hToken);
        return;
    }

    //创建环境信息
    if (!CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE))
    {
        DWORD nCode = GetLastError();
        WriteErrorToLog(nCode, "CreateEnvironmentBlock");

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

    //设置启动参数
    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(hTokenDup, szApp, NULL, NULL, NULL, FALSE, dwCreateFlag, pEnv, szCurrentDirectory, &si, &pi))
    {
        DWORD nCode = GetLastError();
        WriteErrorToLog(nCode, "CreateProcessAsUser");

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

    //附加操作,回收资源

    //等待启动的进程结束
    WaitForSingleObject(pi.hProcess, INFINITE);
    WriteMsgToLog("子进程结束,回收资源...");

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

附加资料:
1.会话0隔离 【点我
2.CreateProcessAsUser() windowstations and desktops 【点我

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MFC调用win32窗口显示调试信息,使用AllocConsole 函数(2010-11-16 15:33:25)转载标签: 调试win32mfc杂谈 分类: 编译器 AllocConsole Function 为主调进程分配一个新的控制台。 语法 C++ : BOOL WINAPI AllocConsole(void); 参数: 无 返回值:如果函数成功,返回值是非零值;如果函数失败,返回值是零值。 备注: 一个进程仅能关联一个控制台,所以该函数在主调进程已经具有控制台时将会失败。 一个进程可以使用 FreeConsole 函数来释放与之关联的控制台,之后它就可以调用该函数来创建一个新的控制台或使用 AttachConsole 函数来关联另一个控制台。 如果主调进程创建了一个子进程,则子进程也将继承这个新创建的控制台。 该函数为新的控制台初始化标准输入、输出、错误句柄等。 标准输入句柄是一个控制台输入缓冲的句柄,标准输出和标准错误句柄则是控制台屏幕缓冲的句柄。为了获得这些句柄,可以使用 GetStdHandle 函数。 该函数主要用于GUI应用程序来创建一个控制台窗口。 GUI应用程序初始化时时没有控制台的,而控制台应用程序则以控制台来初始化的。 要求 : Minimum supported client Windows 2000 Professional Minimum supported server Windows 2000 Server Header: Wincon.h (include Windows.h) Library: Kernel32.lib DLL Kernel32.dll ---------------------------------------------------------------------------------------- 虽然WIN32时代是图形界面时代,但偶尔程序还需要用到命令行模式,比如批处理,这时再搞个图形界面出来显得似乎就不那么专业了。但客户还需要在正常状态下(对于命令行模式,我认为用户是非正常状态,比如脑子进水。)使用图形界面,这决定我们不能开一个控制台工程,而需要使用MFC exe程序。 OK,理所当然的,通过条件控制,命令行下我关掉对话框界面的调用代码,再使用 cout << "Hello world!" << endl; 来向这个友好的世界打个招呼,并坚持认为这句问候应该显示在CMD那个漆黑的窗口里。 很沮丧地说,事实给我与痛击。cmd窗口里仍然漆黑一片,系统完全不理会我的友好。 邓爷爷说,改革开放好!也许,我也需要个改革。 在同事mr. zhang的指导下,我找到一组API:Console Functions!正是这组API,最终让我的友好得以正当地表达。 一。创建一个Console,AllocConsole 直接使用 AllocConsole(); 马上,若是进程内第一次调用这个函数,一个空的cmd窗口会蹦出来。需要注意,一个进程只能创建一个console,多次调用会返回FALSE;而且,这个窗口是个独立的控制台窗口。 MSDN的解释:A process can be associated with only one console, so the AllocConsole function fails if the calling process already has a console. 还有段:If the calling process creates a child process, the child inherits the new console. 二。显示Hello World,WriteConsole 有了console,我们还需要获取它的句柄HANDLE,然后才能在上面显示。方法是 GetStdHandle,它会获取前面我们AllocConsole得到的cmd窗口的句柄;若未调用AllocConsole,将获取标准的输入输出窗口句柄。 MSDN的解释:The GetStdHandle function returns a handle for the standard input, standard output, or standard error device. HANDLE hdlWrite = GetStd

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值