1、VC2008中编写“Windows服务”(Windows Service)程序
vc2008下新建一个 ATL 项目-》 选择创建一个“服务”类型的ATL 项目TestService,将生成如下代码,
class
CTestServiceModule
:
public
CAtlServiceModuleT
<
CTestServiceModule
,
IDS_SERVICENAME
>
{
public
:
DECLARE_LIBID
(
LIBID_TestServiceLib
)
DECLARE_REGISTRY_APPID_RESOURCEID
(
IDR_TESTSERVICE
,
"{1FF78006-B225-4CC0-A7DE-E0C9D31C9937}"
)
HRESULT
InitializeSecurity
()
throw
()
{
// TODO :
调用
CoInitializeSecurity
并为服务提供适当的
//
安全设置
//
建议
- PKT
级别的身份验证、
// RPC_C_IMP_LEVEL_IDENTIFY
的模拟级别
//
以及适当的非
NULL
安全说明符。
return
S_OK
;
}
//
重写这个函数来启动任务啦
HRESULT
Run
(
int
nShowCmd
=
SW_HIDE
)
throw
()
{
HRESULT
hr
=
S_OK
;
hr
=
__super
::
PreMessageLoop
(
nShowCmd
);
if
(
hr
==
S_OK
)
{
if
(
m_bService
)
{
//
需要定义
#define
_ATL_NO_COM_SUPPORT才能启动服务时走到这里
//
可以在这里启动线程,或者什么其他东西来做自己的工作的啦
//
这里是什么都没有做了,只输出一条信息
LogEvent
(
_T
(
"widebright
的服务启动咯,呵呵
"
));
SetServiceStatus
(
SERVICE_RUNNING
);
}
//
进入消息循环,不停的处理消息,可能最后分发到
Handler
去处理,调用了
OnShutdown
等函数的。
__super
::
RunMessageLoop
();
}
if
(
SUCCEEDED
(
hr
))
{
hr
=
__super
::
PostMessageLoop
();
}
//
可以在适当的时候调用
Uninstall
函数来卸载掉服务
//__super::Uninstall();
return
hr
;
}
//
重写,服务退出处理
void
OnShutdown
()
throw
()
{
LogEvent
(
_T
(
"TestService
的服务退出咯,一点都不好玩呵呵
"
));
}
};
CTestServiceModule
_AtlModule
;
//
extern
"C"
int
WINAPI
_tWinMain
(
HINSTANCE
,
HINSTANCE
,
LPTSTR
,
int
nShowCmd
)
{
return
_AtlModule
.
WinMain
(
nShowCmd
);
}
我只要根据需要重写相应的函数来实现自己想要的功能就行了,比如你想创建的“服务”随系统启动,可以重写CAtlServiceModuleT 的Install函数,把里面的CreateService函数的参数修改一下,例如添加与用户交互可以使用 SERVICE_INTERACTIVE_PROCESS,具体可以去MSDN上查找CreateService这个API的说明。
如果想处理服务 停止和启动的动作,可以参考CAtlServiceModuleT 的源代码重写OnStop ()等函数。我上面简单到重写了Run函数,输出一条“事件”其实具体 工作是可以放到这里来完成的吧。
编译,生成程序之后就可以测试了,
执行“TestService -/Service” 就可以把服务注册到系统了,命令行参数其实是在CAtlServiceModuleT::ParseCommandLine 这个函数里面处理,可以去看一下,必要的话重写也是可以的,加上调用 UnInstall来删除服务的代码也很不错的吧。
注册后,就看用“sc start” 或者“net start” 等命令来操纵服务了。在“服务”控制器里面控制与可以:如图
这时候在Run函数中启动一个Notepad.exe,此时没有界面显示,在xp下可以使用下面的方法实现notepad与用户的交互:
//for xp system
DWORD
_stdcall
LaunchAppIntoSession0
(
LPTSTR
lpCommand
)
{
////////////////////////////////////////////system show dlg////////////////////
HDESK
hdeskCurrent
;
HDESK
hdesk
;
HWINSTA
hwinstaCurrent
;
HWINSTA
hwinsta
;
hwinstaCurrent
=
GetProcessWindowStation
();
if
(
hwinstaCurrent
==
NULL
)
{
return
FALSE
;
}
hdeskCurrent
=
GetThreadDesktop
(
GetCurrentThreadId
());
if
(
hdeskCurrent
==
NULL
){
return
FALSE
;
}
//
打开
winsta0
//
打开
winsta0
hwinsta
=
OpenWindowStation
(
L
"Winsta0"
,
FALSE
,
WINSTA_ALL_ACCESS
);
// WINSTA_ACCESSCLIPBOARD|
// WINSTA_ACCESSGLOBALATOMS |
// WINSTA_ENUMDESKTOPS |
// WINSTA_CREATEDESKTOP |
// WINSTA_CREATEDESKTOP |
// WINSTA_ENUMERATE |
// WINSTA_EXITWINDOWS |
// WINSTA_READATTRIBUTES |
// WINSTA_READSCREEN |
// WINSTA_WRITEATTRIBUTES);
if
(
hwinsta
==
NULL
){
return
FALSE
;
}
if
(!
SetProcessWindowStation
(
hwinsta
))
{
return
FALSE
;
}
//
打开
desktop
hdesk
=
OpenDesktop
(
L
"default"
, 0,
FALSE
,
DESKTOP_CREATEMENU
|
DESKTOP_CREATEWINDOW
|
DESKTOP_ENUMERATE
|
DESKTOP_HOOKCONTROL
|
DESKTOP_JOURNALPLAYBACK
|
DESKTOP_JOURNALRECORD
|
DESKTOP_READOBJECTS
|
DESKTOP_SWITCHDESKTOP
|
DESKTOP_WRITEOBJECTS
);
if
(
hdesk
==
NULL
){
return
FALSE
;
}
SetThreadDesktop
(
hdesk
);
////////////////////////////////////////////end of system show dlg////////////////////
STARTUPINFO
si
= {
sizeof
(
si
) };
SECURITY_ATTRIBUTES
saProcess
,
saThread
;
PROCESS_INFORMATION
piProcessB
,
piProcessC
;
// Prepare to spawn Process B from Process A.
// The handle identifying the new process
// object should be inheritable.
saProcess
.
nLength
=
sizeof
(
saProcess
);
saProcess
.
lpSecurityDescriptor
=
NULL
;
saProcess
.
bInheritHandle
=
TRUE
;
// The handle identifying the new thread
// object should NOT be inheritable.
saThread
.
nLength
=
sizeof
(
saThread
);
saThread
.
lpSecurityDescriptor
=
NULL
;
saThread
.
bInheritHandle
=
FALSE
;
CreateProcess
(
NULL
,
lpCommand
, &
saProcess
, &
saThread
,
FALSE
, 0,
NULL
,
NULL
, &
si
, &
piProcessB
);
if
(!
SetProcessWindowStation
(
hwinstaCurrent
))
return
FALSE
;
if
(!
SetThreadDesktop
(
hdeskCurrent
))
return
FALSE
;
if
(!
CloseWindowStation
(
hwinsta
))
return
FALSE
;
if
(!
CloseDesktop
(
hdesk
))
return
FALSE
;
return
TRUE
;
}
这种方法的关键是OpenWindowStation、SetProcessWindowStation、OpenDesktop和SetThreadDesktop这四个函数。这种方法的思路是:当前进程所处于的Session必须有界面交互能力,这样才能显示出对话框。由于第一个交互式用户会登录到拥有WinSta0的Session 0,所以,强制性地把服务所在的进程与WinSta0关联起来,并且打开当前的桌面,把工作线程挂到该桌面上,就可以显示出对话框。
这种方法在
WinXP和Windows2003下工作得不错,很遗憾,在Vista和Windows2008下,一旦执行到OpenWindowStation,试图代开WinSta0工作站时,程序就会出异常。
首先了解一下程序要具备怎样的条件才能与界面交互。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弹一个对话框呢?
幸运的是,还真可以这样做。
//for win7
DWORD
_stdcall
LaunchAppIntoDifferentSession
(
LPTSTR
lpCommand
)
{
DWORD
dwRet
= 0;
PROCESS_INFORMATION
pi
;
STARTUPINFO
si
;
DWORD
dwSessionId
;
HANDLE
hUserToken
=
NULL
;
HANDLE
hUserTokenDup
=
NULL
;
HANDLE
hPToken
=
NULL
;
HANDLE
hProcess
=
NULL
;
DWORD
dwCreationFlags
;
HMODULE
hInstKernel32
=
NULL
;
typedef
DWORD
(
WINAPI
*
WTSGetActiveConsoleSessionIdPROC
)();
WTSGetActiveConsoleSessionIdPROC
WTSGetActiveConsoleSessionId
=
NULL
;
hInstKernel32
=
LoadLibrary
(
L
"Kernel32.dll"
);
if
(!
hInstKernel32
)
{
return
FALSE
;
}
OutputDebugString
(
L
"LaunchAppIntoDifferentSession 1\n"
);
WTSGetActiveConsoleSessionId
= (
WTSGetActiveConsoleSessionIdPROC
)
GetProcAddress
(
hInstKernel32
,
"WTSGetActiveConsoleSessionId"
);
// Log the client on to the local computer.
dwSessionId
=
WTSGetActiveConsoleSessionId
();
do
{
WTSQueryUserToken
(
dwSessionId
,&
hUserToken
);
dwCreationFlags
=
NORMAL_PRIORITY_CLASS
|
CREATE_NEW_CONSOLE
;
ZeroMemory
( &
si
,
sizeof
(
STARTUPINFO
) );
si
.
cb
=
sizeof
(
STARTUPINFO
);
si
.
lpDesktop
=
L
"winsta0\\default"
;
ZeroMemory
( &
pi
,
sizeof
(
pi
) );
TOKEN_PRIVILEGES
tp
;
LUID
luid
;
if
( !::
OpenProcessToken
(
GetCurrentProcess
(),
TOKEN_ADJUST_PRIVILEGES
|
TOKEN_QUERY
|
TOKEN_DUPLICATE
|
TOKEN_ASSIGN_PRIMARY
|
TOKEN_ADJUST_SESSIONID
|
TOKEN_READ
|
TOKEN_WRITE
, &
hPToken
) )
{
dwRet
=
GetLastError
();
break
;
}
else
;
if
( !
LookupPrivilegeValue
(
NULL
,
SE_DEBUG_NAME
, &
luid
) )
{
dwRet
=
GetLastError
();
break
;
}
else
;
tp
.
PrivilegeCount
=1;
tp
.
Privileges
[0].
Luid
=
luid
;
tp
.
Privileges
[0].
Attributes
=
SE_PRIVILEGE_ENABLED
;
if
( !
DuplicateTokenEx
(
hPToken
,
MAXIMUM_ALLOWED
,
NULL
,
SecurityIdentification
,
TokenPrimary
, &
hUserTokenDup
) )
{
dwRet
=
GetLastError
();
break
;
}
else
;
//Adjust Token privilege
if
( !
SetTokenInformation
(
hUserTokenDup
,
TokenSessionId
,(
void
*)&
dwSessionId
,
sizeof
(
DWORD
) ) )
{
dwRet
=
GetLastError
();
break
;
}
else
;
if
( !
AdjustTokenPrivileges
(
hUserTokenDup
,
FALSE
, &
tp
,
sizeof
(
TOKEN_PRIVILEGES
), (
PTOKEN_PRIVILEGES
)
NULL
,
NULL
) )
{
dwRet
=
GetLastError
();
break
;
}
else
;
LPVOID
pEnv
=
NULL
;
if
(
CreateEnvironmentBlock
( &
pEnv
,
hUserTokenDup
,
TRUE
) )
{
dwCreationFlags
|=
CREATE_UNICODE_ENVIRONMENT
;
}
else
pEnv
=
NULL
;
// Launch the process in the client's logon session.
if
(
CreateProcessAsUser
(
hUserTokenDup
,
// client's access token
NULL
,
// file to execute
lpCommand
,
// command line
NULL
,
// pointer to process SECURITY_ATTRIBUTES
NULL
,
// pointer to thread SECURITY_ATTRIBUTES
FALSE
,
// handles are not inheritable
dwCreationFlags
,
// creation flags
pEnv
,
// pointer to new environment block
NULL
,
// name of current directory
&
si
,
// pointer to STARTUPINFO structure
&
pi
// receives information about new process
) )
{
}
else
{
dwRet
=
GetLastError
();
break
;
}
}
while
( 0 );
//Perform All the Close Handles task
if
(
NULL
!=
hUserToken
)
{
CloseHandle
(
hUserToken
);
}
else
;
if
(
NULL
!=
hUserTokenDup
)
{
CloseHandle
(
hUserTokenDup
);
}
else
;
if
(
NULL
!=
hPToken
)
{
CloseHandle
(
hPToken
);
}
else
;
return
dwRet
;
}
启动服务后显示了system权限的Notepad.exe,并且可以与用户进行交互
当然,在本例子启动进程的地方创建一个对话框也是可以
http://blog.sina.com.cn/s/blog_488cff5201017yug.html