【问题标题】:Create a small and concise windows service using Delphi使用 Delphi 创建一个小巧简洁的 windows 服务
【发布时间】:2011-04-06 05:36:17
【问题描述】:

我创建了非常简单的 Windows 服务应用程序,使用 Delphi 按时间顺序更新了一些数据文件。该服务应用程序可以编译,并且运行良好,但我对最终的 exe 文件大小不满意。它超过900K。该服务本身不使用表单、对话框,但我看到 SvcMgr 正在引用表单和其他我没有使用的大型废话。

Name           Size Group Package
------------ ------ ----- -------
Controls     80,224 CODE
Forms        61,204 CODE
Classes      46,081 CODE
Graphics     37,054 CODE

有没有办法可以使服务应用程序更小?或者是否有其他服务模板可以在不使用表单等的情况下使用?

【问题讨论】:

  • 如果你想要一个更小的 exe,用 C++ 编写它。
  • 在Delphi中也可以做到。我下面的示例生成了 50K 的大型服务,它可以完成 Delphi 服务可以做的所有事情。在大多数情况下,这是完全没有必要的,但它可以派上用场。如果没有别的,那么作为一个学习过程。无论如何,他要求在 Delphi 中制作一个小型可执行文件,所以你的评论有点粗鲁。
  • 为什么选择 C++?你也可以用纯 C 甚至在汇编器中实现它;)无论如何,Delphi 的优点是你可以像在 C/C++ 中一样使用纯 Windows API 编程,如果你需要它 - 而且你知道如何那样编码。标准方法以易于使用换取大小,猜想这里的大多数人不记得什么时候 Windows 编程被指责“需要一百行代码来显示‘Hello world’”。他还可以尝试使用运行时包进行编译以获得微小的 exe(然后必须重新分发它们 - 但即使 VC++ 也可能需要自己的 DLL)
  • 对于应该间隔运行的小型应用程序,现在首选的方法是使用 Windows 调度程序,而不是让服务坐在那里什么都不做。优点是您只有一个进程处于活动状态,而计划的进程在执行然后终止,而不使用系统资源(RAM、CPU 时间等)。如果您担心您的 exe 大小,您还应该担心它利用多少系统资源可能无用。
  • @David:这是因为 Delphi 中的默认服务提供了一个设计时界面(服务数据模块),可以随时接受组件、事件日志报告代码等。当然,这是有代价的。通常,服务会添加更多代码并使开销可以忽略不计,但对于非常简单的需求,它可能太多了。但这只是 VCL 的实现,没有人禁止添加较小的。我想一个标准的 MSVC 服务需要更多的努力来添加有用的功能。忘记这一点意味着将苹果与橙子进行比较。

标签: windows delphi windows-services


【解决方案1】:

这是我用来创建基于纯 API 的非常小的服务的代码。 exe的大小只有50K。可能会更小,我使用了其他一些可以省略的单位。使用的编译器是 Delphi 7。使用新的编译器可能会更大,但我没有检查。

代码很旧,我没有检查它。我是几年前写的。所以以它为例,请勿复制粘贴。

{
  NT Service  model based completely on API calls. Version 0.1
  Inspired by NT service skeleton from Aphex
  Adapted by Runner
}

program PureAPIService;

{$APPTYPE CONSOLE}

{$IF CompilerVersion > 20}
  {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
  {$WEAKLINKRTTI ON}
{$IFEND}

uses
  Windows,
  WinSvc;

const
  ServiceName     = 'PureAPIService';
  DisplayName     = 'Pure Windows API Service';
  NUM_OF_SERVICES = 2;

var
  ServiceStatus : TServiceStatus;
  StatusHandle  : SERVICE_STATUS_HANDLE;
  ServiceTable  : array [0..NUM_OF_SERVICES] of TServiceTableEntry;
  Stopped       : Boolean;
  Paused        : Boolean;

var
  ghSvcStopEvent: Cardinal;

procedure OnServiceCreate;
begin
  // do your stuff here;
end;

procedure AfterUninstall;
begin
  // do your stuff here;
end;


procedure ReportSvcStatus(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD);
begin
  // fill in the SERVICE_STATUS structure.
  ServiceStatus.dwCurrentState := dwCurrentState;
  ServiceStatus.dwWin32ExitCode := dwWin32ExitCode;
  ServiceStatus.dwWaitHint := dwWaitHint;

  case dwCurrentState of
    SERVICE_START_PENDING: ServiceStatus.dwControlsAccepted := 0;
    else
      ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP;
  end;

  case (dwCurrentState = SERVICE_RUNNING) or (dwCurrentState = SERVICE_STOPPED) of
    True: ServiceStatus.dwCheckPoint := 0;
    False: ServiceStatus.dwCheckPoint := 1;
  end;

  // Report the status of the service to the SCM.
  SetServiceStatus(StatusHandle, ServiceStatus);
end;

procedure MainProc;
begin
  // we have to do something or service will stop
  ghSvcStopEvent := CreateEvent(nil, True, False, nil);

  if ghSvcStopEvent = 0 then
  begin
    ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
    Exit;
  end;

  // Report running status when initialization is complete.
  ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );

  // Perform work until service stops.
  while True do
  begin
    // Check whether to stop the service.
    WaitForSingleObject(ghSvcStopEvent, INFINITE);
    ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
    Exit;
  end;
end;

procedure ServiceCtrlHandler(Control: DWORD); stdcall;
begin
  case Control of
    SERVICE_CONTROL_STOP:
      begin
        Stopped := True;
        SetEvent(ghSvcStopEvent);
        ServiceStatus.dwCurrentState := SERVICE_STOP_PENDING;
        SetServiceStatus(StatusHandle, ServiceStatus);
      end;
    SERVICE_CONTROL_PAUSE:
      begin
        Paused := True;
        ServiceStatus.dwcurrentstate := SERVICE_PAUSED;
        SetServiceStatus(StatusHandle, ServiceStatus);
      end;
    SERVICE_CONTROL_CONTINUE:
      begin
        Paused := False;
        ServiceStatus.dwCurrentState := SERVICE_RUNNING;
        SetServiceStatus(StatusHandle, ServiceStatus);
      end;
    SERVICE_CONTROL_INTERROGATE: SetServiceStatus(StatusHandle, ServiceStatus);
    SERVICE_CONTROL_SHUTDOWN: Stopped := True;
  end;
end;

procedure RegisterService(dwArgc: DWORD; var lpszArgv: PChar); stdcall;
begin
  ServiceStatus.dwServiceType := SERVICE_WIN32_OWN_PROCESS;
  ServiceStatus.dwCurrentState := SERVICE_START_PENDING;
  ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP or SERVICE_ACCEPT_PAUSE_CONTINUE;
  ServiceStatus.dwServiceSpecificExitCode := 0;
  ServiceStatus.dwWin32ExitCode := 0;
  ServiceStatus.dwCheckPoint := 0;
  ServiceStatus.dwWaitHint := 0;

  StatusHandle := RegisterServiceCtrlHandler(ServiceName, @ServiceCtrlHandler);

  if StatusHandle <> 0 then
  begin
    ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
    try
      Stopped := False;
      Paused  := False;
      MainProc;
    finally
      ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
    end;
  end;
end;

procedure UninstallService(const ServiceName: PChar; const Silent: Boolean);
const
  cRemoveMsg = 'Your service was removed sucesfuly!';
var
  SCManager: SC_HANDLE;
  Service: SC_HANDLE;
begin
  SCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  if SCManager = 0 then
    Exit;
  try
    Service := OpenService(SCManager, ServiceName, SERVICE_ALL_ACCESS);
    ControlService(Service, SERVICE_CONTROL_STOP, ServiceStatus);
    DeleteService(Service);
    CloseServiceHandle(Service);
    if not Silent then
      MessageBox(0, cRemoveMsg, ServiceName, MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
  finally
    CloseServiceHandle(SCManager);
    AfterUninstall;
  end;
end;

procedure InstallService(const ServiceName, DisplayName, LoadOrder: PChar;
  const FileName: string; const Silent: Boolean);
const
  cInstallMsg = 'Your service was Installed sucesfuly!';
  cSCMError = 'Error trying to open SC Manager';
var
  SCMHandle  : SC_HANDLE;
  SvHandle   : SC_HANDLE;
begin
  SCMHandle := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);

  if SCMHandle = 0 then
  begin
    MessageBox(0, cSCMError, ServiceName, MB_ICONERROR or MB_OK or MB_TASKMODAL or MB_TOPMOST);
    Exit;
  end;

  try
    SvHandle := CreateService(SCMHandle,
                              ServiceName,
                              DisplayName,
                              SERVICE_ALL_ACCESS,
                              SERVICE_WIN32_OWN_PROCESS,
                              SERVICE_AUTO_START,
                              SERVICE_ERROR_IGNORE,
                              pchar(FileName),
                              LoadOrder,
                              nil,
                              nil,
                              nil,
                              nil);
    CloseServiceHandle(SvHandle);

    if not Silent then
      MessageBox(0, cInstallMsg, ServiceName, MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
  finally
    CloseServiceHandle(SCMHandle);
  end;
end;

procedure WriteHelpContent;
begin
  WriteLn('To install your service please type <service name> /install');
  WriteLn('To uninstall your service please type <service name> /remove');
  WriteLn('For help please type <service name> /? or /h');
end;

begin
  if (ParamStr(1) = '/h') or (ParamStr(1) = '/?') then
    WriteHelpContent
  else if ParamStr(1) = '/install' then
    InstallService(ServiceName, DisplayName, 'System Reserved', ParamStr(0), ParamStr(2) = '/s')
  else if ParamStr(1) = '/remove' then
    UninstallService(ServiceName, ParamStr(2) = '/s')
  else if ParamCount = 0 then
  begin
    OnServiceCreate;

    ServiceTable[0].lpServiceName := ServiceName;
    ServiceTable[0].lpServiceProc := @RegisterService;
    ServiceTable[1].lpServiceName := nil;
    ServiceTable[1].lpServiceProc := nil;

    StartServiceCtrlDispatcher(ServiceTable[0]);
  end
  else
    WriteLn('Wrong argument!');
end.

编辑:

我在没有资源和 SysUtils 的情况下编译了上面的代码。我在 Delphi XE 下得到了 32KB 的可执行文件,在 Delphi 2006 下得到了 22KB 的可执行文件。在 XE 下我删除了 RTTI 信息。我会写博客,因为它很有趣。我想知道 C++ 可执行文件有多大。

EDIT2:

我更新了代码。它现在是一个工作代码。大多数较大的错误应该消失了。它仍然绝不是生产质量。

【讨论】:

  • 您可能还可以删除安装/卸载功能并使用 sc.exe 或类似的东西安装服务。
  • 可能是的,正如我所说,这是一个非常古老的例子。并且知道如何在代码中做到这一点是一个加号。但它可以被剥离。
【解决方案2】:

你可以不用“大废话”。但是你必须自己与 Windows API 对话。查看来源以获取线索。

“大废话”是为了让您更轻松地编写代码。它以减少设计时间换取增加代码大小。这只是您认为重要的问题。

此外,您是否在没有调试信息的情况下进行编译?调试信息大大增加了 exe 的大小。

【讨论】:

  • 是的,与此同时,我正在寻找源代码以提取它使用的核心 API。我非常喜欢 KOL,因为它制作的小应用程序。 :)
【解决方案3】:

如果您使用的是 Delphi 6 或 7,请查看our LVCL open source libraries

您会在这里找到一些标准 VCL 单元的替代品,代码量要少得多。它具有基本的 GUI 组件(TLabel/TEdit 等),只有创建安装程序所必需的。但它被设计为在没有任何 GUI 的情况下使用。

即使您仅使用 SysUtils 和 Classes 单元,可执行文件的大小也将小于标准 VCL 单元。对于某些操作,它也会比 VCL 更快(我已经包含了 FastCode 部分,或者在 asm 中重写了一些其他部分)。非常适合后台服务。

为了处理后台服务,有SQLite3Service.pas 单元,它与LVCL 完美配合。它比直接 API 调用更高级。

这是一个完美运行的后台服务程序:

/// implements a background Service
program Background_Service;

uses
  Windows,
  Classes,
  SysUtils,
  WinSvc,
  SQLite3Service;

// define this conditional if you want the GDI messages to be accessible
// from the background service 
{$define USEMESSAGES}

type
  /// class implementing the background Service
  TMyService = class(TService)
  public
    /// the background Server processing all requests
    // - TThread should be replaced by your own process
    Server: TThread;

    /// event trigerred to start the service
    // - e.g. create the Server instance
    procedure DoStart(Sender: TService);
    /// event trigerred to stop the service
    // - e.g. destroy the Server instance
    procedure DoStop(Sender: TService);

    /// initialize the background Service
    constructor Create; reintroduce;
    /// release memory
    destructor Destroy; override;
  end;


const
  SERVICENAME = 'MyService';
  SERVICEDISPLAYNAME = 'My service';


{ TMyService }

constructor TMyService.Create;
begin
  inherited Create(SERVICENAME,SERVICEDISPLAYNAME);
  OnStart := DoStart;
  OnStop := DoStop;
  OnResume := DoStart; // trivial Pause/Resume actions
  OnPause := DoStop;
end;

destructor TMyService.Destroy;
begin
  FreeAndNil(Server);
  inherited;
end;

procedure TMyService.DoStart(Sender: TService);
begin
  if Server<>nil then
    DoStop(nil); // should never happen
  Server := TThread.Create(false); 
end;

procedure TMyService.DoStop(Sender: TService);
begin
  FreeAndNil(Server);
end;

procedure CheckParameters;
var i: integer;
    param: string;
begin
  with TServiceController.CreateOpenService('','',SERVICENAME) do
  // allow to control the service
  try
    if State<>ssErrorRetrievingState then
      for i := 1 to ParamCount do begin
        param := paramstr(i);
        if param='/install' then
          TServiceController.CreateNewService('','',SERVICENAME,
              SERVICEDISPLAYNAME, paramstr(0),'','','','',
              SERVICE_ALL_ACCESS,
              SERVICE_WIN32_OWN_PROCESS
                {$ifdef USEMESSAGES}or SERVICE_INTERACTIVE_PROCESS{$endif},
              SERVICE_AUTO_START).  // auto start at every boot
            Free else
        if param='/remove' then begin
           Stop;
           Delete;
        end else
        if param='/stop' then
          Stop else
        if param='/start' then
          Start([]);
      end;
  finally
    Free;
  end;
end;

var Service: TMyService;
begin
  if ParamCount<>0 then
    CheckParameters else begin
    Service := TMyService.Create;
    try
      // launches the registered Services execution = do all the magic
      ServicesRun;
    finally
      Service.Free;
    end;
  end;
end.

如果您愿意,可以在on our forum 发布其他问题。

【讨论】:

  • 用LVCL编译,上面的例子编译成一个27136字节的Background_Service.exe文件。手头有完整的 VCL 兼容类。
  • 令人印象深刻!会看看的。
  • 我一定遗漏了一些东西,因为虽然这可行,但在更大的服务项目中使用它时,我无法让它接受我的停止请求......通过“sc stop svcname”。有什么想法吗?
【解决方案4】:

您始终可以使用 Visual Studio 服务模板来创建一个小型服务主机,将您的 Delphi 代码编译成 DLL。有点不整洁,但可能是从你所在的位置开始缩小尺寸的最简单方法。简单的无操作服务是使用静态链接的 91KB 或使用动态链接到 C 运行时的 36KB。

【讨论】:

  • 所以 91 KB 比直接调用 WinSVC API 的 Delphi 可执行文件大。所以不值得同时改变 IDE 和语言! ;)
  • 是的,但如果服务应用程序直接针对 Windows API 而不是使用内置项目模板编写,则 MSVC 中的服务应用程序将比 Delphi 更小。此处引用的 91KB 用于从内置模板创建的服务。
  • 在这个可执行文件大小级别,一些 KB 没有多大意义。只需加载 exe,将其链接到 Windows dll,并初始化其内存管理器,将使用比这更多的 RAM。 exe大小没有多大意义。
  • @A.Bouchez 好吧,我也基本上同意这一点,但 OP 确实提出了这个问题。我确实觉得不必要的浪费很烦人,几乎在原则上。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-16
  • 1970-01-01
  • 1970-01-01
  • 2021-05-17
  • 2011-02-12
  • 2010-10-10
相关资源
最近更新 更多