サービスとVCLフォームアプリケーション(win32アプリケーション)の両方で使用されるコードがあります。基盤となるアプリケーションがNTサービスとして実行されているのか、アプリケーションとして実行されているのかを判断するにはどうすればよいですか?
ありがとう。
編集開始
これはまだ注目を集めているように見えるので、不足している情報と新しい Windows パッチで回答を更新することにしました。いずれにせよ、コードをコピーして貼り付けないでください。コードは、物事をどのように行うべきかを示した単なるショーケースです。
編集の終わり:
親プロセスが 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;
フォーム ベースのアプリケーションでない場合、アプリケーション オブジェクト (Forms.application) メインフォームは nil になります。
uses
Forms, ... ;
function IsFormBased : boolean;
begin
Result := Assigned(Forms.Application.MainForm);
end;
GetCurrentProcessId
とのマッチングはどうEnumServicesStatusEx
ですか?
このパラメーターは、構造体lpServices
の配列を受け取るバッファーを指します。ENUM_SERVICE_STATUS_PROCESS
照合は、列挙されたサービス プロセス ID: に対して行われますServiceStatusProcess.dwProcessId
。
別のオプションは、 を使用してインスタンスWMI
を照会することです。Win32_Service
ProcessId=GetCurrentProcessId
私はそれを疑います
System.IsConsole
System.IsLibrary
期待される結果が得られます。
私が考えることができるのは、Applicationオブジェクトを TObject としてメソッドに渡し、その区別を行い、渡されたオブジェクトのクラス名が
TServiceApplication
or
TApplication
とはいえ、コードがサービスで実行されているのか GUI で実行されているのかを知る必要はありません。おそらく設計を再考し、表示したい (または表示したくない) メッセージを処理するオブジェクトを呼び出し元に渡すようにする必要があります。(知りたいメッセージ/例外を表示するためだと思います)。
このようなものを試すことができます
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;
少なくともForms Application オブジェクトとSvcMgr Application オブジェクトを区別できる場合、1 つのプロジェクトでサービスとフォーム アプリケーションの両方を兼ねることはできません (理想的にはそうではありません)。おそらく、フォーム用に個別のプロジェクトが必要です。コードとサービス コード。
したがって、おそらく最も簡単な解決策は、プロジェクトの条件定義です。つまり、サービス プロジェクトのプロジェクト設定で、Conditional Defines に「 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 をサービスとして実行したいが、ソース コードを変更して「適切な」サービスに変換する方法がない場合にのみ使用される手法です。
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.
「ランナー」(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 アプリなど) でも機能します。
私は実際にapplication.showmainform変数をチェックすることになりました。
skamradt の isFormBased の問題は、このコードの一部がメイン フォームが作成される前に呼び出されることです。
aldyn-software の SvCom_NTService というソフトウェア ライブラリを使用しています。目的の 1 つはエラーのためです。それらをログに記録するか、メッセージを表示します。@Robに完全に同意します。私たちのコードはよりよく維持され、関数の外でこれを処理する必要があります。
もう 1 つの意図は、データベース接続とクエリの失敗です。関数には、クエリを開くためのさまざまなロジックがあります。サービスの場合は nil を返しますが、プロセスを続行します。しかし、アプリケーションでクエリ/接続の失敗が発生した場合、メッセージを表示してアプリケーションを停止したいと考えています。