Day 19

External Server Objects and Functions

by Ted Graham


CONTENTS

The ActiveX and Java controls described in yesterday's lesson allow you to deliver external objects to the client. In addition, you can utilize external objects on the server. These extend the capabilities of the server while never having to be delivered to a client machine. The server also utilizes external functions to extend the built-in capabilities of the server.

Today you will learn about the following topics:

OLE Automation Objects

By now, you have created objects using IntraBuilder's many built-in classes. You have also created objects from custom classes defined in JavaScript. Today, you will create objects from classes that are completely foreign to IntraBuilder.

Objects are shared among Windows applications using a technology known as OLE automation.

OLE automation is part of the OLE 2.0 standard. This standard defines the sharing of objects between separate applications. In particular, OLE automation allows a client application to execute commands inside a server application. IntraBuilder provides an OleAutoClient class for working with OLE automation servers.

IntraBuilder acts as an OLE automation client (or container). Other applications such as Word, Excel, and OLEnterprise act as OLE automation servers. To establish a link between IntraBuilder and one of these servers, you must know the program identifier (ProgID) of the server.

A program identifier is a unique name for an OLE server application. Client applications use the program identifier to search the system registry. The registry contains the information necessary to access the server application.

As an example, Microsoft Excel surfaces several different OLE automation servers. Their program identifiers include Excel.Sheet and Excel.Chart. The identifiers for the OLE automation server should be documented by the server vendor. You can also use a tool such as OLEnterprise to locate servers on your system.

Creating an OLE Automation Client Object

All OLE automation servers are accessed using the OleAutoClient class in IntraBuilder. This is a non-visual class like the built-in Date and Array classes. This means that you instantiate these objects in your script, but you do not work with them using the visual tools in the Form Designer.

The OleAutoClient class takes a single parameter. This parameter is the program identifier of the desired server. To create an IntraBuilder object representing an Excel spreadsheet, use the following command:


var excel = new OleAutoClient("Excel.Sheet");

This command creates an object named excel. The properties and methods of excel are entirely dependent upon the server application. The dynamic nature of the IntraBuilder object model makes it possible to create objects with properties and methods that IntraBuilder never anticipated. This capability allows almost unlimited extensions to the IntraBuilder product.

After creating the client object in IntraBuilder, you interact with it just like any other IntraBuilder object. For example, you use the Inspector to look at its properties and methods. If you have Excel installed on your system, try running this simple program:


excel = new OleAutoClient("Excel.Sheet");

_sys.inspect(excel);

This shows the excel object in the Inspector. Feel free to browse the properties and methods to become familiar with the capabilities of the object. For more information about the objects that Excel (or any other OLE automation server) surfaces, including their properties and methods, check the documentation that comes with the server application. For Microsoft Excel 7.0, you look in the online help for a definition of the properties and methods. From the help file's Contents page, select the Microsoft Excel Visual Basic Reference book.

Using Automation Objects

Surely you want to do more than inspect these powerful objects. After creating the object, you manipulate the properties and execute the methods to control the behavior of the server application. The Excel object creates spreadsheets, charts values, and performs calculations. One of Excel's many strong points is the extensive array of financial calculations available. Rather than duplicating these functions in IntraBuilder, you can use OLE automation to have Excel perform the calculations.

Calling an Excel Method

Consider a Web page that calculates the amount of a monthly loan payment, based on the loan amount, period, and interest rate entered on the form. Rather than calculating the monthly payment in IntraBuilder, you could use an OleAutoClient object to access Excel. Excel has a built-in function to calculate the payment. Although this example might be analogous to using a pile driver to push in a thumbtack, it does illustrate the basics of OLE automation.

One of the many methods that the Excel.Sheet server surfaces is called evaluate. This method evaluates any Excel expression and returns the result. The following example calculates the payment for a 36-month loan of $20,000 at 10 percent annual interest:


var xl = new OleAutoClient("excel.sheet");

var pmt = xl.evaluate("pmt(10/1200,36,20000)");

Storing Values in Cells

To make this code a little more dynamic, store the three initial values to cells on the spreadsheet and store the formula to a fourth cell. Then update the cell values to experiment with different payment schedules. The formula cell will always contain the current payment amount. The following example demonstrates the use of cells:


var xl=new OleAutoClient("excel.sheet");

xl.range("A1").value = 10/1200;

xl.range("b1").value = 36;

xl.range("C1").value = 20000;

xl.range("D1").value = "=PMT(A1,b1,C1)";

var pmt = xl.range("D1").value;

Then to see what the monthly payment would be at 9 percent interest, you simply change the value of A1:


xl.range("A1").value = 9/1200;

pmt = xl.range("D1").value;

Creating a Loan Calculation Form

This technique is used in the loan calculation form shown in Figure 19.1. The Calculate button's onServerClick() event code contains the call to the OLE automation server to calculate the monthly loan payment.

Figure 19.1 : A loan calculation form in Navigator.

When this form is submitted to the IntraBuilder server, it actually uses Excel to perform the calculation and then report the result back to the browser. The user does not need to have Excel on the client machine; all of the communication between the OLE automation client (IntraBuilder) and server (Excel) takes place on the Web server machine. The source for this form is shown in Listing 19.1.


Listing 19.1. Payment.jfm using OLE automation with Excel.

  1: // {End Header} Do not remove this comment//

  2: // Generated on 02/14/97

  3: //

  4: var f = new paymentForm();

  5: f.open();

  6: class paymentForm extends Form {

  7:    with (this) {

  8:       onServerLoad = class::form_onServerLoad;

  9:       height = 14;

 10:       left = 5;

 11:       top = 0;

 12:       width = 63;

 13:       title = "Loan Calculator";

 14:    }

 15:    with (this.html1 = new HTML(this)){

 16:       height = 2;

 17:       left = 2;

 18:       width = 52;

 19:       alignHorizontal = 1;

 20:       text = "<h1>Loan Payment Calculator</h1>";

 21:       pageno = 0;

 22:    }

 23:    with (this.html2 = new HTML(this)){

 24:       height = 1;

 25:       left = 2;

 26:       top = 3;

 27:       width = 30;

 28:       alignHorizontal = 2;

 29:       text = "Amount of Loan ";

 30:       pageno = 0;

 31:    }

 32:    with (this.amount = new Text(this)){

 33:       left = 34;

 34:       top = 3;

 35:       width = 10;

 36:       value = "";

 37:       pageno = 0;

 38:    }

 39:    with (this.html3 = new HTML(this)){

 40:       height = 1;

 41:       left = 2;

 42:       top = 5;

 43:       width = 30;

 44:       alignHorizontal = 2;

 45:       text = "Repayment period (in months) ";

 46:       pageno = 0;

 47:    }

 48:    with (this.period = new Text(this)){

 49:       left = 34;

 50:       top = 5;

 51:       width = 5.5;

 52:       value = "";

 53:       pageno = 0;

 54:    }

 55:    with (this.html5 = new HTML(this)){

 56:       height = 1;

 57:       left = 2;

 58:       top = 7;

 59:       width = 30;

 60:       alignHorizontal = 2;

 61:       text = "Annual Percentage Rate ";

 62:       pageno = 0;

 63:    }

 64:    with (this.rate = new Text(this)){

 65:       left = 34;

 66:       top = 7;

 67:       width = 5.5;

 68:       value = "";

 69:       pageno = 0;

 70:    }

 71:    with (this.html6 = new HTML(this)){

 72:       height = 1.2;

 73:       left = 40;

 74:       top = 7;

 75:       width = 4;

 76:       alignVertical = 1;

 77:       text = "%";

 78:       pageno = 0;

 79:    }

 80:    with (this.calcButton = new Button(this)){

 81:       onServerClick = class::calcButton_onServerClick;

 82:       left = 2;

 83:       top = 9;

 84:       width = 12;

 85:       text = "Calculate";

 86:    }

 87:    with (this.backButton = new Button(this)){

 88:       onServerClick = {;form.pageno = 1};

 89:       left = 2;

 90:       top = 11;

 91:       width = 14;

 92:       text = "Back";

 93:       pageno = 2;

 94:    }

 95:    with (this.html7 = new HTML(this)){

 96:       height = 1;

 97:       left = 2;

 98:       top = 9;

 99:       width = 30;

100:       alignHorizontal = 2;

101:       text = "Monthly Payment ";

102:       pageno = 2;

103:    }

104:    with (this.paymentHTML = new HTML(this)){

105:       height = 1;

106:       left = 34;

107:       top = 9;

108:       width = 12;

109:       pageno = 2;

110:    }

111:    function form_onServerLoad()

112:    {

113:       this.xl = new OleAutoClient("excel.sheet");

114:       this.xl.range("A1").value = 0;

115:       this.xl.range("b1").value = 0;

116:       this.xl.range("C1").value = 0;

117:       this.xl.range("D1").value = "=PMT(A1,b1,C1)";

118:    }

119:    function calcButton_onServerClick()

120:    {

121:       var f = this.form, xl = f.xl;

122:       xl.range("A1").value = parseInt(f.rate.value)/1200;

123:       xl.range("b1").value = parseInt(f.period.value);

124:       xl.range("C1").value = parseInt(f.amount.value);

125:       f.paymentHTML.text = "" + (-xl.range("D1").value);

126:       f.pageno = 2;

127:    }

128: }


Lines 1 through 110 define the controls on the payment form. The form_onServerLoad() method beginning on line 111 creates the OLE automation object xl as a property of the form. It then initializes the spreadsheet cell values.

The calcButton_onServerClick() method, beginning on line 119, passes the values from the form to the spreadsheet (lines 122 to 124). Line 125 takes the calculated value from spreadsheet cell D1 and displays it on the form. Finally, line 126 changes the form to page 2, displaying the payment controls and the Back button.

The backButton control defined on lines 87 to 94 simply changes the page number back to 1 (line 88).

Remote OLE Automation Server Objects

The OLE 2.0 specification allows client applications to access server applications on the same machine. But what if you want to access an OLE automation server on a different machine? For example, you might have several different machines that act as Web servers, each running IntraBuilder. Or you might have several different machines hosting IntraBuilder agents that all service a single Web server. (Remote IntraBuilder agents are possible with the client/server version of the product.) You might want to set up a single application server running your OLE automation server that would service all of the IntraBuilder server machines.

Although this type of distributed OLE is not currently possible with Windows and IntraBuilder itself, there is a product that bridges the gap and makes it possible. OLEnterprise from Open Environment Corporation (a subsidiary of Borland) gives IntraBuilder access to OLE automation servers on remote machines.

Remote Procedure Call (RPC) Servers

Not only does OLEnterprise give you access to remote OLE automation servers, but it also gives you access to any remote server application that conforms to the DCE RPC specification. This gives IntraBuilder access to application servers and database servers on UNIX or even mainframe systems.

OLEnterprise registers the RPC server and then acts as a middleman between the RPC server and IntraBuilder. OLEnterprise presents the RPC server to IntraBuilder as if it were an OLE automation server.

NOTE
To find out more about OLEnterprise, check out the Open Environment home page at www.oec.com.

External Functions

You can extend the capability of the IntraBuilder server not only through the use of external objects, but also through the use of external functions. There are many things that IntraBuilder is simply not designed to do, but other vendors do these things very well. These vendors often make libraries of functions available in the form of an add-on DLL. These DLLs are used by any development tool, such as C++, Delphi, or Visual Basic. Function libraries are available for a wide variety of purposes, including data collection, communications, fax and e-mail, and credit card validation.

IntraBuilder allows you to prototype external functions and then call them just like you call internal functions such as alert() or parseInt(). The prototyping tells IntraBuilder the name and location of the function, as well as the data type of the return value and any parameters. This is necessary so that IntraBuilder can call the external function and exchange data in a compatible format.

The extern Command

You use the extern command to prototype the function in IntraBuilder. The extern command uses several data type keywords to indicate the type of the return value and the parameters. These type keywords are the generally recognized base types in C++ and most other programming languages, such as int for 32-bit integer values and short for 16-bit integer values. The types are modified by the keyword unsigned if the integer value is used for positive values only. The keywords are also modified with an asterisk if the return value or parameter is a pointer to the specified type.

As an example, consider the MessageBeep() function. This function is contained in the USER32.DLL file that ships with Windows. The functions in the Windows DLLs are collectively referred to as the Windows API. According to the Windows API documentation, MessageBeep() plays a sound. The function takes one parameter of type unsigned int and returns a boolean type value indicating whether the function is successful.

Using this information, you prototype the function using this extern command:


extern boolean MessageBeep(unsigned int) "user32.dll";

After prototyping the function, call it just like a built-in function:


var result = MessageBeep(64);

NOTE
There is one difference between calling an IntraBuilder function and calling an external function. IntraBuilder functions such as parseInt() can take optional parameters. External functions, however, require an exact number of parameters. Because MessageBeep() is prototyped with one parameter, you must pass one and only one parameter each time you call the function.

Calling Conventions

The extern command shown earlier is pretty basic. For instance, it does not specify the calling convention. The calling convention is a predefined standard by which programs exchange information. There are three calling conventions supported by the extern command: cdecl, pascal, and stdcall. Before prototyping any function, you need to know the calling convention that it uses. Most 32-bit programs use the stdcall convention, which is the IntraBuilder default.

The following extern for MessageBeep() shows the inclusion of the calling convention:


extern stdcall boolean MessageBeep(unsigned int) "user32.dll";

Keep in mind that stdcall is the default calling convention, so you don't actually need to specify it in this case. Almost every one of the functions in the Windows API uses the stdcall convention. But you might run into other libraries that use a different convention.

Renaming External Functions

You also use the extern command to assign a different name to the external function. You use this capability to access two different functions that happen to have the same name, or to simplify the name in IntraBuilder.

You might also want to rename functions when the documented name does not match the actual name of the function. For example, the Windows API documents a function named MessageBox(), but the actual function name is MessageBoxA(). You use the extern command to rename the function in IntraBuilder to match the documented name:


extern int MessageBox(void*,          // owner window

                      char*,          // message text

                      char*,          // title text

                      unsigned int    // style

                     ) "user32.dll"

                     from "MessageBoxA";

This assigns the documented name MessageBox() to the function in IntraBuilder. When you call the MessageBox() function, IntraBuilder actually calls the MessageBoxA() function in USER32.DLL.

TIP
Many of the Windows API functions are documented under aliases. Trying to prototype the documented name produces an error. This is normally the case if the function takes any strings or structures as parameters. If you do encounter an error prototyping the documented name, try adding a capital letter "A" to the end of the function name.

After you have prototyped the function, you call it with the new name:


var result = MessageBox(0, "Save File?", "Extern Sample", 35);

This function call opens a dialog box, as shown in Figure 19.2. You see the title and text that were passed as parameters. The style parameter, 35, tells the function to add the question mark and buttons for Yes, No, and Cancel.

Figure 19.2 : The dialog box generated by MessageBox().

Data Type Keywords

The previous examples showed the use of several data type keywords, such as int, char*, boolean, and unsigned int. The complete list of data type keywords is shown in Table 19.1.

Table 19.1. Data type keywords for the extern command.

Keyword
IntraBuilder Data Type
External Data Type
charNumeric 8-bit character
unsigned char   
shortNumeric 16-bit integer
unsigned short   
intNumeric 32-bit integer
unsigned int   
long   
unsigned long   
floatNumeric 32-bit floating-point number
doubleNumeric 64-bit floating-point number
long doubleNumeric 80-bit floating-point number
booleanBoolean 8-bit Boolean value
voidNoneN/A
char*String Null-terminated string
void*String Structure of unknown type and length
...N/AVariable number of parameters

In addition to the types shown in Table 19.1, there are pointers to each of these types.

A pointer is a data type in languages such as C, C++, and Pascal. Rather than containing an actual value, a pointer contains the memory address of a value. All pointers are 32 bits in size because that is the size of a memory address.

Prototyping a Pointer

To indicate that a return type or parameter is a pointer to a particular data type, put the asterisk after the data type. Table 19.1 shows two pointer types: char* and void*. In addition to these, there are pointers to all the other types as well.

As an example, if an external function returns a pointer to a 16-bit integer, prototype it as short*. Likewise, if a parameter is a pointer to an unsigned long value, prototype it as unsigned long*.

Parameter-Only Keywords

The last two items shown in Table 19.1, void* and ..., are used for parameter types but not for return types. While void indicates that there is no return type or parameter, void* is used to indicate a pointer to an unknown data type. The typical use of void* is to indicate that a parameter is a structure. Structures are explained later in this chapter.

These IntraBuilder data type keywords correspond to the base data types in the C programming language. If you are familiar with C or C++, you will be able to prototype functions in IntraBuilder by simply determining the base type for any parameters and the return value. If this is the case, you might want to skim over the next few sections.

Creating a Function Prototype

The last few sections showed you how to use the syntax of the extern command and the data type keywords used by the command. But when prototyping external functions, this is only part of the battle. Before you prototype a function, you must check the documentation from the function's vendor. In some place, the vendor will show the function prototype. However, this documentation is usually written for C programmers and might appear a little cryptic to the rest of us.

The next few sections give you some tips on turning this type of function documentation into an IntraBuilder prototype.

Using the Documentation

Most function libraries come with some sort of documentation. If you are using the Windows API itself, you'll need to get documentation. The official source of information is the Windows SDK, available from Microsoft. The Microsoft Developer Network CD also contains a wealth of information. Development products such as Borland C++ also contain documentation on the Windows API. And numerous books are available on this subject.

Figure 19.3 shows a typical function definition that comes from the Borland C++ help file.

Figure 19.3 : Borland documentation for the MessageBox() function.

The documentation gives you the function name and identifies the return type as int. The QuickInfo link shown in Figure 19.3 indicates that the function is contained in the USER32.DLL file. So the documentation provides the function name, the number of parameters, and possibly the base type of the return value and parameters.

This documentation indicates that the base type of the return value is int. However, the parameter types are shown as HWND, LPCTSTR, and UINT, which do not match any of the IntraBuilder data type keywords. These custom data types are simply different names for one of the base data types. You have to turn to other sources to determine the base data types.

Using Header Files

When you get an external function library, it usually contains a header file along with the DLL. Among other things, the header file contains the function prototype (so, if it's not in the documentation, it will be here) and definitions of the custom data types.

The header file is intended to be used by C programmers. The programmers simply include the header in their program, and then they have access to the functions and the custom data types.

Custom Data Types and typedef

As an IntraBuilder programmer, you also use the header file to determine the base data types of the custom types. Open the header file in an editor that allows you to do text searches (such as Window's Notepad), and search for the data type. For instance, the Windows API header file WINDEF.H defines the MessageBox() UINT data type:


typedef unsigned int        UINT;

In C, typedef defines a new data type. This simply indicates that the type (or types) on the right are synonymous with the type on the left. So UINT is just another name for unsigned int.

Multiple Layers of typedef

Sometimes you have to look through several typedef commands to find the base data type. For example, the Windows API header file WINNT.H defines the LPCTSTR data type:


typedef LPCSTR LPCTSTR;

This indicates that LPCTSTR is another name for the LPCSTR type. Elsewhere in the file, LPCSTR is defined:


typedef CONST CHAR *LPCSTR, *PCSTR;

This defines both *LPCSTR and *PCSTR as CONST CHAR types. The CONST is a C keyword indicating that a parameter cannot be modified by the function. Ignore this keyword when trying to determine the base data type. The asterisk before each of the type names indicates that it is a pointer type. Therefore, LPCSTR is a pointer to a CHAR. The CHAR type is also defined in this header file:


typedef char CHAR;

The typedef defines CHAR to be another name for char, which is one of the IntraBuilder data types. From these three lines you can determine that the LPCTSTR data type is just a fancy name for a pointer to a char. You can express a pointer to char as char* in the extern command.

Other Type Definition Techniques

Other data type definition techniques are used in the header files. For example, the HWND is defined by this line in WINDEF.H:


DECLARE_HANDLE        (HWND);

To see what the DECLARE_HANDLE macro does, you have to look in WINNT.H:


typedef void *PVOID;

typedef PVOID HANDLE;

#define DECLARE_HANDLE(name) typedef HANDLE name

The type definition of HWND relies on the preprocessor macro DECLARE_HANDLE as well as the typedef commands. The macro evaluates to a typedef for a HANDLE type. HWND is a HANDLE, which is itself a PVOID type. Because PVOID is a pointer to a void type, HWND is a pointer to a void. This is prototyped as void* in the extern.

This type of detective work in the header file reveals the base data type for any of the custom data types in function definitions. Of course, in the worst case, you can contact the function library's vendor to request information about the base data types in question.

Structures and Enumerated Constants

Two custom data types that you might encounter are not based on one of the IntraBuilder base data types. These data types are structures and enumerated constants.

Later in this chapter, you will see the GetDateFormat() function. One of the parameters to this function is of type SYSTEMTIME. The definition of this custom type is located in the WINBASE.H header file:


typedef struct _SYSTEMTIME {

    WORD wYear;

    WORD wMonth;

    WORD wDayOfWeek;

    WORD wDay;

    WORD wHour;

    WORD wMinute;

    WORD wSecond;

    WORD wMilliseconds;

} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;

The heading typedef struct indicates that this is a structure type.

A structure is a custom data type that combines multiple data types. The individual members of the structure are just like the individual properties of an IntraBuilder object.

Structures are covered in depth near the end of this chapter.

TIP
All structure parameters are prototyped as void* in the extern command.

The other custom data type that you might encounter is enum. For example, the WINNT.H file defines a custom data type named TOKEN_TYPE:


typedef enum _TOKEN_TYPE {

    TokenPrimary = 1,

    TokenImpersonation

    } TOKEN_TYPE;

typedef TOKEN_TYPE *PTOKEN_TYPE;

The heading typedef enum indicates that this is an enumerated constant type.

An enumerated constant is a custom data type that can take any of a set of predefined values. The values are identified with a constant name.

The enumerated constants have integer equivalents. By default, the first constant has the value of 0, and each subsequent constant has a value one larger than the previous. In the TOKEN_TYPE enumerated constant, the first value is explicitly defined to have a value of 1 instead. The subsequent constant is one greater than this, so it has a value of 2.

You have two options when handling these enumerated constants. The first is to pass the equivalent integer value. For instance, you would use a value of 1 in any place that the documentation for TOKEN_TYPE indicates you should use the value TokenPrimary. The other option is to use the preprocessor to define constants for these values:


#define TokenPrimary        1

#define TokenImpersonation  2

Then you use the constant names just as the documentation would indicate.

TIP
All enumerated constant parameters are prototyped as unsigned int in the extern command.

Using WINDEF.H for Windows Base Types

The preceding examples apply to determining the base type for any custom data type. Because the most common source of external functions is the Windows API, IntraBuilder includes a JavaScript header file that defines the most common data types for you. This file, WINDEF.H, is located in the IntraBuilder\Include folder.

To use this header file, simply #include it in your script before prototyping a Windows API function. Here is the MessageBox() prototype using this header file:


#include "windef.h"

extern int MessageBox(

    HWND,      // handle of owner window

    LPCTSTR,   // address of text in message box

    LPCTSTR,   // address of title of message box

    UINT       // style of message box

   ) "user32.dll"

   from "MessageBoxA";

This header does not include all of the Windows data types-just the most common types. If you encounter a Windows type that is not defined in the header file, use the techniques shown earlier to determine the base data type.

Parameter Values

The function definition in the documentation also tells you what values are acceptable for each of the parameters. Some parameters, such as the text and title in the MessageBox(), are left up to your choosing (as long as the value is the correct type). Others require specific values. Figure 19.4 shows more of the MessageBox() documentation. This section defines each of the parameters.

Figure 19.4 : Parameter definitions for MessageBox().

null Values as Parameters

One type of value that you might encounter is a NULL value. For example, Figure 19.4 shows that hWnd can have a value of NULL.

Although IntraBuilder supports a null value of its own, you should never pass an IntraBuilder null to an external function. Instead, you pass a zero to an external function that is expecting a C type NULL value. Each of the MessageBox() examples has used this technique to pass a NULL value for the hWnd parameter.

Symbolic Constant Values

The function documentation might also indicate that certain parameters take symbolic constant values. For example, the documentation in Figure 19.4 began to list the possible symbolic constants for the uType parameter. Figure 19.5 shows more constants that can be used for the uType parameter.

Figure 19.5 : Parameter constants for the uType parameter.

The examples so far have used the literal value 35, rather than one of the symbolic constants, for the uType parameter. This was only a temporary trick until the constants were introduced. The value 35 is a combination of two constants, MB_ICONQUESTION (seen in Figure 19.5) and MB_YESNOCANCEL.

Determining the Value of Symbolic Constants

The symbolic constants are defined in the same header files that contain the data type definitions. When you encounter a symbolic constant in the documentation, search the header file to find the value.

After you find the value for the constant, you have two choices. First, you could simply use the value in place of the constant, as you did with the 35 earlier. Or you could cut and paste the constant declarations from the C header file to your JavaScript program. Then you use the constants as indicated by the documentation.

Here are the two lines from WINUSER.H that define the MessageBox() styles that the examples have been using:


#define MB_YESNOCANCEL              0x00000003L

#define MB_ICONQUESTION             0x00000020L

This syntax is almost exactly like the JavaScript syntax for defining preprocessor constants. The L at the end of the hexadecimal values indicates that the numeric constant should be stored as a long type. This is not used by JavaScript, so remove the L if you copy this type of line to your own script.

Using Correct Parameter Values in MESSAGE.JS

Listing 19.2 combines what you have learned in the last few sections. It contains the constant definitions that are copied from the C header file. It uses WINDEF.H to simplify the prototyping of the MessageBox() function. It calls the MessageBox() function and evaluates the result.


Listing 19.2. Using MessageBox() in MESSAGE.JS.

 1: #include "windef.h"

 2: /*

 3:  * MessageBox() Flags

 4:  */

 5: #define MB_OK                0x00000000

 6: #define MB_OKCANCEL          0x00000001

 7: #define MB_ABORTRETRYIGNORE  0x00000002

 8: #define MB_YESNOCANCEL       0x00000003

 9: #define MB_YESNO             0x00000004

10: #define MB_RETRYCANCEL       0x00000005

11: #define MB_ICONHAND          0x00000010

12: #define MB_ICONQUESTION      0x00000020

13: #define MB_ICONEXCLAMATION   0x00000030

14: #define MB_ICONASTERISK      0x00000040

15: #define MB_ICONWARNING       MB_ICONEXCLAMATION

16: #define MB_ICONERROR         MB_ICONHAND

17: #define MB_ICONINFORMATION   MB_ICONASTERISK

18: #define MB_ICONSTOP          MB_ICONHAND

19: /*

20:  * Dialog Box Command IDs

21:  */

22: #define IDOK        1

23: #define IDCANCEL    2

24: #define IDABORT     3

25: #define IDRETRY     4

26: #define IDIGNORE    5

27: #define IDYES       6

28: #define IDNO        7

29: #define IDCLOSE     8

30: #define IDHELP      9

31: extern int MessageBox(

32:     HWND,      // handle of owner window

33:     LPCTSTR,   // address of text in message box

34:     LPCTSTR,   // address of title of message box

35:     UINT       // style of message box

36:    ) "user32.dll"

37:    from "MessageBoxA";

38: var result = MessageBox(0, "Save File?", "Extern Sample",

39:              MB_ICONQUESTION | MB_YESNOCANCEL);

40: switch (result) {

41: case IDYES:

42:    alert("File Saved");

43:    break;

44: case IDNO:

45:    alert("File Not Saved");

46:    break;

47: case IDCANCEL:

48:    alert("Exit aborted");

49:    break;

50: }


Line 1 includes the JavaScript header file, WINDEF.H. This contains the data type definitions for the extern command beginning on line 31.

Lines 2 to 18 contain constant definitions that are copied from the C header file WINUSER.H. When you copy these sections of constants to the JavaScript file, it is easier to change the look of the MessageBox() at a later time. Lines 19 to 30 are also copied from WINUSER.H. These define the constants that are returned from the MessageBox() function. These constants are used in the switch block beginning on line 40.

Lines 31 to 37 prototype the external function that is used on lines 38 and 39. Notice the use of the style constants in the function call. The pipe symbol is used to combine multiple constants. Lines 40 to 50 display a different alert for each of the three possible values returned by the MessageBox()function.

Structures

One of the C data types that you will encounter deserves some extra attention. Structures are custom data types that combine multiple base data types. There is no equivalent data type in IntraBuilder, which makes working with structures challenging.

Structures can be thought of as primitive objects. A structure is a single entity that includes multiple members, just like an object includes multiple properties. Each member of an object contains a distinct piece of data.

One of the many structures used by the Windows API is called SYSTEMTIME. The definition of this structure is shown in Figure 19.6.

Figure 19.6 : The definition of the SYSTEMTIME structure.

The SYSTEMTIME structure contains eight members. Each member is of type WORD, which is a custom data type equivalent to an unsigned short. Table 19.1 indicates that an unsigned short is a 16-bit (2-byte) integer value. The entire size of the structure is the sum of the member sizes. Therefore, this structure is a total of 16 bytes in size.

Passing Structures in IntraBuilder

Two Windows API functions that use the SYSTEMTIME structure are GetSystemTime() and GetDateFormat(). GetSystemTime() takes a single parameter, which is a pointer to a SYSTEMTIME structure. It stores the current date and time to the various members of the structure. The GetDateFormat() function takes a SYSTEMTIME structure as one of its parameters. It creates a string representation of the SYSTEMTIME value based on the other parameters.

You prototype these two functions just like other functions. To indicate that a parameter is a structure, use the void* data type keyword in the extern command.

Before calling the external function, you must initialize an IntraBuilder string to hold the structure value. This string must be at least as large as the structure itself.

Listing 19.3 shows a script file that prototypes the two functions just mentioned and passes the structure created by one to the other.


Listing 19.3. Using the SYSTEMTIME structure in DATE.JS.

 1: #include "windef.h"

 2: // C prototype:

 3: // VOID GetSystemTime(

 4: //  LPSYSTEMTIME  lpSystemTime // address of system time structure

 5: // );

 6: extern VOID GetSystemTime(

 7:   LPSTRUCTURE      // address of system time structure

 8: ) "kernel32.dll";

 9: // C prototype:

10: // int GetDateFormat(

11: //  LCID  Locale,       // locale for which date is to be formatted

12: //  DWORD  dwFlags,     // flags specifying function options

13: //  CONST SYSTEMTIME * lpDate,  // date to be formatted

14: //  LPCTSTR  lpFormat,  // date format string

15: //  LPTSTR  lpDateStr,  // buffer for storing formatted string

16: //  int  cchDate        // size of buffer

17: // );

18: extern int GetDateFormat(

19:  LCID,      // locale for which date is to be formatted

20:  DWORD,     // flags specifying function options

21:  STRUCTURE, // date to be formatted

22:  LPCTSTR,   // date format string

23:  LPTSTR,    // buffer for storing formatted string

24:  int        // size of buffer

25: ) "kernel32.dll"

26: from "GetDateFormatA";

27: var Locale    = 0;  // null pointer

28: var dwFlags   = 2;  // long date format

29: var lpDate    = new StringEx().replicate(" ", 16);

30: var lpFormat  = 0;  // null pointer

31: var lpDateStr = new StringEx().replicate(" ", 80);

32: var cchDate   = lpDateStr.length;

33: // get current date

34: GetSystemTime( lpDate );

35: // pass lpDate structure to GetDateFormat

36: var len = GetDateFormat( Locale, dwFlags, lpDate,

37:           lpFormat, lpDateStr, cchDate );

38: // display the formatted date

39: alert(lpDateStr.substring(0,len-1));


This script file, DATE.JS, has three basic sections. Lines 1 to 26 prototype the external functions. Lines 27 to 32 initialize the variables. And lines 33 to 39 perform the action of the script.

Line 1 includes the WINDEF.H header file. This file contains the type definitions for the Windows data types used in the extern commands.

The WINDEF.H that ships with IntraBuilder 1.0 and 1.01 contains incorrect type definitions for several pointer types, including pointers to structures. The CD that comes with this book contains an updated copy of the header file that corrects this problem. Be sure to use the updated copy when running the scripts in this chapter.

Lines 2 to 5 show the documented prototype for the GetSystemTime() function. Lines 6 to 8 contain the IntraBuilder prototype for this function. Notice that the LPSYSTEMTIME parameter on line 4 is prototyped as LPSTRUCTURE on line 7. The LPSTRUCTURE type is defined in WINDEF.H as a void*.

Lines 9 to 17 show the documented prototype for the GetDateFormat() function. Lines 18 to 26 contain the IntraBuilder prototype for this function. Notice that the CONST SYSTEMTIME * on line 13 is prototyped as STRUCTURE on line 21. WINDEF.H defines the types STRUCTURE, PSTRUCTURE, and LPSTRUCTURE, which are all interchangeable.

Lines 27 to 32 initialize variables for each of the parameters of the GetDateFormat() function. The variable names are taken from the suggested names in the C prototype on lines 11 to 16. This makes it easier to coordinate the script with the actual documentation.

Pay particular attention to line 29. This line initializes the lpDate variable. It is initialized as a string containing 16 spaces. This length matches the length of the SYSTEMTIME structure itself. This string is then passed to the GetSystemTime() function on line 34. After line 34 executes, lpDate contains the data associated with a SYSTEMTIME structure.

It is vital that you initialize variables that will hold structures. They must be initialized to a size at least as large as the structure itself. If you have trouble determining the size of the structure, initialize the variable to a large size. If you do not properly initialize the variable, an external function could write to an inappropriate place in memory. This could cause IntraBuilder or the external function to crash.

The lpDate structure is then passed to the GetDateFormat() function on lines 36 and 37. Notice that you never need to know the contents of the structure in this script. One function fills it, and you simply pass that structure to another function. This will be the case in many circumstances.

The GetDateFormat() function stores a string version of the date to the lpDateStr variable. This variable is then displayed by the alert on line 39. The return value from GetDateFormat() is the number of bytes written to lpDateStr. This return value is used on line 39 to trim lpDateStr to the correct length. The length is actually trimmed to one less than the return value, because the C function would have written and counted a terminating null character.

The final result of the function is an Alert dialog showing the current date in a text format. The result will look similar to that shown in Figure 19.7.

Figure 19.7 : An Alert dialog showing the result of GetDateFormat().

Interpreting Structures

The script in Listing 19.3 did not need to know the contents of the SYSTEMTIME structure. There are times, however, when you do need to determine the individual member values in the structure. Simple integer values like those in SYSTEMTIME can be determined using IntraBuilder methods, but more complex data such as floating-point numbers or pointers require the use of the structure parsing functions described in the next section.

The structure contains the internal representation of each member value. For example, the unsigned short, or WORD, type is represented by two bytes. Each byte contains a value from 0 to 255. The first byte represents multiples of 1, and the second byte represents multiples of 256. So the number 5 is represented by the bytes 5 and 0, in that order. The number 260 is represented by the bytes 4 and 1, because it contains one multiple of 256 with four multiples of 1 left over.

Knowing this, you convert an unsigned short value stored in a structure to the actual number that it represents. You use the asc() method of the StringEx object to determine the value stored in a particular byte.

The reverse is also possible. You use the str() method of the StringEx object to create a character string value representing a byte value.

Listing 19.4 contains a function that uses this technique to increment the year member of a SYSTEMTIME structure.


Listing 19.4. Manipulating a SYSTEMTIME member in ADDYEAR.JS.

 1: function addYear (stStruc)

 2: {

 3:    // Create var to simplify calling the asc()

 4:    // and str() methods of StringEx()

 5:    var str = new StringEx();

 6:    // Year is first WORD value in structure

 7:    var year = (str.asc(stStruc.substring(0,1)) +

 8:                str.asc(stStruc.substring(1,2)) * 256 );

 9:    // Increment the year

10:    year++;

11:    // Create new structure containing the new year

12:    // and the rest of the stStruc structure

13:    stStruc = str.chr(year % 256) +

14:              str.chr(parseInt(year/256) % 256) +

15:              stStruc.substring(2,16);

16:    return (true);

17: }


The addYear() function takes a SYSTEMTIME structure as the parameters (in line 1). This function uses the methods of StringEx to manipulate the structure string stStruc. Line 5 creates a StringEx object for this purpose.

Lines 7 and 8 use the first two bytes of the string to determine the year value. The first byte is taken at face value, and the second byte is multiplied by 256. The sum of these two bytes represents the year member.

This year value is incremented in line 10. Lines 13 to 15 create a new SYSTEMTIME structure using the incremented year and the balance of stStruc.

The technique shown in Listing 19.4 is useful for unsigned integer values of all sizes. An unsigned long value is stored using four bytes. The first byte contains multiples of 1 (256 to the power of 0), the second contains multiples of 256 (256 to the power of 1), the third contains multiples of 65,536 (256 to the power of 2), and the fourth contains multiples of 16,777,216 (256 to the power of 3).

The IntraBuilder Structure Parsing Library

For most of the non-integer data types, you cannot easily parse the structure string to determine the value. To parse these types of structure members, you can use the structure parsing library that ships with IntraBuilder. This collection of functions is contained in a file named STRUCMEM.DLL in the IntraBuilder\Samples folder.

The prototypes for these functions are contained in a script named STRUCMEM.JS, which is also in the IntraBuilder\Samples folder. To use these functions, you need to include the STRUCMEM.H header file. It defines the constant values that are used for various parameters. This file is located in the default IntraBuilder\Include folder.

The structure parsing library contains functions to retrieve values from the structure as
well as store values in a structure string. The functions prototyped in STRUCMEM.JS are described next.

GetStructNumber(<structure>, <offset>, <type>)

This function returns the value of type <type> located at position <offset> in the structure string <structure>. The <offset> is zero-based, so the first member is at offset 0. The offset of each member is determined by the size of the members before it. The <type> can be one of the type constants defined in STRUCMEM.H:

The types UCHAR, USHORT, UINT, and ULONG indicate unsigned values of the indicated type. The last four items in the list are Windows data types, which can be used in place of their actual base type for convenience.

In addition to these constants, there are also size constants. They match these names, except that TYPE is replaced by SIZEOF, as in SIZEOF_INT and SIZEOF_ULONG. These size constants are useful when calculating the <offset>:

SetStructNumber(<structure>, <offset>, <type>, <slen>, <value>)

This function writes the value <value> of type <type> at position <offset> in the structure string <structure>. The <slen> parameter indicates the length of <structure>. This is necessary to make sure that SetStructNumber() does not write past the memory allocated for this string. The types are the same as those used by the GetStructNumber() function. The function returns the number of bytes written to <structure>.

GetStructString(<structure>, <offset>, <length>, <target>, <tlen>)

This function copies the string at offset <offset> of length <length> in the structure string <structure> to <target>. The <tlen> parameter indicates the length of <target>. Structures rarely contain strings; they usually contain pointers to strings, which are handled by another function. However, this function is useful for retrieving structures that are members of other structures. The return value is the number of bytes written to <target>.

SetStructString(<structure>, <offset>, <slen>, <value>, <vlen>)

This function copies <vlen> bytes from the string <value> to the offset <offset> of string <structure>. The <slen> parameter indicates the size of <structure>. The return value indicates the number of bytes written to <structure>.

GetStructCharPointer(<structure>, <offset>, <length>, <target>, <tlen>)

This function writes <length> bytes to <target> from the string pointed to by the pointer at offset <offset> in the structure string <structure>. The <tlen> parameter indicates the size of <target>. Structures usually contain pointers to strings rather than strings themselves. This function retrieves the string pointed to by such a pointer. If the pointer points to a null-terminated string, you set <length> to 0. The return value indicates the number of bytes written to <target>.

SetStructCharPointer(<structure>, <offset>, <slen>, <value>)

This function writes a pointer to the structure string <structure> at offset <offset>. The pointer points to the variable that is passed as the <value> parameter. The <slen> parameter indicates the size of <structure>. You must pass a variable to the <value> parameter. You cannot pass a constant because the purpose of this function is to store the pointer to the variable in the structure. The variable <value> must remain in scope throughout the life of this structure. The return value indicates the number of bytes written to <structure>.

A few other functions are prototyped in STRUCMEM.JS. They are primarily intended to work with the Unicode version of IntraBuilder released in the Asian markets. Check out the STRUCMEM.JS file itself for information on these functions.

Using the Structure Parsing Library

The addYear() function shown in Listing 19.4 can be modified to use the structure parsing library instead of dissecting the structure itself. The updated version of addYear() is shown in Listing 19.5.


Listing 19.5. The structure parser at work in addYear().

 1: #include "strucmem.h"

 2: function addYear (stStruc)

 3: {

 4:    // prototype the structure parsing functions

 5:    _sys.scripts.run(_sys.env.home() + "samples\\strucmem.js");

 6:    // Year is first WORD value in structure

 7:    var year = GetStructNumber(stStruc,0,TYPE_WORD);

 8:    // Update the structure string with the incremented year

 9:    SetStructNumber(stStruc, 0, TYPE_WORD, stStruc.length, ++year);

10:    return (true);

11: }


Line 1 includes the STRUCMEM.H header file. This header defines the type constants used in lines 7 and 9.

Line 5 runs the script that prototypes the parsing functions. This version runs the script in its default location. You can duplicate this in your own script, or you can copy the STRUCMEM.JS and STRUCMEM.DLL files to your application folder. Then you don't need to include a path in line 5.

Line 7 uses the GetStructNumber() function to determine the value stored in the year member. Because year is the first member in the structure, its offset is 0. If you wanted to get the month member, the offset would be SIZEOF_WORD. To get the third member, the offset would be SIZEOF_WORD*2.

Line 9 uses the SetStructNumber() function to store the incremented year value back to stStruc. Again, the offset is 0 and the type is TYPE_WORD.

Summary

Today you learned about extending the functionality of the IntraBuilder server through the use of external objects and functions. External objects are accessed through OLE automation. IntraBuilder acts as an OLE automation client, and other applications such as Word, Excel, and OLEnterprise act as OLE automation servers. After an OLE automation object has been created, you use it just like any internal non-visual IntraBuilder object.

External functions are created using another programming tool such as Borland C++ or Delphi. These functions are prototyped in IntraBuilder using the extern command. After the function has been prototyped, you use it just like an IntraBuilder function such as parseInt() or encode(). IntraBuilder supports the basic C data types. When prototyping an external function that uses custom data types, you need to determine the base data types. If the custom data type is a structure, you can use the structure parsing library to manipulate the member values.

Q&A

Q:How do I determine the program identifier for an OLE automation server?
A:The product documentation should provide this information. If it doesn't, you can check with the product vendor. Another option is to use a tool called the Object Explorer, included with OLEnterprise. This tool lists all of the applications that have registered themselves as OLE servers. You can look for the program identifier and even test whether the server responds to OLE automation commands.
Q:Why does the OLE automation example work in the IntraBuilder IDE, but not in the IntraBuilder Server?
A:If the payment form does not work in the server, you are probably using IntraBuilder version 1.0. You should get the 1.01 update to get problem-free execution of OLE automation in the server.
Q:How many functions are in IntraBuilder?
A:There are surprisingly few functions in IntraBuilder. alert(), decode(), encode(), eval(), inspect(), parseFloat(), and parseInt() make up a total of seven.
Q:Why is there a discrepancy between the documented name and the actual function names in the Windows API?
A:The Windows API supports two different character sets: ANSI and Unicode. Functions that take strings or structures as parameters are implemented twice-once for each character set. The MessageBox() function is actually implemented twice-once as MessageBoxA(), and once as MessageBoxW(). The header files used by the C programmers define the documented name to reference the appropriate function depending on the character set used by the application.
Q:Are function prototypes case-sensitive?
A:Yes. The function name used in the extern command must exactly match the name of the function in the external program. For instance, you receive an error if you try to prototype a function called MESSAGEBOXA() in USER32.DLL because the case of the function name is incorrect.

Workshop

The Workshop section provides questions and exercises to help you get a better feel for the material you learned today. Try to answer the questions and at least think about the exercises before moving on to tomorrow's lesson. You'll find the answers to the questions in Appendix A, "Answers to Quiz Questions."

Quiz

  1. Is IntraBuilder an OLE automation client, server, or both?
  2. How do you create an OLE automation object named wrd from an application whose product identifier is word.basic?
  3. How are external functions different from IntraBuilder functions?
  4. What is the function prototype for MadeUp() in MADEUP.DLL? The function takes a string and a pointer to a long and returns an enumerated constant.
  5. What is wrong with this prototype?
    extern void* SomeOtherFunction(void) "MADEUP.DLL";
  6. How large is the following LOGBRUSH structure?
    1: typedef unsigned int UINT;
    2: typedef unsigned long DWORD;
    3: typedef long LONG;
    4: typedef DWORD COLORREF;
    5: typedef struct tagLOGBRUSH { // lb
    6: UINT lbStyle;
    7: COLORREF lbColor;
    8: LONG lbHatch;
    9: } LOGBRUSH;

Exercises

  1. Here is some information from the Windows header files about the MessageBeep() function in the USER32.DLL file. There is also information about four of the symbolic constants that are used for the uType parameter. Using this information and the WINDEF.H header file, write a script that duplicates the symbolic constants, prototypes MessageBeep(), and calls it using the symbolic constant MB_ICONEXLAMATION.
    #define MB_ICONHAND 0x00000010L
    #define MB_ICONQUESTION 0x00000020L
    #define MB_ICONEXCLAMATION 0x00000030L
    #define MB_ICONASTERISK 0x00000040L
    BOOL MessageBeep(
    UINT uType // sound type
    );
  2. The following information comes from the IDAPI.H header file that ships with
    the Borland Database Engine (BDE). Use this information to prototype the IDAPI32.DLL function DbiGetRecord().
    1: #define DBIFN __stdcall
    2: #define UINT16 unsigned short
    3: #define UINT32 unsigned long
    4: typedef unsigned char BYTE;
    5: typedef unsigned char BOOL8;
    6: typedef short BOOL16;
    7: typedef UINT16 DBIResult; // Function result
    8: typedef UINT32 hDBIObj; // Generic handle
    9: typedef hDBIObj hDBICur; // Cursor handle
    10: typedef BYTE far *pBYTE;
    11: typedef enum // Lock types
    12: {
    13: dbiNOLOCK = 0, // No lock (Default)
    14: dbiWRITELOCK = 1, // Write lock
    15: dbiREADLOCK = 2 // Read lock
    16: } DBILockType;
    17: typedef struct {
    18: UINT32 iSeqNum;
    19: UINT32 iPhyRecNum;
    20: UINT16 bRecChanged;
    21: BOOL16 bSeqNumChanged;
    22: BOOL16 bDeleteFlag;
    23: } RECProps;
    24: typedef RECProps far *pRECProps;
    25: DBIResult DBIFN DbiGetRecord (
    26: hDBICur hCursor,
    27: DBILockType eLock,
    28: pBYTE pRecBuff,
    29: pRECProps precProps
    30: );
    Exercises 3 through 5 refer to the following script. You should also refer to the script in exercise 2 to complete these questions.
    1: #include "strucmem.h"
    2: // prototype the structure parsing functions
    3: _sys.scripts.run(_sys.env.home() + "samples\\strucmem.js");
    4: // prototype the BDE function
    5: extern unsigned short DbiGetRecord(unsigned long,
    6: unsigned int, char*, void*) "idapi32";
    7: // initialize the structure
    8: recprop = new StringEx().replicate(" ", Ex3 );
    9: // create a query object
    10: q=new Query("select * from biolife.dbf");
    11: // get the record properties for this record
    12: DbiGetRecord(q.rowset.handle, 0, Ex4 , recprop);
    13: // get the physical record number from the structure
    14: var recno = GetStructNumber(recprop, Ex5 , TYPE_ULONG);
    15: // display the record number
    16: alert("Record Number " + parseInt(recno));
  3. What value would you use in place of Ex3 on line 8 in the preceding code?
  4. The pRecBuff parameter to DbiGetRecord() takes a pointer to a buffer that will contain the record data. Because this script does not use the record data, a null pointer should be used. What value do you use in place of Ex4 on line 12 to pass a null pointer?
  5. The record number is contained in the iPhyRecNum member of the RECProps structure. What value should you use in place of Ex5 on line 14? Use one or more of the SIZEOF constants from STRUCMEM.H.