Search code examples
inno-setup

Array variables and dynamic access in [Code] section


My installer has Components which come associated with downloadable files. These things are changing from build to build, so I'm using #insert to create the [Components] section as well as the appropriate entries in the [Files] section. Some of these components rely on common downloadable files.

To now include the correct urls in the downloads page, I'm currently defining array variables that are named like the component and have as values the names of the required downloadable files, for example:

#dim myfeature[2] {"01aed27862e2087bd117e9b677a8685aebb0be09744723b4a948ba78d6011bac", "677756ac5969f814fd01ae677dbb832ab2e642513fea44ea0a529430d5ec1fdc"}

In the code for the download page I'm checking which components where selected via WizardSelectedComponents() and after converting the string to an array of strings, I'm trying to get to the previously defined variable and that is where I'm failing:

function GetDownloads(): Array of String;
var
  Downloads: Array of String;
  SelectedComponents: String;
  SelectedArray: Array of String;
begin
  SelectedComponents := WizardSelectedComponents(False);
  // a custom procedure to parse the comma seperated string
  SelectedArray := ParseArray(SelectedComponents, SelectedArray);

  // trying to get to the constant array now this works:
  MsgBox(ExpandConstant('{#myfeature[0]}'), mbInformation, MB_OK);

  // same but trying to use the selected component value returns this as a literal
  // '+SelectedArray[0]+' instead the expanded value
  MsgBox(ExpandConstant('{#' + SelectedArray[0] + '[0]}'), mbInformation, MB_OK);
end;

So I understand something is up with the # mark but I could not find a way to solve this properly.

Thank you! Markus


Solution

  • ExpandConstant expands Inno Setup "constants", not preprocessor values. See also Evaluate preprocessor macro on run time in Inno Setup Pascal Script.

    You cannot access elements of a preprocessor compile-time array using run-time indexes.

    If you know C/C++, it's like if you were trying to do:

    #define VALUE1 123
    #define VALUE2 456
    
    int index = 1;
    int value = VALUE ## index
    

    I'm not really sure I completely understand what are you doing. But it seems that you need to create an array on compile time from various sources and use it on runtime.

    There are several approaches that can be used for that. But you definitely need runtime array initialized on run time. But the code that initializes it can be generated on compile time.

    An example of the approach follows (and some links to other approaches are at the end).

    At the beginning of your script, define these support functions:

    [Code]
    var
      FeatureDownloads: TStrings;
    
    function AddFeature(
      Feature: Integer; CommaSeparatedListOfDownloads: string): Boolean;
    begin
      if not Assigned(FeatureDownloads) then
      begin
        FeatureDownloads := TStringList.Create();
      end;
      while FeatureDownloads.Count <= Feature do
        FeatureDownloads.Add('');
      if FeatureDownloads[Feature] <> '' then
        RaiseException('Downloads for feature already defined');
      FeatureDownloads[Feature] := CommaSeparatedListOfDownloads;
      Result := True;
    end;
    
    #define AddFeature(Feature, CommaSeparatedListOfDownloads) \
      "<event('InitializeSetup')>" + NewLine + \
      "function InitializeSetupFeature" + Str(Feature) + "(): Boolean;" + NewLine + \
      "begin" + NewLine + \
      "  Result := AddFeature(" + Str(Feature) + ", '" + CommaSeparatedListOfDownloads + "');" + NewLine + \
      "end;"
    

    In your components include files, do:

    #emit AddFeature(2, "01aed27862e2087bd117e9b677a8685aebb0be09744723b4a948ba78d6011bac,677756ac5969f814fd01ae677dbb832ab2e642513fea44ea0a529430d5ec1fdc")
    

    If you add:

    #expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
    

    to the end of your main script, you will see in the Preprocessed.iss generated by the preprocessor/compiler that the #emit directive expands to:

    <event('InitializeSetup')>
    function InitializeSetupFeature2(): Boolean;
    begin
      Result := AddFeature(2, '01aed27862e2087bd117e9b677a8685aebb0be09744723b4a948ba78d6011bac,677756ac5969f814fd01ae677dbb832ab2e642513fea44ea0a529430d5ec1fdc');
    end;
    

    Now you have FeatureDownloads Pascal Script runtime variable that you can access using FeatureDownloads[SelectedArray[0]] to get comma-separated string, which you can parse to the individual downloads.

    This can be optimimized/improved a lot, but I do not know/understand the extent of your task. But I believe that once you grasp the concept (it might be difficult at the beginning), you will be able to do it yourself.


    Another similar questions: