确定是否作为VCL表单或服务运行

时间:2009-10-14 15:03:23

标签: delphi vcl

我有在服务和VCL表单应用程序(win32应用程序)中使用的代码。如何确定底层应用程序是作为NT服务还是作为应用程序运行?

感谢。

12 个答案:

答案 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)

如何将GetCurrentProcessIdEnumServicesStatusEx匹配? 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;