我有在服务和VCL表单应用程序(win32应用程序)中使用的代码。如何确定底层应用程序是作为NT服务还是作为应用程序运行?
感谢。
答案 0 :(得分:9)
如果应用程序对象(Forms.application)mainform不是基于表单的应用程序,则它将为nil。
uses
Forms, ... ;
function IsFormBased : boolean;
begin
Result := Assigned(Forms.Application.MainForm);
end;
答案 1 :(得分:9)
开始编辑
由于这似乎仍然得到了一些关注,我决定用缺少信息和更新的Windows补丁来更新答案。在任何情况下,您都不应该复制/粘贴代码。代码只是展示应该如何完成的事情。
END OF EDIT :
您可以检查父进程是否为SCM(服务控制管理器)。如果您作为服务运行,则始终如此,如果作为标准应用程序运行,则永远不会出现这种情况。另外我认为SCM总是具有相同的PID。
您可以这样检查:
type
TAppType = (atUnknown, atDesktop, atService);
var
AppType: TAppType;
function InternalIsService: Boolean;
var
PL: TProcessList;
MyProcessId: DWORD;
MyProcess: PPROCESSENTRY32;
ParentProcess: PPROCESSENTRY32;
GrandParentProcess: PPROCESSENTRY32;
begin
Result := False;
PL := TProcessList.Create;
try
PL.CreateSnapshot;
MyProcessId := GetCurrentProcessId;
MyProcess := PL.FindProcess(MyProcessId);
if MyProcess <> nil then
begin
ParentProcess := PL.FindProcess(MyProcess^.th32ParentProcessID);
if ParentProcess <> nil then
begin
GrandParentProcess := PL.FindProcess(ParentProcess^.th32ParentProcessID);
if GrandParentProcess <> nil then
begin
Result := SameText(string(ParentProcess^.szExeFile), 'services.exe') and
(SameText(string(GrandParentProcess^.szExeFile), 'winlogon.exe') or
SameText(string(GrandParentProcess^.szExeFile), 'wininit.exe'));
end;
end;
end;
finally
PL.Free;
end;
end;
function IsService: Boolean;
begin
if AppType = atUnknown then
begin
try
if InternalIsService then
AppType := atService
else
AppType := atDesktop;
except
AppType := atService;
end;
end;
Result := AppType = atService;
end;
initialization
AppType := atUnknown;
TProcessList是这样实现的(再次没有包含THashTable,但任何哈希表应该没问题):
type
TProcessEntryList = class(TList)
private
function Get(Index: Integer): PPROCESSENTRY32;
procedure Put(Index: Integer; const Value: PPROCESSENTRY32);
public
property Items[Index: Integer]: PPROCESSENTRY32 read Get write Put; default;
function Add(const Entry: TProcessEntry32): Integer; reintroduce;
procedure Clear; override;
end;
TProcessList = class
private
ProcessIdHashTable: THashTable;
ProcessEntryList: TProcessEntryList;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure CreateSnapshot;
function FindProcess(const ProcessId: DWORD): PPROCESSENTRY32;
end;
implementation
{ TProcessEntryList }
procedure TProcessEntryList.Clear;
var
i: Integer;
begin
i := 0;
while i < Count do
begin
FreeMem(Items[i]);
Inc(i);
end;
inherited;
end;
procedure TProcessEntryList.Put(Index: Integer; const Value: PPROCESSENTRY32);
var
Item: Pointer;
begin
Item := inherited Get(Index);
CopyMemory(Item, Value, SizeOf(tagPROCESSENTRY32));
end;
function TProcessEntryList.Get(Index: Integer): PPROCESSENTRY32;
begin
Result := PPROCESSENTRY32(inherited Get(Index));
end;
function TProcessEntryList.Add(const Entry: TProcessEntry32): Integer;
var
EntryCopy: PPROCESSENTRY32;
begin
GetMem(EntryCopy, SizeOf(tagPROCESSENTRY32));
CopyMemory(EntryCopy, @Entry, SizeOf(tagPROCESSENTRY32));
Result := inherited Add(EntryCopy);
end;
{ TProcessList }
constructor TProcessList.Create;
begin
inherited;
ProcessEntryList := TProcessEntryList.Create;
ProcessIdHashTable := THashTable.Create;
end;
destructor TProcessList.Destroy;
begin
FreeAndNil(ProcessIdHashTable);
FreeAndNil(ProcessEntryList);
inherited;
end;
function TProcessList.FindProcess(const ProcessId: DWORD): PPROCESSENTRY32;
var
ItemIndex: Integer;
begin
Result := nil;
if not ProcessIdHashTable.ContainsKey(IntToStr(ProcessId)) then
Exit;
ItemIndex := Integer(ProcessIdHashTable.Item[IntToStr(ProcessId)]);
Result := ProcessEntryList.Items[ItemIndex];
end;
procedure TProcessList.CreateSnapshot;
var
SnapShot: THandle;
ProcessEntry: TProcessEntry32;
ItemIndex: Integer;
begin
SnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if SnapShot <> 0 then
try
ProcessEntry.dwSize := SizeOf(ProcessEntry);
if Process32First(SnapShot, ProcessEntry) then
repeat
ItemIndex := ProcessEntryList.Add(ProcessEntry);
ProcessIdHashTable.Add(IntToStr(ProcessEntry.th32ProcessID), TObject(ItemIndex));
until not Process32Next(SnapShot, ProcessEntry);
finally
CloseHandle(SnapShot);
end;
end;
答案 2 :(得分:5)
我怀疑
System.IsConsole
System.IsLibrary
会给你预期的结果。
我所能想到的是将 Application 对象作为TObject传递给您需要进行区分并测试传递对象的类名为
的方法。TServiceApplication
or
TApplication
也就是说,您不需要知道您的代码是在服务还是GUI中运行。您可能应该重新考虑您的设计并让调用者传递一个对象来处理您想要(或不想要)显示的消息。 (我假设它是为了显示你想知道的消息/例外)。
答案 3 :(得分:5)
如何将GetCurrentProcessId
与EnumServicesStatusEx
匹配?
lpServices
参数指向接收ENUM_SERVICE_STATUS_PROCESS
结构数组的缓冲区。
匹配是针对该结构中的枚举服务进程ID ServiceStatusProcess.dwProcessId
完成的。
另一个选项是使用WMI
查询ProcessId=GetCurrentProcessId
的{{3}}个实例。
答案 4 :(得分:4)
您可以尝试这样的事情
Function IsMyformInsideaWindowsService(aForm:TObject) :Boolean;
Begin
Result:=aForm.ClassParent.ClassName='TService'; //When a form is running under a service the Class Parent is a TService
End;
答案 5 :(得分:3)
单个项目不能(或者我应该说理想情况下不是)服务和表单应用程序,至少如果你能够区分 Forms Application对象和 SvcMgr 应用程序对象 - 您必须拥有表单代码和服务代码的单独项目。
所以也许最简单的解决方案是项目条件定义。即在服务项目的项目设置中,将“ SERVICEAPP ”添加到条件定义中。
然后,只要您需要改变行为:
{$ifdef SERVICEAPP}
{$else}
{$endif}
对于皮带和括号,您可以在某些启动代码中采用先前描述的测试之一,以确保您的项目已使用所定义的预期符号进行编译。
program ... ;
:
begin
{$ifdef SERVICEAPP}
// test for service app - ASSERT if not
{$else}
// test for forms app - ASSERT if not
{$endif}
:
end.
您的 Forms 应用实际上可能正在作为服务运行,使用允许任何应用程序作为服务运行的粗略技术。
在这种情况下,您的应用程序当然总是是 Forms 应用程序,处理这种情况的最简单方法是使用您仅指定的命令行开关可执行文件的服务定义,以便您的应用程序可以通过测试该命令行开关来响应。
这样可以让您更轻松地测试“服务模式”行为,因为您可以使用IDE中定义的交换机以“调试”模式运行应用程序,但这不是构建服务的理想方式申请所以我不会仅凭这一点推荐它。这种技术通常仅在您希望作为服务运行的EXE但无法修改源代码以将其转换为“适当”服务时使用。
答案 6 :(得分:2)
你可以使用GetStdHandle方法来获取控制台句柄。当应用程序运行时,windows服务还没有输出console.if GetStdHandle等于零意味着你的应用程序作为windows服务运行。
{$APPTYPE CONSOLE} // important
uses
uServerForm in 'uServerForm.pas' {ServerForm},
uWinService in 'uWinService.pas' {mofidWinServer: TService},
Windows,
System.SysUtils,
WinSvc,
SvcMgr,
Forms,etc;
function RunAsWinService: Boolean;
var
H: THandle;
begin
if FindCmdLineSwitch('install', ['-', '/'], True) then
Exit(True);
if FindCmdLineSwitch('uninstall', ['-', '/'], True) then
Exit(True);
H := GetStdHandle(STD_OUTPUT_HANDLE);
Result := H = 0;
end;
begin
if RunAsWinService then
begin
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm(TmofidWinServer, mofidWinServer);
SvcMgr.Application.Run;
end
else
begin
Forms.Application.Initialize;
Forms.Application.CreateForm(TServerForm, ServerForm);
Forms.Application.Run;
end;
end.
答案 7 :(得分:1)
我实际上最终检查了 application.showmainform 变量。
skamradt的isFormBased的问题是在创建主窗体之前调用了一些代码。
我正在使用aldyn-software中名为SvCom_NTService的软件库。其中一个目的是出错;要么记录它们,要么显示消息。我完全赞同@Rob;我们的代码应该得到更好的维护,并在函数之外处理它。
另一个目的是失败的数据库连接和查询;我的函数中有不同的逻辑来打开查询。如果它是服务,那么它将返回nil但继续该过程。但是如果在应用程序中出现失败的查询/连接,那么我想显示一个消息并暂停应用程序。
答案 8 :(得分:1)
“Runner”(https://stackoverflow.com/a/1568462)的答案看起来非常有用,但我无法使用它,因为既没有定义TProcessList,也没有定义CreateSnapshot。在Google中搜索“TProcessList CreateSnapshot”只会找到7个页面,包括此页面和此页面的镜像/引号。没有代码存在。唉,我的声誉太低了,无法发送评论,询问我在哪里可以找到TProcessList的代码。
另一个问题:在我的电脑(Win7 x64)中,“services.exe”不在“winlogon.exe”中。它在“wininit.exe”里面。由于它似乎是Windows的实现细节,我建议不要查询祖父母。此外,services.exe不需要是直接父级,因为进程可以分叉。
所以这是我直接使用TlHelp32的版本,解决了所有问题:
uses
Classes, TlHelp32;
function IsRunningAsService: boolean;
function FindProcess(FSnapshotHandle: THandle; PID: DWORD; var lppe: TProcessEntry32): boolean;
var
ContinueLoop: BOOL;
begin
ContinueLoop := Process32First(FSnapshotHandle, lppe);
while Integer(ContinueLoop) <> 0 do
begin
if lppe.th32ProcessID = PID then
begin
result := true;
Exit;
end;
ContinueLoop := Process32Next(FSnapshotHandle, lppe);
end;
result := false;
end;
var
CurProcessId: DWORD;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
ExeName, PrevExeName: string;
DeadlockProtection: TList<Integer>;
begin
Result := false;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
try
CurProcessId := GetCurrentProcessId;
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ExeName := '';
while FindProcess(FSnapshotHandle, CurProcessId, FProcessEntry32) do
begin
if DeadlockProtection.IndexOf(FProcessEntry32.th32ProcessID) > -1 then break;
DeadlockProtection.Add(FProcessEntry32.th32ProcessID);
PrevExeName := ExeName;
ExeName := FProcessEntry32.szExeFile;
(*
Result := SameText(PrevExeName, 'services.exe') and // Parent
SameText(ExeName, 'winlogon.exe'); // Grandparent
*)
Result := SameText(ExeName, 'services.exe'); // Parent
if Result then Exit;
CurProcessId := FProcessEntry32.th32ParentProcessID;
end;
finally
CloseHandle(FSnapshotHandle);
DeadlockProtection.Free;
end;
end;
此代码也适用,即使在没有MainForm的应用程序中也是如此(例如CLI应用程序)。
答案 9 :(得分:0)
检查您的Applicatoin是否是TServiceApplication的实例:
IsServiceApp := Application is TServiceApplication;
答案 10 :(得分:0)
我没有找到可以轻松使用的简单答案,并且不需要重新编译,并允许使用一个exe作为服务和应用程序。您可以使用命令行参数(如“... \ myapp.exe -s”)将程序安装为服务,然后从程序中检查它:
如果ParamStr(ParamCount)=&#39; -s&#39;然后
答案 11 :(得分:0)
您可以根据检查当前进程的会话ID进行检查。所有服务都以会话ID = 0运行。
function IsServiceProcess: Boolean;
var
LSessionID, LSize: Cardinal;
LToken: THandle;
begin
Result := False;
LSize := 0;
if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, LToken) then
Exit;
try
if not GetTokenInformation(LToken, TokenSessionId, @LSessionID, SizeOf(LSessionID), LSize) then
Exit;
if LSize = 0 then
Exit;
Result := LSessionID = 0;
finally
CloseHandle(LToken);
end;
end;