У меня есть код, который используется как в службах, так и в приложениях VCL Form (приложение win32). Как определить, работает ли базовое приложение как служба NT или приложение?
Спасибо.
У меня есть код, который используется как в службах, так и в приложениях VCL Form (приложение win32). Как определить, работает ли базовое приложение как служба NT или приложение?
Спасибо.
Я действительно закончил проверку переменной application.showmainform.
Проблема с skamradt isFormBased заключается в том, что часть этого кода вызывается до создания основной формы.
Я использую библиотеку программного обеспечения под названием SvCom_NTService от aldyn-программного обеспечения. Одна из целей - ошибки; либо для их регистрации, либо для отображения сообщения. Я полностью согласен с @Rob; наш код следует лучше поддерживать и обрабатывать вне функций.
Другое намерение - для неудачных подключений и запросов к базе данных; У меня есть другая логика в моих функциях для открытия запросов. Если это услуга, то она вернет нуль, но продолжит процесс. Но если в приложении возникают неудачные запросы/подключения, я хотел бы отобразить messaage и остановить приложение.
НАЧАТЬ ИЗМЕНЕНИЯ
Так как это все еще, кажется, получает некоторое внимание, я решил обновить ответ с отсутствием информации и новых патчей для 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) будет равна нулю, если это не приложение на основе форм.
uses
Forms, ... ;
function IsFormBased : boolean;
begin
Result := Assigned(Forms.Application.MainForm);
end;
Я сомневаюсь, что
System.IsConsole
System.IsLibrary
даст вам ожидаемые результаты.
Все, что я могу представить, это передать объект приложения как TObject методу, в котором вам нужно выполнить это различие и проверить, что переданный объект classname является
TServiceApplication
or
TApplication
Тем не менее, вам не нужно знать, работает ли ваш код в службе или графическом интерфейсе. Вероятно, вы должны переосмыслить свой дизайн и заставить вызывающего передать объект для обработки сообщений, которые вы хотите (или не хотите) показывать. (Я предполагаю, что это для показа сообщений/исключений, которые вы хотели бы знать).
Как насчет соответствия GetCurrentProcessId
против EnumServicesStatusEx
?
Параметр lpServices
указывает на буфер, который получает массив структур ENUM_SERVICE_STATUS_PROCESS
.
Совпадение выполняется с идентификатором процесса перечисления: ServiceStatusProcess.dwProcessId
в этой структуре.
Другая опция использует WMI
для запроса Win32_Service
экземпляров, где ProcessId=GetCurrentProcessId
.
Вы можете попробовать что-то вроде этого
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 и 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, который вы хотите запустить как услугу, но не имеете возможности изменить исходный код, чтобы превратить его в "правильную" службу.
вы можете использовать метод 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.
Ответ от "Runner" (fooobar.com/questions/509894/...) выглядел очень полезным, но я не мог использовать его, поскольку не определены ни TProcessList, ни CreateSnapshot. Поиск "TProcessList CreateSnapshot" в Google просто найдет 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).
Проверьте, является ли ваш Applicatoin экземпляром TServiceApplication:
IsServiceApp := Application is TServiceApplication;
Я не нашел простой ответ, который можно легко использовать и не требует перекомпиляции, и позволяет использовать один exe как службу и приложение. Вы можете установить свою программу в качестве службы с параметром командной строки, например "...\myapp.exe -s", а затем проверить ее из программы:
если ParamStr (ParamCount) = '-s', затем