[Mirality Systems Logo]
---
News
Polls
Forum
Software
DynaSig
MAX Code Manager
Inno Setup
Creatures
Pueblo/UE
Fun and Amusement
The BOFH
Feedback
Guestbook

   

 

 

 Inno Setup: Tips & Tricks


Printable version of this article

Foreword

Firstly, please note that I'm just another user of Inno Setup, not one of its developers. To the best of my knowledge, information contained in this article was correct at the time it was written, but it's possible that it contains errors, or that an upgrade to Inno has rendered something mentioned here unnecessary or incorrect. This is intended as an aide, rather than anything definitive :)

Prerequistites

I'm expecting you to be using Inno Setup 5. Some of the material covered here is also applicable to IS4, but for most of the cooler stuff you need IS5. In fact, some of the material is applicable to any sort of programming, not only Inno Setup scripts. A lot of general principles are still relevant.

I'm also expecting you to be reasonably familiar with programming, if not Pascal specifically. This isn't a programming tutorial -- while I will be covering some of the syntax of Pascal, I won't cover much of the semantics -- you're expected to have some basic programming knowledge already :-)

 

Now let's get down to it.

Contents

[Code] basics: readability

The most important thing to bear in mind at all times is that your code needs to be readily understandable. This is true for all programming, incidentally -- not just scripting. Don't forget that your code may need to be read, understood, and maintained by someone else (especially for a commercial or other team project) -- and even if you're working on something completely private, you'll need to be able to come back after three years (having completely forgotten why you were doing certain things) and be able to easily pick up where you left off.

There are a number of techniques that enhance code readability. I'm not going to go into too much detail here (there are plenty of books on the subject already), but I'll mention some of the ones that I think are the most important:

  1. Choose meaningful names.
    This covers any sort of symbols you name -- functions, variables, parameters, anything. Don't be afraid to give it a long name -- you can always use copy & paste if it's hard to type. Avoid using names like "a" or "x" or "thing" -- be descriptive. You shouldn't have to go look back at where it gets assigned in order to figure out what it actually is.
  2. Write short routines.
    Keep your routines short and to the point. Each routine should really only have one task to accomplish, and do the smallest amount of work it has to in order to get there. When you need to do something more complicated, do it as a series of calls to smaller routines. Don't take this too far, though -- remember always that your goal is readability. If all your methods contain only a single line, that's probably not going to be all that readable! :) A general rule of thumb is that each routine should fit within a single "screen" (without having to scroll). If your routines are longer than that, look for sensible segments that can be extracted into their own routine.
  3. Avoid duplication.
    If you're doing exactly the same thing (or almost the same thing) in several different places, then you should try to work out a way to eliminate that duplication, and have it only in one place. That way, if you discover a bug (or simply a better way to do it), you only have a single place that it needs to be changed. This is usually accomplished by extracting the code into a routine of its own, which you can then call wherever it is required. Don't forget that you can pass parameters to the routine -- this lets them be more flexible (and more useful).
  4. Avoid magic numbers.
    This is related to the previous one. Try to avoid using a number when that causes it to potentially lose meaning. Especially when you're using it several times. Think about what would happen if you suddenly needed to use a different number -- how hard would it be to change? Would you even remember (years later) what that number was actually for?

    This one probably needs a more concrete example. Say you're writing code for a custom page. One of the lines might be: NameEdit.Top := ScaleY(15);. This is ok, by itself. But what if you're wanting to position other controls aligned with that one? Say: NameLabel.Top := ScaleY(15);. This is not so ok, now -- you're duplicating the position. Change the second one like so: NameLabel.Top := NameEdit.Top;. This accomplishes two things. First, you've made it more obvious that the two controls are supposed to line up. Second, if you ever move the first control, the second will automatically follow it.

    For another example, see defining constants below.
  5. Keep related code together.
    Sometimes it's not so obvious which parts of your code are related. If you change something in one routine, you may need to alter other routines to compensate. For example, if you add extra controls to a page, you'll need to change the code that saves the user's choices. It makes your life a lot easier if all of that code is nearby in the script -- firstly, because you don't have to go hunting for it, and secondly, because you're less likely to forget about it. See the sections on Forward references and #includes for more details.
  6. Comment where necessary, but do it as little as possible.
    Comments are another code smell. If you can't understand something (or more importantly, don't think someone else, or yourself months or years later, would understand) without a comment, then you should first of all try to rewrite it so that you can understand it without the comment. Choose better variable and routine names. Extract some of the code into a new routine. Whatever it takes. Only add the comment if it's really necessary.

    Note that this does not mean that you should just skip comments entirely, without any attempt to clean up your code. Don't be afraid to put in comments, even page-long comments, if it helps readability. But don't comment just for the sake of commenting, either.

[Code] basics: global variables

Like I said, this isn't really a Pascal tutorial, but I'll cover some of the basics. Global variables are considered a "code smell" -- ie. something that makes your code smell bad, and thus should be used as little as possible. Since ROPS (Inno's scripting language) doesn't support classes, however, you'll find that to do anything nontrivial, a certain amount of global variables are required. You should try to minimise the number of globals you use, but don't do so at the expense of the readability of your code.

To declare a global variable, just put a var block outside all other routines. For example:

var
  DataFolder, ServerName: String;
  ServerTimeout: Integer;
  DatabaseInstalled: Boolean;

Globals are typically declared at the top of your program (ie. at the top of your [Code] section). Keeping them all together makes them easier to find. However, you may find that they're easier to keep track of if you put them immediately above the routines that use them. This is a matter of personal taste. See also the #include section, as that shows a way to do a mix of both.

[Code] basics: comments

The [Code] section uses Pascal-style comments, which are quite different from Inno's standard leading-semicolon. In fact, the leading-semicolon form will not work inside the [Code] section, just as the Pascal comments don't work outside of [Code]. So be careful.

So what are the Pascal comments? There are actually two types. One is the familiar single-line comment, which starts at the comment marker and continues until the end of the line. This is written using two forward slashes (//) (just like in C++). The second type is a block comment, which starts at the opening symbols and keeps on going until it hits the closing symbols, potentially across multiple lines. Two different block comments are available. The first starts with an open brace ({) and ends with a close brace (}), and the second starts with an open parenthesis and asterisk ((*) and ends with an asterisk and close parenthesis (*)).

It's important to realise that if you start a block comment, you must end it with the matching terminator. You can't stop a { comment with *). Also, block comments do not nest. This means that if you already have a block comment in a section of code that you want to comment out, you need to surround it with the other block comment type (or insert extra opening tags). This is one of the reasons why it's a good idea to use the single-line comments most of the time :) And don't worry -- strings take priority over comments, so it won't ignore the Inno constants (though you still need to ExpandConstant them).

[Code] basics: strings

I won't go into too much detail here -- everybody knows what strings are. But it's worthwhile mentioning a few details. First: strings are kept inside single quotes ('). Second, to put the single quote itself into a string, you double it (''). Third, individual characters can be represented either in single quotes (like a one-character string) or with a hash mark (#) followed by its ASCII value as a decimal number. This is particularly handy for unprintable characters.

Hashed characters can be concatenated with each other and with quoted strings without having to use the + operator (though you still need it to join two quoted strings together). For example, you can include a line break in a string like so: 'This is the first line.'#13#10'This is the second line.' (this is the most common use of hashed characters, incidentally). One thing you will need to be careful of, though -- make sure that the hash character is not the first non-space character on the line. If it is, then the line will get misinterpreted as an ISPP directive.

There's one more thing you need to watch out for. Strings, unlike arrays (which we'll cover later), use a one-based index. This means that if you have a string variable text, then text[2] represents the second character of the string. There's no such thing as text[0].

Arithmetic expressions

This is all pretty standard, and should be familiar to anyone with programming experience. The one thing to watch out for is that in Pascal, the slash operator (/) results in a floating-point number, not an integer as you might expect. If you want the integral result of a division, then you use the div operator. If you want the remainder of a division (aka the modulus), then use the mod operator.

[Code] basics: defining constants

Not to be confused with Inno Setup constants (such as {app} -- and which aren't exactly constant anyway), I'm referring here to "real" constants you define yourself in your [Code]. These can be either numbers or strings. They're handy if you want to follow rule 4 above (avoiding magic numbers), and also for rule 3 (avoiding duplication).

const
  DatabaseInstanceName = 'FooBazDb';
  HorizontalMargin = 15;
  VerticalMargin = 10;

[Code] basics: defining enumerations

I'm sure we all know that a Boolean is a variable that can only contain one of two values: True or False. But what if you needed three values? Or four? Or twelve? You could use an integer, but sometimes it's not as obvious what "4" actually represents (see the magic numbers rule!), or it may be completely nonsensical to put "5" in there, or to be able to multiply it. This is where enumerations come in handy. In fact, you may want to use an enumeration even for something that's two-state -- you may be able to give it a more meaningful name that way (rule 1).

var
  DatabaseInstallation: (diNotInstalled, diInstallOnReboot, diInstalled);

The above snippet illustrates a Pascal convention: giving each alterative a prefix from the initials of its parent type or variable. The reason for doing this is because these values all share the same namespace with each other (and with other constants), and so you can get a clash quite quickly.

[Code] basics: defining types

When working with enumerations (above) and arrays (below) in particular, you can get quite complicated constructs for your variable types. You need to make sure that you only specify them once. There are two reasons for this: firstly, to avoid duplication (rule 3), and secondly, to avoid incompatibility. To demonstrate that, have a look at this:

BAD CODE
procedure InstallServer(ServerMode: (smNone, smLocal, smRemote, smBoth));
begin
  // ...
end;

procedure InstallComponents();
var
  ServerToInstall: (smNone, smLocal, smRemote, smBoth);
begin
  // ...
  InstallServer(ServerToInstall);
  // ...
end;

The above code will not compile, even though it may seem reasonable at first glance. You'll get a Type Mismatch error on the line that calls InstallServer. (Actually, point of fact: in the current edition of ROPS you will not in fact get any error, even though technically you should [the duplication is potentially hazardous]. You will however get a runtime error if you try the same trick with static arrays.) Why? It's because the two variables (ServerToInstall and the ServerMode parameter) actually are of two separate types -- albeit two types with a similar definition. Because it has been defined twice, the compiler doesn't know that the two are supposed to be compatible. It errs on the side of caution, making them incompatible until proven otherwise. How do we resolve this? With a common definition:

type
  ServerModes = (smNone, smLocal, smRemote, smBoth);
  
procedure InstallServer(ServerMode: ServerModes);
begin
  // ...
end;

procedure InstallComponents();
var
  ServerToInstall: ServerModes;
begin
  // ...
  InstallServer(ServerToInstall);
  // ...
end;

Note: standard Delphi convention (shared by some flavours of Pascal, including Inno Setup's) is to prefix a type name with "T". Following this convention, ServerModes above should have been named TServerModes, or even TServerMode. For enumeration types specifically, sometimes an "E" is used as a prefix instead (eg. EServerModes). Personally, I tend to not follow this convention, but you may find it useful to distinguish types from variables. My example code will usually not adhere to it, though :)

[Code] basics: arrays

There are times when you need to work with lists of things. Inno already provides direct support for lists of strings (either through TStringList or its base, TStrings), but what if you want a different sort of list, or don't want the overhead of the high-level stringlist classes (not that there is much)? Enter arrays. There are two different kinds of arrays:

  • Static arrays
    These are of a fixed, preallocated size. Useful when you know in advance how big you'll need it to be, or if you want different indexing. Static arrays are the only kind that permit you to use arbitrary indexes.
    type
      SixteenIntegers = array[0..15] of Integer;
      TenStrings = array[1..10] of String;
      FiveBooleans = array[-2..2] of Boolean;
      ArrayOfArrays = array[0..3] of array[1..4] of String;
    While Pascal supports a few additional types (such as multidimensional arrays) and syntaxes (such as specifying only the number of elements), ROPS does not. In ROPS, you must explicitly specify both the lower and upper bounds of an array, and each array can only contain one dimension. (But you can still construct arrays of arrays, as shown above.)

    Accessing the elements is just as simple:
      SixteenIntegers[2] := 15;
      TenStrings[5] := 'test';
      FiveBooleans[-1] := True;
      ArrayOfArrays[3][1] := 'hello';
    Normal Pascal allows you to use characters and enumeration constants as array indicies as well, not just integers. Sadly that is a capability that is currently lacking in ROPS.

    Don't forget the magic number rule as well -- it may be a good idea to define a named constant for the number of elements in the array, or the upper bound of the array. This will make it easier to do iterations over the array later on, especially if that number changes at a later date. This is especially true because ROPS also does not support the Low and High functions, which is how you'd determine the array bounds in classic Pascal. (Note that GetArrayLength only works on dynamic arrays, not static ones.)
  • Dynamic arrays
    These are probably the arrays you'll use the most. They are declared in a similar manner to static arrays -- except without the indicies. We'll see why in a moment:
    type
      IntegerArray = array of Integer;
      StringArray = array of String;
      ArrayOfArrays = array of array of String;
    Dynamic arrays start out containing no elements at all, which means of course that you can't access any of them. You rectify this by calling the SetArrayLength support function. This will allocate the specified number of elements, which you can then access in exactly the same fashion as for static arrays. And because this is a function call, you can of course pass in different numbers (perhaps based on user input). The lower bound for dynamic arrays is always zero.

    What happens when you call SetArrayLength on an array that already contains data? Well, this isn't documented, so it may be subject to change, but in the current release at least it will resize the array nondestructively. This means that if you specify a smaller size, it will drop elements off the end, and for a larger size it will add extra ones to the end -- keeping whatever values were stored in the other elements.

    Dynamic arrays are very good candidates for passing as parameters to routines. This is because they can be sized differently for different calls -- so the same code can be used to process a six-element array as for a twenty-element array. To do this, of course, the code will need to find out how big the array is -- and that's what the GetArrayLength function is for.

    You cannot have a multidimensional dynamic array either, but just as with static arrays, you can have arrays of arrays, which are similar. (In fact, you can even have hybrids, such as a dynamic array of static arrays, or the reverse.) An array (static or dynamic) of dynamic arrays allows you to create what's called a jagged array, where each "row" can contain different numbers of elements. For example:
    procedure Jaggedness();
    var
      Jagged: array of array of Integer;
    begin
      SetArrayLength(Jagged, 3);
      SetArrayLength(Jagged[0], 4);
      SetArrayLength(Jagged[1], 2);
      SetArrayLength(Jagged[2], 5);
      Jagged[0][0] := 2;
      Jagged[0][1] := 5;
      // ...
    end;

Beginnings and endings: statement blocks

In Pascal, statement blocks start with begin and end with end. Unlike some other languages, you cannot start new blocks wherever you want -- you can only do so when they're associated with some other statement, such as an if or for.

Most of the conditional and looping statements (which I'll get to in a moment) act on only a single statement. To get them to act on multiple statements, you must enclose them in a begin/end block. And it does no harm to enclose a single statement in a block, either -- or even no statements at all!

This all leads to my personal standard coding style, which you will see throughout this document. It's one that I recommend to others, as I think it's less error-prone than other formats. At its core, there are two key rules to follow:

  1. Indent to show statement structure and grouping.
  2. Always use begin and end, even when only enclosing a single statement.

The first rule is an offshoot of the general principle to keep related code together. Provided you do it correctly, it allows you to see at a glance which code is inside which block, and thus what code is affected by conditional and looping statements.

The second rule acts as a safety mechanism. By doing this from the outset things become easier if you have to come back later and add additional code. If you've got an if statement that executes a single other statement without a begin/end block, it's far too easy to just add an extra statement afterwards, thinking that the if will apply to it too. Which of course it won't, until you rewrite it with a block.

[Code] basics: Conditional statements

Decisions are typically the most common thing you'll be doing, as they tend to be the heart of any system. Like most languages, Pascal uses the if statement to handle decision making:

  if condition then begin
    // ...
  end;
  
  if condition then begin
    // ...
  end else if condition then begin
    // ...
  end else begin
    // ...
  end;

Note that semicolons are forbidden immediately before an else; this is because in Pascal semicolons are used to separate statements, and the else is considered part of the same statement as the if. Stick to the begin/end rules that I've already outlined and you should be fine. (This is also one of the reasons why I keep begin and end on the same line as the if statement -- things read better when you get to the else, and writing it like this actually reinforces the fact that you shouldn't put a semicolon on the end just before the else.

Now onto the conditions themselves. A valid condition is simply any expression with Boolean type. Which could be simply calling a function that returns a Boolean, or it could be using a relational operator, or even some combination of both.

The relational operators are fairly straightforward: = tests for equality, <> for inequality, and then you've got less-than, greater-than, etc, as you'd expect. Note that in Pascal = always represents an equality test, unlike in some other languages -- assignment uses the := statement instead, which cannot be used inside an expression anyway.

The boolean operators are and, or, and not. (There's also an xor operator, but that's seldom useful.) These are fairly self-explanatory, but there is one important thing you need to remember: they have quite high precedence. This means that a statement like this:
  if a = 5 or a = 6 then begin
.. will not do what you'd naturally expect. Since the and has higher precedence, the above is equivalent to this:
  if a = (5 or a) = 6 then begin
.. which will end up doing some quite strange bitwise operations and not at all what you thought it would. The solution is to explicitly bracket everything else around the boolean operators, like so:
  if (a = 5) or (a = 6) then begin
See True, false, or maybe below for another bit of potentially unexpected behaviour.

There's one other type of conditional block in Pascal, and that's the case statement. It's similar to switch in C and Select Case in VB -- it jumps to one particular block of statements depending on the value of a single variable. That variable must be an ordinal type (integer, character, or enumeration) -- you can't use strings or floating-point numbers, as with constants. And the selectors cannot be variables, they must be constant.

  case variable of
    option1:
      begin
        // ...
      end;
    option2:
      begin
        // ...
      end;
    option3:
      begin
        // ...
      end;
  else
    begin
      // ...
    end;
  end;

There are a few things worth pointing out here. Note that I've changed my standard indentation pattern a little. This is because it's better to preserve code readability than to stick to rigid style rules, and it just so happens that the above style is (I think) the clearest and most readable way to structure this particular statement. Note the final else and end -- they don't have a corresponding begin. That's because they relate directly back to the case statement itself. The else block is executed if none of the other selectors match. (It's optional, but it's usually worthwhile to include, even if for no other reason than to display an error dialog saying "Oops, the developer forgot to handle something. Tell them what an idiot they are." :-)

In addition, you are not limited to single values for those selector options. You may also specify comma-separated values (eg. 4, 6, 129), ranges of values (eg. 4..79), and of course combinations of the two. (You can't "fall through" one handler block to another, the way you can in C.)

[Code] basics: Looping constructs

It's fairly common to want to do things repetitively, and this is where loops come in. Once again, looping structures should be familiar to anyone who is used to programming, so I won't explain in detail. What you may not be familiar with is the specific syntax used in Pascal for these structures, and so that's what I'll cover here:

  // A for loop, with increasing index:
  for variable := low to high do begin
    // ...
  end;
  // A for loop, with decreasing index:
  for variable := high downto low do begin
    // ...
  end;
  // Do something while the condition is true:
  while condition do begin
    // ...
  end;
  // Repeat something until the condition is true:
  repeat
    // ...
  until condition;

There are four points worth noting about these. First, Pascal does not support "steps" in the for loop. The index variable you specify will either increase by one or decrease by one each time -- you can't make it increase by two each time, for example, although you can accomplish the same effect through multiplication within the loop body.

Second, the variable in for loops is limited to integers in ROPS. (In regular Pascal, you can also use characters and enumeration constants, just like with arrays.)

Third, the while loop will be skipped entirely if the condition is false the first time. The repeat loop will always execute at least once. This should be pretty much as you'd expect, given the placement of the conditions within the loop structure.

Finally, note that the repeat...until loop does not use the begin or end keywords. Despite this, it still permits you use multiple statements within the loop.

[Code] basics: Escape clauses

Normally, a routine ends when execution "falls off" the end. At that point, control returns to the calling routine (or to Inno, for an event function). However, sometimes you want to leave the routine earlier. The most common case is returning early from a function. Pascal doesn't have an equivalent to C's return x; statement (which returns a specific value). Instead, whatever value is stored in the Result variable when control leaves the function is the value that's returned.

A few useful points about the Result variable: this is an implied variable. You don't need to explicitly declare it -- it's automatically created based on the function's definition. It is always the same type as the function itself. In all other respects, however, it is a normal variable. You can assign and reassign it as much as you want, use it in comparisons, whatever. But whatever it holds when the function returns is what gets returned to the caller.

So, now we know about Result. A fairly common coding style is to test for preconditions near the start of your routine, and simply bail out (with some appropriate return value) if conditions aren't satisfactory. For example, in your InitializeSetup function you may want to check that a particular third-party component is already installed. If it isn't, then there isn't really much point in continuing. Now, you could do this with nested if statements -- but that quickly gets awful. Have a look at this:

BAD CODE
function InitializeSetup(): Boolean;
begin
  Result := False;
  if Component1IsInstalled() then begin
    if Component2IsInstalled() then begin
      if Component3IsInstalled() then begin
        Result := True;
        // ...
      end else begin
        MsgBox('Sorry, no component 3.', mbError, MB_OK);
      end;
    end else begin
      MsgBox('Sorry, no component 2.', mbError, MB_OK);
    end;
  end else begin
    MsgBox('Sorry, no component 1.', mbError, MB_OK);
  end;
end;

Is this good code? No. All those nested if statements are messy -- and what's more, they violate the proximity guideline: the error message telling the user that component 1 couldn't be found is nowhere near the code that does the checking. So, let's write it a different way. The next approach is sometimes referred to as "exit early" -- and that's exactly what we're going to do, with the help of the exit keyword:

function InitializeSetup(): Boolean;
begin
  Result := False;
  if not Component1IsInstalled() then begin
    MsgBox('Sorry, no component 1.', mbError, MB_OK);
    exit;
  end;
  if not Component2IsInstalled() then begin
    MsgBox('Sorry, no component 2.', mbError, MB_OK);
    exit;
  end;
  if not Component3IsInstalled() then begin
    MsgBox('Sorry, no component 3.', mbError, MB_OK);
    exit;
  end;

  Result := True;
  // ...
end;

Doesn't that look better? Another way of writing it would have the Result := False; line removed from the start and instead written immediately before each of the exit; lines. This makes it look a lot more like the C return statement. Of course, doing that in this case would lead to a bit of duplication, but it's not an especially bad form, so go ahead and do it that way if you feel more comfortable with it.

So, exit will exit from the routine. What if you don't want something that drastic? What if you just want to break out of a for, while, or repeat loop? The question brings the answer: it's the break keyword. Similarly, if you want to abandon the current iteration of the loop and jump right back to the increment/condition, you use the continue keyword.

[Code] basics: The evils of with

I'm including this section just so people can recognise this particular language construct and understand what it actually means. In my opinion, however, this statement is the Spawn of EvilTM and should not be used under any circumstances.

BAD CODE
  MyPage := CreateInputOptionPage(wpSelectComponents, 'a', 'b', 'c', True, False);
  with MyPage do begin
    Add('1');
    Add('2');
    SelectedValueIndex := 1;
  end;

So, what does all that mean? Within a with block, you can refer to methods and properties of a specific variable without having to specify the variable name each time. In the above snippet, for example, the first line is actually calling MyPage.Add('1');, and the third is equivalent to MyPage.SelectedValueIndex := 1;.

"What's wrong with that?", I hear you ask. The problem is ambiguity. Look again. If you hadn't noticed that with statement, or knew how it worked, you'd think that this is just calling a normal procedure and assigning to a regular variable. So using with has actually made it harder to understand this code, which is of course a step in the wrong direction.

It gets worse. Imagine that the with block above was in a procedure somewhere, and took a parameter called Name, which we want to add as a third option. So let's insert a new line just below the two existing Adds, right? Something like this:
  Add(Name);
But that won't work. You see, MyPage also has a property called Name. And because the with block is closer than the parameter declaration, the property takes precedence -- so you're actually calling MyPage.Add(MyPage.Name);. And there is in fact no way to access the parameter from within the with block -- it's completely hidden.

Ok, so we'll just look up all the properties and methods, and then make sure we pick a unique name. That's safe, right? Wrong. Inno is an evolving product, which means that new properties and methods are getting added to objects all the time. So what may be safe today, could be unsafe tomorrow -- and suddenly your code stops working for no readily apparent reason. That sort of problem is very difficult to spot -- especially if the code itself is only executed under certain conditions.

Given all that, why would people use with? It only provides a single benefit: less typing. It's frequently introduced when people want to manipulate several properties on an object buried several levels deep, for example if you wanted to mess with the properties of WizardForm.NextButton.Parent. But there's a much safer way to do that -- just declare a local variable with a nice short name. Then you can assign the object to it using its long-winded name and thereafter just use the short name for everything.

The moral of the story: don't use with. Ever. It provides no benefits and comes with significant dangers.

(For the curious: I have less objection to VB's With statement. The difference is that in VB's With, you have to use a leading dot when referring to a property or method on the named variable, which eliminates the ambiguity problem. Until you start nesting with statements, of course. (shudder))

[Code] basics: True, false, or maybe: when is a boolean not a boolean?

Ah, boolean logic. If you've made it this far then I'd hope that you already know how boolean logic works in general -- you did read the Conditional statements section, didn't you?

When writing conditional logic, some people like to be very explicit, writing such things as:
if Test = True then ...
You may have also noticed that I never do that. There's two very good reasons for that:

  1. It's redundant
    The only time that something can be equal to True is of course when it is already a Boolean. And of course all the if statement wants is a Boolean. You've already got what it wants, so why go do an unnecessary comparison? Aha! But what about testing if it's equal to False? Well, you could do that, but it's tidier just to use not instead (eg. if not Test then).
  2. It's potentially wrong
    Booleans can only equal True or False, right? Wrong. Booleans are internally stored as bytes, which can of course contain any number from 0 through to 255. Normally, False is encoded as 0, and True as 1. But sometimes things can get confused (especially when you're interacting with external code which has possibly been written in a different language, which might have different definitions for True and False [for example, VB defines True as 255, not 1]), and you get some other value instead.

That last one deserves a bit more explanation. Fortunately, in logic contexts (for example, in the if statement or when using the boolean operators and and or) Pascal has a rule which says "if it's zero, it's False, and if it's not False, then it's True". So if you somehow get a "17" in there, then your code will still work if you just use if x then or if not x then. But if you had said if x = True then... well, True is 1, and 17 isn't 1 now, is it? So you've gotten yourself into a weird situation where the variable is True but is not equal to True.

Comparing against False is not as dangerous. Just about everybody agrees that False is 0, and 0 is the only value that can be False. So if you really must use a comparison, you can fairly safely do comparisons in terms of False (eg. if x = False then, if x <> False then). But really, it's much cleaner if you just leave out the comparison altogether.

So, in summary: don't ever compare anything with True!

Forward references

In Pascal, you must declare a routine before it can be called. Sometimes that just isn't convenient, especially when you're trying to keep related functions together. This is where forward references come in handy:

procedure Routine2(); forward;

procedure Routine1();
begin
  // ...
  Routine2();
  // ...
end;

procedure Routine2();
begin
  // ...
end;

Using ExpandConstant

You'll frequently need to use Inno constants from within your [Code]. To do this, you need to use the ExpandConstant support function. Why? Because the actual value of those constants isn't actually known until installation time (and sometimes not even then -- {app} for example isn't known until after the wpSelectDir page). This means that the compiler can't do anything with them, and you need to call a function to expand them at runtime.

Using ExpandConstant is easy. Here's an example: if FileExists(ExpandConstant('{app}\readme.txt')) then ... -- this checks to see if the readme.txt file exists in the application's folder. Note that you can put other text in there as well, not only the constant itself. This is in fact recommended, since Inno will automatically compensate and generate a valid path, whether {app} already has a trailing backslash or not.

ISPP variable "constants" (such as {#SomeVariable}) are an exception to this rule. You do not need to use ExpandConstant on them (unless of course the ISPP variable expands to contain {app} or some other constant). This is because unlike the Inno constants, the actual value of the ISPP constants is known at compile time.

Using Check functions

Sometimes, to decide whether you should install a particular file or run a particular subinstall task, you just need to use a more complicated condition than that permitted by [Components], [Tasks], Flags, or Windows versions. That's when it's time to use a Check function. Again, these are covered pretty well in the helpfile, so I'll be brief. Check functions must be functions, and must return a Boolean. Normally they won't take any parameters, but you can specify some if you want to.

Another point worth mentioning is that the Check parameter itself is considered outside of the Pascal "scope" rules, meaning that you do not need to define a forward reference for it, or to make sure your [Code] section comes before the others, or anything like that.

Now here comes the fun part. Check functions are not guaranteed to be called, nor are they guaranteed to be called only once. That's not quite as bad as it sounds, and when you think about it you will see why it's that way. Inno will call the Check function whenever it thinks, to the best of its knowledge, that the entry in question should be used. The Check function acts as a sort of tiebreak, in effect. If Inno has already decided that the entry shouldn't be installed, then there's no point in calling the Check function -- nothing it can do would change Inno's mind. If Inno thinks that it should be installed, however, it then gives the Check function a chance to veto it. And for some types of entries, Inno may need to ask the question again -- and if it does, then it will have to call your function again. Why? Simply because Inno has no idea what you're actually checking for. For all it knows, conditions might have changed in the interim, and while it shouldn't have been installed before, now it should be. The upshot of all of this is that you need to make sure that your Check functions simply answer the question, without any other side effects.

For most types of Check functions (those on [Files] or [Registry] entries for example), Inno will wait until it's performing the actual installation before asking. This means that you'll be able to use custom wizard pages to make your decisions, among other things. For some types, however (such as those on [Components]), Inno will ask the question almost immediately, before any wizard pages have been shown. That's because of when Inno fills out all the pages -- just before it calls InitializeWizard, in fact. So for this type of Check function you won't be able to use custom pages to help make your decisions. If you're in doubt about when a particular Check function is getting called, simply put a MsgBox into it. That'll quickly show you exactly when and how often it's getting called -- although bear in mind that a different version of Inno may end up calling it differently.

As mentioned above, Inno may call your Check functions more than once, just in case your conditions change. But what if your conditions don't change? What if your test is particularly lengthy, or won't work quite the same if you do it a second time? There are a couple of ways to resolve this:

  1. Work it out upfront
    Simply perform your test at the start of the installation, either in InitializeSetup or in CurStepChanged (see Hooking into the start or end of the installation). Store the result into a global variable. In your Check function, simply return that variable.
  2. Work it out the first time, and cache the answer for later
    Just like it says: the first time your Check function is called, work out the answer, and cache it before returning. The second and subsequent times, simply return the cached value. Now you could do this with a pair of Booleans, but my favourite technique is to use a trinary enumeration instead. And just for a change, let's give this one a real, live, practical example:
    var
      DotNet11Installed: (dnUnknown, dnNotInstalled, dnInstalled);
    
    function IsDotNet11Installed(): Boolean;
    begin;
      if DotNet11Installed = dnUnknown then begin
        DotNet11Installed := dnNotInstalled;
        if RegKeyExists(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322') then
          DotNet11Installed := dnInstalled;
      end;
      Result := DotNet11Installed = dnInstalled;
    end;
    
    function InitializeSetup(): Boolean;
    begin;
      DotNet11Installed := dnUnknown;
      Result := True;
    end;
    Ok, so RegKeyExists may not be the most complex test in the world, and doesn't really need this sort of protection. But the principle is sound.

One final note, before I leave the subject of Check functions: Check functions are only called at install time, never at uninstall time -- even if they're placed on an [UninstallRun] entry. This is intended to cover situations where, for example, you have a Check function that decides there's no need to install a particular subcomponent. Similarly, you can place the Check function on the [UninstallRun] entry as well to indicate to it that there's no need to run its uninstaller. Note that due to the way that Inno's uninstall logs work, if the installer is run multiple times (perhaps when upgrading to several successive versions), then the entry will be executed if the Check function returned True on any of those times. (If you forgot to add the RunOnceId, then it may even get called multiple times!)

One common mistake is to think that Check functions are suitable for asking the user a question at uninstall time, such as whether to delete a set of data files or not. They're not suitable for this, as outlined above. If you want to do that, you'll have to do it using pure [Code], not with Check functions.

BeforeInstall and AfterInstall parameters

Almost all "action" entries (such as [Files] and [Registry]) can be given a BeforeInstall or AfterInstall handler (or even both!). Again, the help file is pretty clear on how these work. But again, I'll give a brief intro.

Either routine must be a procedure. They may take any number of parameters, though typically you won't need any. And just like Check functions, they will not be called if Inno decides (for whatever reason) that the entry in question should not be installed. Unlike Check functions, however, they are guaranteed to be called exactly once (if at all), and if the BeforeInstall routine is called, then the AfterInstall routine will be as well (unless the installation failed and aborted on that particular entry).

These are handy for performing minor tasks relating to a particular file. For example, you may put an AfterInstall handler on a configuration [File] to first copy in a basic template file, and then update it based on the user's choices in the wizard.

Using code constants

You'll sometimes need to write some complicated logic to obtain the appropriate value for something (or at least, more complicated than that permitted by Check functions and the other standard mechanisms). Code constants are your friends here. You can use code constants almost everywhere you can use standard constants -- there are a few [Setup] entries that they don't make sense for, and of course there's no point at all in using a code constant with ExpandConstant.

The format is very simple, and explained fairly well in the help file. You can use either this form: {code:FunctionName} or this one: {code:FunctionName|Parameter}. Note in particular that there can be only one parameter, which is passed as a string and does not need quotes around it (in fact, if you put quotes in then they will be passed into the function as well).

In the [Code] section, you must declare a corresponding function as follows:

function FunctionName(Default: String): String;

You can of course change the function and parameter name to whatever you wish, but the rest must stay the same. Note that even if you are calling it using the first form (specifying no parameter), the parameter is still required -- it will simply be assigned an empty string. You're free to either use the parameter or ignore it, as you wish. The function will only be called when Inno needs to obtain the actual value -- so if it already knows that the entry you've used it on should not be installed, then the function will never be called.

Just like Check functions, order is unimportant here -- you do not need to declare or define your function earlier than the line that uses the {code:...} constant. And also just like Check functions, it's possible for the function to get called multiple times, even if it's only being used on a single entry. So it's best to keep things simple. In fact, typically these functions do little more than return the contents of a global variable that was calculated earlier.

Using #include

I've said before that you should try to keep related routines together. But even doing that doesn't always help, when you have a lot of routines in the same script file. The answer? Break them out into different files, of course! This is what the #include directive is for. And it's even supported by native Inno, so you don't need to have ISPP installed to use it.

So what does #include do? Basically it takes the contents of the named file and treats it exactly as if you'd copied and pasted the whole thing at the exact position where the #include line was. Thereby including one file within the other one (hence the name).

First of all, of course, you need to create a second file to put things into. This can be called whatever you want, and have any extension you want. I usually use .isi (for Inno Setup Include), but you'll probably find it easier to just keep using .iss (if you use any other extension, you have to keep switching to All files or you won't be able to see them in the Open dialog).

The very first line of the file should be the [Code] section opening. This is fairly important -- don't even put any comments above this line. Why? Because [Code] and regular Inno script have different comment styles, and either could be in effect (it depends on where the #include is located). So it's safest to begin with a known state.

Next, put the global variables you want to define here, and finally the routines. It's important to remember that everything shares a common namespace, so you can't define a variable called "NameEdit" (for example) in both places. For this reason, you may want to put some sort of prefix (for example, "MyPage_" for MyPage.isi) on the names that are supposed to be "private".

Now you need to put the #include line into your main script. Remember that everything in the file gets treated as if it had been pasted in where the #include line itself is. The normal rules of Pascal still apply -- you must declare something before you can use it. This means that where you put the line is very important. If possible, you need to put it after everything from the main script that it uses, but before everything that uses it. You may need to rearrange your functions a little to achieve this, or insert some forward references.

The layout that I typically use is as follows (this is by no means the only way to do it, nor possibly even the best way to do it. It's simply the way I do it):

  • Main script
    1. Public constants
    2. Public type definitions
    3. Public global variables
    4. Public utility functions (that can be called by anything)
    5. #includes
    6. Routines private to the main script
    7. Inno event handlers (such as InitializeWizard)
  • Include file
    1. Global variables that you want to "export" to the main script
    2. "Private" constants
    3. "Private" types
    4. "Private" variables
    5. "Private" functions
    6. Entrypoint functions (the ones intended to be called from the main script

I put "private" in quotes there because as I said earlier, these functions are not truly private -- they're actually accessible to anything after the #include line itself. Another possible structure goes like this:

  • Main script
    1. Public constants
    2. Public type definitions
    3. Public global variables
    4. Public utility functions
    5. #include .ish files
    6. Routines private to the main script
    7. Inno event handlers
    8. #include .isi files
  • .ish files
    1. Global variables that you want to "export" to the main script
    2. Forward references for the entrypoint routines
  • .isi files
    1. "Private" constants
    2. "Private" types
    3. "Private" variables
    4. "Private" functions
    5. Entrypoint functions

This structure is a bit more work. It focuses on preventing your main script from calling anything in the include files (apart from the designated entrypoint routines) -- which the first didn't. It won't prevent your include files from calling routines in the main script that they shouldn't (which the first does). Neither one will protect against one include file calling something in another (which shouldn't be allowed, if you're maintaining proper separation) -- however there is a way to test for that. Once you've gotten everything compiling and working properly, simply reverse the order of all the #include lines (don't swap the .ishes and the .isis, just the lines within each group). If it still compiles and runs after that, then you can be confident that there's no coupling between your include files.

For more detailed examples of #include files in action, see my ISD article. And for more information on one of the main drawbacks to include files (and a workaround), see Debugging with include files.

Debugging with include files

Having your script divided across multiple files is both good and bad for readability -- it's good for code isolation (keeping functions self-contained) and for keeping related routines together. It's not so good for browsing, as the Inno IDE can (at the moment) only open a single file at a time. (Though you can, of course, use a different editor as your IDE.) And unfortunately, it's absolutely terrible for debugging -- again, because the IDE can only load a single file. And here your options are more limited, since the Inno IDE is the only thing that can do debugging. So what can we do?

Well, you've basically got three options (though there are other variations on these themes):

  • Debug with message boxes
    Sprinkle calls to MsgBox around the problem area, and then just run without debugging. Which boxes come up and what they contain can give you quite a bit of information. Don't forget to remove the calls once you're done! :)
  • Temporary copy back to main script
    Copy all of the code from your include file into the clipboard. Switch back to your main script, comment out the #include line, and then paste all that code in its place. Now you can do your debugging. When you're done, you can cut the code back to the clipboard, uncomment the include, and paste the code back into the original include file.
  • Write everything to a single file and debug that
    You'll need ISPP for this one. At the very bottom of your main script, as the very last line (even after any #includes), put the following line:
    #expr SaveToFile("debug.iss")
    (make sure you choose a unique name -- this will overwrite whatever filename you pick!) Now compile your script. This will create a new file (with the name you chose), with all the preprocessing already done -- meaning that it contains everything from all of your includes (along with resolving any other ISPP directives you may have used). Open that file in the Inno IDE and debug as normal.

    The tricky part, however, is next. If you had to make any changes to get things to work, you need to make those same changes to your original files. Remember, the next time you compile your "real" script, debug.iss will get overwritten. You'll also need to make sure, before each isolated debugging session, that the #expr line is still the very last one in the file. Some editor programs will insert new sections at the end of the file, thereby putting the #expr in the wrong place.

Hooking into the start or end of the installation

Best practise is to not alter the user's system in any way, until they click the final Next button (on the wpReady page). Sometimes it's tempting to carry out commands in response to the user's selection on custom wizard pages, but you should fight that urge. The user must always be allowed to go back and change their mind, or even cancel the entire installation, without any consequences. As a result, the wizard pages are used simply as information-gatherers, with action to be performed later. When is the best time? That depends on what you're doing.

There are two places where it's nice and easy to attach additional actions to (in addition to the BeforeInstall and AfterInstall parameters). Naturally enough, these are the start of the installation process, and its end. I'll start with a snippet:

procedure DoPreInstall();
begin
  // ...
end;

procedure DoPostInstall();
begin
  // ...
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
  if CurStep = ssInstall then begin
    DoPreInstall();
  end else if CurStep = ssPostInstall then begin
    DoPostInstall();
  end;
end;

This is your starting point. CurStepChanged is of course one of the standard Inno event routines, and can only be defined once in your script -- so if you've already got one, then you'll need to merge them (most likely by moving code from your existing procedure into either DoPreInstall or DoPostInstall). And speaking of those routines:

  • DoPreInstall will be called after the user clicks Next on the wpReady page, but before Inno installs any of the [Files] and other standard script items.
  • DoPostInstall will be called after Inno has completed installing all of the [Files], [Registry] entries, and so forth, and also after all the non-postinstall [Run] entries, but before the wpInfoAfter or wpFinished pages. However it may still be before some COM components are registered -- if Inno decides that a reboot is required, it won't carry out the regserver until after the reboot.

Custom wizard pages

I won't go into too much detail here, as there are much better places to find information. Your first port of call should be Inno's help file -- specifically the "Custom Wizard Pages" topic, and the "Support Functions Reference" and "Support Classes Reference". From there, you should also look at two of the example scripts supplied with Inno, namely CodeDlg.iss and CodeClasses.iss.

Once you're familiar with the basic idea, you might also find it useful to read my ISD specification (which is by no means an official Inno spec, it just seems to me like a good way to do things).

But just as a quick overview, if you're coming to Inno 5 from having previously used custom pages in Inno 4, you'll find it considerably different.... but better :-) Rather than having to implement all the page-tracking yourself, it's all handled internally by Inno in the same manner as its own standard wizard pages, thereby allowing you far greater flexibility with much more readable code.

The general idea is that you should create all the wizard pages that could be used by your setup up front, inside the InitializeWizard handler -- even if most of the time the user will never see that page. This allows Inno to keep proper "housekeeping" and ensures that pages don't get mysteriously duplicated, which can happen if you try to follow the Inno4 model of creating the pages as the user reaches them. Instead, Inno provides the ShouldSkipPage handler, which lets you determine under what conditions the page should be shown and what conditions it should be skipped.

There are two different "groups" of custom pages provided by Inno. The first are what I tend to call "template pages", and are created by the various CreateInputXXXPage and CreateOutputXXXPage functions. These are prestructured for gathering or displaying a specific type of information, and provide lots of additional properties and methods to help you with that task. Unless your needs are esoteric, these are what you'll almost always use when building custom pages. You'll find examples of these in the CodeDlg.iss example script.

The second are true custom pages, created by means of CreateCustomPage. This gives you a blank slate on which you can put whatever controls you want, in whatever layout you want. Naturally, you lose out on the helper functions, but you gain in flexibility. You'll find examples of this in the CodeClasses.iss example script.

And that's all I'll say on the topic for the moment. Review the examples, the help, and the ISD page, and that should get you on your way to developing custom pages of your own!

Installation behaviour

One common mistake people make when they start implementing their own custom pages is to take action the moment the user clicks on the Next button -- perhaps by writing some information to the Registry, perhaps even by beginning an automatic uninstall of the previous version of the application.

Don't do that.

Go back and read again what I said in Hooking into the start or end of the installation. A user of your setup program should be able to click their way all the way up to the wpReady page, then suddenly change their mind and click Cancel -- and their system should not have been changed in any way. Only once they pass wpReady and agree to start the installation itself should it start to modify things.

The upshot of all this is that while you might be asking all these questions of the user in your shiny new custom pages, you should simply save the results until later on. Once the installation has actually begun, then it's time to pull those answers out and act on them. Perhaps in CurStepChanged(ssInstall) or CurStepChanged(ssPostInstall), or even a BeginInstall/EndInstall handler somewhere. But not before then.

Afterword

This article is still a work in progress. I will be gradually extending it, as I get time, as new ideas come to me, and as I receive feedback on what areas you think it would be useful to cover. To submit some feedback, just use the link in the menu to the left (though you'll need to scroll back to the top of the page to see it). Don't be shy -- I welcome feedback of any sort, whether it is a simple comment, a suggestion, or a death threat wrapped around a brick. It's all good. Well, mostly all good :-) You'll also find me hanging around in the Inno support forums. I even answer questions occasionally.

Made with a Text Editor! Valid XHTML 1.0! Valid CSS!
----

Last Modified: 2006-07-09  03:30:37 UTC
webmaster@mirality.co.nz
Website copyright © 2002-08 by Mirality Systems.
Mirality Systems, MSI, Ultra Enterprises, UE, and their respective logos are trademarks of Gavin Lambert.

Click for Auckland, New Zealand Forecast