The full scoop on InstallScript Structures

Structures in InstallScript are, like in all programming languages, used to create composite data types. In addition to this, InstallScript structures maintain their internal memory representation similar to C structures and hence pointers to these can passed to WIN32 API functions that work on structures.

 

From InstallShield 6, structures are implemented as COM objects. Using the '&' operator on a structure still retrieves a pointer to a location in memory where the members of the structure are laid out in exactly the same fashion as C structures with single byte alignment.

 

InstallScript structures taking up the COM avatar opens up the possibility of passing them over COM methods to other objects. This way a COM object implemented in Visual Basic can access the members in an InstallScript structure just as they were some COM objects' properties.

 

On the other hand, there are some interesting ramifications (aka gotchas) introduced by COMizing structures. Read on to understand how structures work and also find out how to prevent some common structure pitfalls.

 

The Basics

The typedef keyword is used to define the layout of a structure. The following typedefs define the WIN32 API structure - WINDOWPLACEMENT :

 

typedef POINT
begin
    long    x;
    long    y;   
end;

 

typedef RECT
begin
    long    left;
    long    top;
    long    right;
    long    bottom;
end;

 

typedef WINDOWPLACEMENT
begin
    int     length;
    int     flags;
    int     showcmd;
    POINT   ptMinPosition;
    POINT   ptMaxPosition;
    RECT    rcNormalPosition;
end;

 

WINDOWPLACEMENT is interesting for a couple of reasons. First, it has members like ptMinPostion & rcNormalPosition that are structures. Second, it is a typical WIN32 structure in the sense that before passing it to any API function you need to set the length member to the size of the structure itself. This is where the Sizeof function comes in handy. When you call SizeOf with a structure variable it returns the number of bytes required to accommodate its members, similar to sizeof operator in C/C++.

 

With the above type definitions you could write a function IsWindowMaximized as follows:

 

prototype BOOL USER32.GetWindowPlacement(HWND, WINDOWPLACEMENT POINTER);
prototype BOOL IsWindowMaximized(HWND);

 

function  BOOL IsWindowMaximized(hWnd)
WINDOWPLACEMENT wp;
begin
    wp.length = SizeOf(wp); //to please the WIN32 gods

 

    GetWindowPlacement(hWnd, &wp);

 

    return (SW_SHOWMAXIMIZED == wp.showcmd);
end;

 

Structures as COM objects

When a structure is used as a COM object all its members appear as properties on the COM object. Here's an example of how you can use this feature:

 

InstallShield Objects are also COM objects. Hence invoking methods or accessing properties on InstallShield Objects happens through COM. Here's the script for an InstallShield object called God:

 

property(get) STRING LatestNews();
STRING m_strLatestNews;

 

function STRING get_LatestNews()
begin
    return m_strLatestNews;
end;

 

method void Marry(object, object);

 

function void Marry(she, he)
begin  
    if(he.NetWorth < 1024 * 1024) then
        m_strLatestNews = he + " did not marry " + she;
        Err.Raise(1000, "", "He's too poor");
    endif;

 

    she.NetWorth = she.NetWorth + he.NetWorth;
    he.NetWorth = 0;                                
   
    m_strLatestNews = he + " married " + she;
end;

 

In a setup that includes an instance named "My God" of the above object you include the following script:

 

typedef PERSON
begin
    string    Name(50);
    number    Age;
    number    NetWorth;
end;

 

function OnBegin()
PERSON him, her;
OBJECT God;
begin
    him.Name = "Joe";
    him.Age = 39;
    him.NetWorth = 9800;

 

    her.Name = "Jane";
    her.Age = 37;
    her.NetWorth = 1048576;

 

    try
        set God = GetObject("My God");
        God.Marry(her, him);
    catch
        MessageBox("Marriage failed because: " + Err.Description, SEVERE);
    endcatch;

 

    MessageBox(God.LatestNews, INFORMATION);
   
    abort;
end;

 

Its pretty obvious what's going to happen here!

 

From the COM perspective the first member of a structure is special because it is regarded as what is known in COM parlance as the default value. This is the value of the object when it is used as such without referring to any of its methods/properties. Thus in the function Marry the assignments to m_strLatestNews gets the 'Name' of the object to build the news item. The InstallScript debugger displays only the default value of an object and hence when you watch a structure variable you only the see the value of the first member.

 

Structures in function parameters and assignments.

Structures can be passed to InstallScript functions & DLL functions only as POINTERs. Given the above definition of the type PERSON, the following line will result in a compiler error

 

prototype string Describe(PERSON); //error C8018: 'PERSON' :

                                   //typedef object illegal in this context

 

while

 

prototype string Describe(PERSON POINTER);

 

will compile just fine. On a similar note you cannot assign structures as a whole. The following script will result in 1 compiler error:

 

function foo()
PERSON p1, p2;
PERSON POINTER pPerson;
begin
    p1 = p2; //error C8081: 'p1' : invalid result for assignment

 

    pPerson = &p;
    pPerson->Age = 101;
end;

 

Note the use of the -> operator to access members through structure pointers.

 

BUT, the COM side of structures introduces some positive twists in the above situation.

 

The COM twist on structures

You can prototype a function as taking an object and pass a structure in its place. Run the following script:

 

prototype void Knight(object);

 

function void Knight(him)
begin
    him.Name = "Sir " + him.Name;
end;

 

function OnBegin()
PERSON p;
begin
    p.Name = "Richard Attenborough";
   
    Knight(p);
   
    MessageBox(p.Name, INFORMATION); //will display Sir Richard Attenborough
end;

 

The function Knight was declared as taking an object as opposed to an object BYREF but it still managed to change a member of the structure. This is because structure variables are really object variables and such variables have a subtle difference from other variables like number or string.

 

Generally variables in script are strongly bound references to their internal representation. By strongly bound, I mean that they refer to the same 'thing' for their whole lifetime. Structure/object variables on the other hand only start off as references bound to particular objects and can anytime be bound to a different one. Run the following script to see what I mean:

 


/* 1*/ function OnBegin()
/* 2*/ PERSON x, y;
/* 3*/ begin
/* 4*/        x.Name = "Steve";
/* 5*/        y.Name = "Larry";
/* 6*/
/* 7*/        set y = x;
/* 8*/
/* 9*/        x.Name = "Bill";
/*10*/
/*11*/        MessageBox(y.Name, INFORMATION);
/*12*/ end;

 

The MessageBox displays "Bill" instead of "Steve"!

 

This is what happens as the script execution proceeds. Line 2 causes 2 com objects that represent PERSON to be created and x & y are bound to these objects. Line 7 causes the object referenced by p2("Larry") to be released/destroyed and x & y now point to the same "Steve" PERSON. Line 12 causes x & y to go out of scope and hence release their references to the "Bill" object causing it to be destroyed.

 

Arrays of Structures

With all these possibilities with structures, it would only be reasonable to expect support for arrays of structures. This can be done but with a bit of work.

 

The most obvious way to declare an array of PERSONs would be

 

PERSON people(100);

 

This would compile fine BUT when you try to access the member of any of the array element an exception will be thrown in script. This is because the compiler treats the above declaration as

 

object people(100);

 

The scripting engine would thus create an array of 100 object references to NOTHING! It is obviously a bug in the compiler to pass through such declarations. Expect this to be fixed in a future maintenance release.

 

The solution to this problem would be to create the structure objects manually in script. I can hear you going "But there is no dynamic allocation method in script (like new in C++)". Yes, but here's something close:

 

prototype object newPerson();

 

function  object newPerson()
PERSON p;
begin
    return p;
end;

 

When this function is called a PERSON structure object is created and bound to the variable p. The use of p as the return value causes an additional reference to this structure object and hence p going out of scope will not result in the destruction of the structure object.

 

Armed with this function you can build your own array of structures as shown below:

 

function OnBegin()
object people(100);
int    i;
PERSON p;
begin
    for i = 0 to 99
        people(i) = newPerson();
    endfor;

 

    //now you can party on with people, for eg:

 

    people(10).Name = "Ten";

 

    set p = people(23);
    p.NetWorth = 2400000; //people(23).NetWorth will now be 2400000 too!
end;

 

The caveat with this approach is that you need to define a function each for every type of structure you want to put in an array. A future version of the compiler/scripting engine will make up for this deficiency.

 

Well, that's the full scoop on structures in InstallScript. Hope this article enables you to exploit them to the fullest in your installation scripts.

 

Rajesh Ramachandran.

07/14/200