代码之家  ›  专栏  ›  技术社区  ›  M Schenkel

确定是否作为VCL窗体或服务运行

  •  11
  • M Schenkel  · 技术社区  · 15 年前

    我有在服务和VCL窗体应用程序(Win32应用程序)中使用的代码。如何确定基础应用程序是作为NT服务还是作为应用程序运行?

    谢谢。

    12 回复  |  直到 7 年前
        1
  •  9
  •   skamradt    15 年前

    如果不是基于表单的应用程序,则应用程序对象(Forms.Application)主窗体将为零。

    uses
      Forms, ... ;
    
    function IsFormBased : boolean;
    begin
      Result := Assigned(Forms.Application.MainForm);
    end;
    
        2
  •  9
  •   Runner    11 年前

    开始编辑

    由于这似乎仍然得到了一些注意,我决定用丢失的信息和更新的Windows补丁更新答案。在任何情况下,都不应复制/粘贴代码。这段代码只是一个关于应该如何做的展示。

    编辑结束 :

    您可以检查父进程是否是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;
    
        3
  •  5
  •   Lieven Keersmaekers    15 年前

    我怀疑

    System.IsConsole
    System.IsLibrary
    

    会给你预期的结果。

    我能想到的就是通过 应用 对象作为对象,以对象的方法进行区分,并测试通过的对象的类名是否为

    TServiceApplication 
    or
    TApplication
    

    也就是说,不需要知道代码是在服务中运行还是在GUI中运行。您可能应该重新考虑您的设计,并让调用者传递一个对象来处理您希望(或不希望)显示的消息。(我想是为了显示您想知道的消息/例外情况)。

        4
  •  5
  •   kobik    9 年前

    匹配怎么样 GetCurrentProcessId 反对 EnumServicesStatusEx 是吗?
    这个 lpServices 参数指向接收 ENUM_SERVICE_STATUS_PROCESS 结构。 匹配是根据枚举的服务进程ID完成的: ServiceStatusProcess.dwProcessId 在那个结构中。

    另一个选择是使用 WMI 查询 Win32_Service 实例在哪里 ProcessId=GetCurrentProcessId .

        5
  •  4
  •   RRUZ    15 年前

    你可以试试这个

    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;
    
        6
  •  3
  •   Deltics    15 年前

    单个项目不能(或者我应该说理想情况下不是)同时使用服务和表单应用程序,至少如果您能够区分 形式 应用程序对象和 向量空间模型 应用程序对象-对于表单代码和服务代码,您必须有单独的项目。

    因此,最简单的解决方案可能是项目条件定义。即在服务项目的项目设置中添加“ Service应用程序 “条件定义。

    当你需要改变行为时,简单地:

    {$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.
    

    有可能你的 形式 应用程序实际上是作为一种服务运行的,使用的是允许 任何 要作为服务运行的应用程序。

    在这种情况下,你的应用程序当然会 总是 是一个 形式 应用程序和处理这种情况的最简单方法是有一个命令行开关,您只在可执行文件的服务定义中指定该开关,这样您的应用程序就可以通过测试该命令行开关做出适当的响应。

    当然,这可以让你更容易地测试你的“服务模式”行为,因为你可以在“调试”模式下运行你的应用程序,而这个开关是在IDE中定义的,但是它不是构建服务应用程序的理想方法,因此我不单凭这个优势来推荐它。这是一种技术,通常只有当您有一个想要作为服务运行但无法修改源代码以将其转换为“正确”服务的exe时才使用。

        7
  •  2
  •   MajidTaheri    13 年前

    可以使用GetStdHandle方法获取控制台句柄。当应用程序以Windows服务运行时,没有输出控制台。如果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.
    
        8
  •  1
  •   M Schenkel    15 年前

    我最终检查了 应用程序.showmainform 变量。

    skamradt基于isformBased的问题在于,在创建主窗体之前调用了其中一些代码。

    我正在使用一个名为svcom_ntservice的软件库。其中一个目的是记录错误或显示消息。我完全同意@rob;我们的代码应该得到更好的维护,并在函数之外进行处理。

    另一个目的是处理失败的数据库连接和查询;我在函数中有不同的逻辑来打开查询。如果它是一个服务,那么它将返回nil,但继续这个过程。但是,如果应用程序中出现失败的查询/连接,那么我希望显示消息并停止应用程序。

        9
  •  1
  •   Community CDub    8 年前

    “跑者”的回答( 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;
    

    即使在没有主窗体的应用程序(例如,cli应用程序)中,此代码也有效。

        10
  •  0
  •   ataman Ed Salgado    9 年前

    检查您的应用程序是否为TserviceApplication的实例:

    IsServiceApp := Application is TServiceApplication;
    
        11
  •  0
  •   Molochnik    7 年前

    我没有找到一个简单的答案,可以很容易地使用,不需要重新编译,并允许使用一个exe作为服务和应用程序。您可以使用命令行参数(如___ myapp.exe_“s_)将程序安装为服务,然后从程序中进行检查:

    如果paramstr(paramcount)='-s',则

        12
  •  0
  •   Z.B.    7 年前

    您可以基于检查当前进程的会话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;