众所周知,使用Delphi创建的Service Application系统服务,在XP系统中与桌面交互十分流畅,可以随意打开自己程序的窗口,不过自Vista系统开始后台服务不再允许与桌面系统直接交互了(关于session 0 的详情百度相关文章可见原理描述)。究其原因是因为windows认为系统服务在设计的初衷就不应该与桌面UI进行交互,但是我们的项目有时候又不得不借助系统服务实现开机自启(目标是做一个开机自启的中间件,需要界面)。有人说添加注册表或者放到启动项下就可以了,的确,这是一种方式,但是如果想要更好的自启效果,我们认为后台服务更适合(防止被杀毒软件屏蔽等)。
网络上的部分大佬给出了比较粗略的解决方法,提到了可以使用CreateProcessAsUser打开第三方应用,但是具体使用Delphi完成的代码基本没有。笔者在搜罗了大部分资料后完成了这一功能。
一、创建后台服务,此处可以百度到详细的制作方法,本文不再赘述;
二、实现调用Notepad.exe(关键代码):
此处引用了tlHelp32,jwaWtsApi32;
其中jwaWtsApi32 使用的是JEDI API 包,百度可以搜到下载地址(如果有人需要也可以联系我),下载下来后添加引用路径即可。
procedure CreateProc(ProcessName: string);
var
SessionID: DWORD;
UserToken: THandle;
CmdLine: string;
si: _STARTUPINFOW;
pi: _PROCESS_INFORMATION;
begin
SessionId:= WtsGetActiveConsoleSessionID;
if SessionID = $FFFFFFFF then Exit;
if WTSQueryUserToken(SessionID, UserToken) then begin
CmdLine:= 'notepad.exe';//其实就是入参,我这里为了方便测试写死了记事本,可以替换成需要的第三方应用地址。
UniqueString(CmdLine);
ZeroMemory(@si, SizeOf(si));
si.cb := SizeOf(si);
SI.lpDesktop := PChar('winsta0\Default');
SI.dwFlags := STARTF_USESHOWWINDOW;
SI.wShowWindow := SW_SHOWNORMAL;
ZeroMemory(@pi, SizeOf(pi));
try
CreateProcessAsUser(UserToken, nil, pchar(CmdLine), nil, nil, False,
0, nil, nil, si, pi);
except on E: Exception do
// Log4error(e.Message);
end;
CloseHandle(UserToken);
end else begin
// Log GetLastError ...
end;
end;
三、在系统服务的ServiceStart
事件中添加启动代码,如果想做的更好,可以在服务中用线程定时守护exe,避免exe被用户误杀进程:
procedure TDelphiService.ServiceStart(Sender: TService; var Started: Boolean);
begin
Started := True;
CreateProc('D:\Private\10-其他功能\后台服务\out\1.exe');
end;
四、安装服务,试试看效果吧~
这个方法在XP/WIN7/WIN10下都亲测有效,希望对大家有帮助。