Inno Setup Windows DLL function call with pointer

2019-07-02 03:20发布

问题:

I am trying to set a service's failure actions using Inno Setup's Pascal scripting language. I receive the classic "access violation at address..." error. Seems that it is impossible because the language don't have any support to pointers. Any ideas? Here is the code snippet:

type
  TScAction = record
    aType1 : Longword;
    Delay1 : Longword;
    aType2 : Longword;
    Delay2 : Longword;
    aType3 : Longword;
    Delay3 : Longword;
  end;

type
  TServiceFailureActionsA = record
    dwResetPeriod : DWORD;
    pRebootMsg : String;
    pCommand : String;
    cActions : DWORD;
    saActions : TScAction;
  end;

function ChangeServiceConfig2(hService: Longword; dwInfoLevel: Longword; lpInfo: TServiceFailureActionsA): BOOL;
  external 'ChangeServiceConfig2A@advapi32.dll stdcall';

procedure SimpleChangeServiceConfig(AService: string);
var
  SCMHandle: Longword;
  ServiceHandle: Longword;
  sfActions: TServiceFailureActionsA;
  sActions: TScAction;
begin
  try
    SCMHandle := OpenSCManager('', '', SC_MANAGER_ALL_ACCESS);
    if SCMHandle = 0 then
      RaiseException('SimpleChangeServiceConfig@OpenSCManager: ' + AService + ' ' + 
        SysErrorMessage(DLLGetLastError));
    try
      ServiceHandle := OpenService(SCMHandle, AService, SERVICE_ALL_ACCESS);
      if ServiceHandle = 0 then
        RaiseException('SimpleChangeServiceConfig@OpenService: ' + AService + ' ' + 
          SysErrorMessage(DLLGetLastError));
      try

        sActions.aType1 := SC_ACTION_RESTART;
        sActions.Delay1 := 60000;               // First.nDelay: in milliseconds, MMC displayed in minutes
        sActions.aType2 := SC_ACTION_RESTART; 
        sActions.Delay2 := 60000;
        sActions.aType3 := SC_ACTION_RESTART; 
        sActions.Delay3 := 60000;

        sfActions.dwResetPeriod := 1;           // in seconds, MMC displayes in days
        //sfActions.pRebootMsg := null;         // reboot message unchanged
        //sfActions.pCommand := null;           // command line unchanged
        sfActions.cActions := 3;                // first, second and subsequent failures
        sfActions.saActions := sActions;        

        if not ChangeServiceConfig2(
           ServiceHandle,                       // handle to service
           SERVICE_CONFIG_FAILURE_ACTIONS,      // change: description
           sfActions)                           // new description
        then
          RaiseException('SimpleChangeServiceConfig@ChangeServiceConfig2: ' + AService + ' ' + 
            SysErrorMessage(DLLGetLastError));
      finally
        if ServiceHandle <> 0 then
          CloseServiceHandle(ServiceHandle);
      end;
    finally
      if SCMHandle <> 0 then
        CloseServiceHandle(SCMHandle);
    end;
  except
    ShowExceptionMessage;
  end;
end;

回答1:

You have two problems in your script. Like Deanna suggested you have to use the var keyword in the declaration of the lpInfo parameter.

Also you need to change the TScAction type to an array with two elements.

Here is my script that you can include in your Inno Setup script.

const
  SERVICE_CONFIG_DELAYED_AUTO_START_INFO  = 3;  //The lpInfo parameter is a pointer to a SERVICE_DELAYED_AUTO_START_INFO structure.
                                                //Windows Server 2003 and Windows XP:  This value is not supported.
  SERVICE_CONFIG_DESCRIPTION              = 1;  //The lpInfo parameter is a pointer to a SERVICE_DESCRIPTION structure.
  SERVICE_CONFIG_FAILURE_ACTIONS          = 2;  //The lpInfo parameter is a pointer to a SERVICE_FAILURE_ACTIONS structure.
                                                //If the service controller handles the SC_ACTION_REBOOT action, the caller must have
                                                // the SE_SHUTDOWN_NAME privilege. For more information, see Running with Special Privileges.
  SERVICE_CONFIG_FAILURE_ACTIONS_FLAG     = 4;  //The lpInfo parameter is a pointer to a SERVICE_FAILURE_ACTIONS_FLAG structure.
                                                //Windows Server 2003 and Windows XP:  This value is not supported.
  SERVICE_CONFIG_PREFERRED_NODE           = 9;  //The lpInfo parameter is a pointer to a SERVICE_PREFERRED_NODE_INFO structure.
                                                //Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP:  This value is not supported.
  SERVICE_CONFIG_PRESHUTDOWN_INFO         = 7;  //The lpInfo parameter is a pointer to a SERVICE_PRESHUTDOWN_INFO structure.
                                                //Windows Server 2003 and Windows XP:  This value is not supported.
  SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6;  //The lpInfo parameter is a pointer to a SERVICE_REQUIRED_PRIVILEGES_INFO structure.
                                                //Windows Server 2003 and Windows XP:  This value is not supported.
  SERVICE_CONFIG_SERVICE_SID_INFO         = 5;  //The lpInfo parameter is a pointer to a SERVICE_SID_INFO structure.
  SERVICE_CONFIG_TRIGGER_INFO             = 8;  //The lpInfo parameter is a pointer to a SERVICE_TRIGGER_INFO structure. 
                                                //This value is not supported by the ANSI version of ChangeServiceConfig2.
                                                //Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP:  This value is not supported until Windows Server 2008 R2.

  SC_ACTION_NONE        = 0; // No action.
  SC_ACTION_REBOOT      = 2; // Reboot the computer.
  SC_ACTION_RESTART     = 1; // Restart the service.
  SC_ACTION_RUN_COMMAND = 3; // Run a command.

type
  TScAction = record
    aType1 : Longword;
    Delay1 : Longword;
  end;

type
  TServiceFailureActionsA = record
    dwResetPeriod : DWORD;
    pRebootMsg : String;
    pCommand : String;
    cActions : DWORD;
    saActions : array of TScAction;
  end;

function ChangeServiceConfig2(
  hService: Longword; 
  dwInfoLevel: Longword;
  var lpInfo: TServiceFailureActionsA): BOOL;
  external 'ChangeServiceConfig2A@advapi32.dll stdcall';


procedure SimpleChangeServiceConfig(AService: string);
var
  SCMHandle: Longword;
  ServiceHandle: Longword;
  sfActions: TServiceFailureActionsA;
  sActions: array of TScAction;
begin
  SetArrayLength(sActions ,3);
  try
    SCMHandle := OpenSCManager('', '', SC_MANAGER_ALL_ACCESS);
    if SCMHandle = 0 then
      RaiseException('SimpleChangeServiceConfig@OpenSCManager: ' + AService + ' ' + 
        SysErrorMessage(DLLGetLastError));
    try
      ServiceHandle := OpenService(SCMHandle, AService, SERVICE_ALL_ACCESS);
      if ServiceHandle = 0 then
        RaiseException('SimpleChangeServiceConfig@OpenService: ' + AService + ' ' + 
          SysErrorMessage(DLLGetLastError));
      try

        sActions[0].aType1 := SC_ACTION_RESTART;
        sActions[0].Delay1 := 60000;               // First.nDelay: in milliseconds, MMC displayed in minutes
        sActions[1].aType1 := SC_ACTION_RESTART; 
        sActions[1].Delay1 := 60000;
        sActions[2].aType1 := SC_ACTION_NONE; 
        sActions[2].Delay1 := 60000;

        sfActions.dwResetPeriod := 1;           // in seconds, MMC displayes in days
        //sfActions.pRebootMsg := null;         // reboot message unchanged
        //sfActions.pCommand := null;           // command line unchanged
        sfActions.cActions := 3;                // first, second and subsequent failures
        sfActions.saActions := sActions;        

        if not ChangeServiceConfig2(
           ServiceHandle,             // handle to service
           SERVICE_CONFIG_FAILURE_ACTIONS, // change: description
           sfActions)       // new description
        then
          RaiseException('SimpleChangeServiceConfig@ChangeServiceConfig2: ' + AService + ' ' + 
            SysErrorMessage(DLLGetLastError));
      finally
        if ServiceHandle <> 0 then
          CloseServiceHandle(ServiceHandle);
      end;
    finally
      if SCMHandle <> 0 then
        CloseServiceHandle(SCMHandle);
    end;
  except
    ShowExceptionMessage;
  end;
end;     


回答2:

Try using the var keyword in the declaration for the lpInfo parameter to specify that it's to pass a pointer to the structure to the function.