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 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;
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 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.
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.
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