Published By | Date | Version | Knowledge Level | Keywords |
---|---|---|---|---|
Revelation Technologies | 18 DEC 1991 | 2.1X | EXPERT | RBASIC, COMPILERS, STARTUP.OBJ, REVMSG.LIB, REVINTRF.H, EXE2BIN, DESCRIPTOR, INTERRUPTS, STRINGS, RAM, EXPANDED, MEMORY |
Advanced Revelation's C Language Interface allows C programs to be called from an Advanced Revelation application. This facility enables the development of very complex and efficient routines, such as the Linear Hash filing system, Advanced Revelation's mouse support, and even some less glamorous routines such as drawing the shadow around a window.
The C Interface has an undeserved reputation for being difficult to use. Unfortunately, the documentation does little to dispel this notion, assuming advanced skills in the intricacies of C programming.
However, you do not need to be a whiz to program in C or to incorporate C into your Advanced Revelation applications. This technical bulletin will give you an introduction to the C interface, provide you with step-by-step procedures, and present code examples for the most commonly asked "how do I do it in C" questions.
Note: This bulletin presumes a good working knowledge of programming in R/BASIC and at least a nodding acquaintance with C programming. In addition, it is not intended to be a substitute for the documentation provided with the C interface; you should have that documentation available for information on the functions discussed here.
Developing good software requires using the right tools for the task at hand. Choosing the language for a particular routine is no exception. R/BASIC is a very powerful language that, to a large degree, keeps you out of trouble. Because the R/BASIC language handles complex issues such as memory allocation, bounds checking, etc., it is almost impossible not to stay "in bounds" when programming. C, on the other hand, is a very powerful language that does virtually nothing to keep you out of trouble. You are responsible for making sure that you don't go out of bounds, and when you cross the line (and you will), you can only blame yourself.
Despite the extra work, there are times that you will want to write a routine in C. It may be a routine that requires the use of BIOS or DOS interrupts or one that can be performed faster in C than in R/BASIC. You may also have a third-party object library that you want to access for an Environmental Bond.
The next choice to be made is which C compiler to use. Your options are:
MS-C version 6.x appears to work with the C Interface Version 2.0, but has not been tested extensively; use it if you must, but do not trust it implictly. Unfortunately, Borland's C compilers are not supported.
We recommend Microsoft C 5.1 or QuickC. The Manx compiler has some limitations that are cumbersome, so it is not recommended.
There are some limitations that you need to keep in mind with the C interface. Some of these make sense without further explanation; others will be treated in more detail later in this bulletin. They are listed here together for reference.
The C interface provides all of the necessary framework for calling a C program from Advanced Revelation in the same manner that you would call an R/BASIC program. The key elements that comprise that framework are the STARTUP.OBJ module, the REVMSC.LIB library, and the REVINTRF.H header.
Advanced Revelation already has a built-in facility for calling specially coded assembly language routines. The startup is a bit of code that makes a C routine look (to Advanced Revelation) exactly like one of those special assembly routines.
This library provides functions for transferring values between C and R/BASIC, and for file I/O, string space allocation, and calling other programs in Advanced Revelation from C.
This header file defines special structures needed to use C with Advanced Revelation and provides prototypes for the functions in the REVMSC.LIB library.
Understanding the initial steps required to use the C interface is often the most difficult part of the process. This section will take you step by step through creating, compiling, linking, converting, and running your first C program for Advanced Revelation. Your code will go through several stages of transformation using both compiler utilities and a conversion utility supplied with the C Interface.
First use your favorite DOS editor to write the C program. Your program must define the symbolic constant MSC, and include the REVINTRF.H header file in that order. Figure 1 provides the source code for a simple C routine, including all relevant header information. Save the file as HELLO.C.
The next step is to compile the C program to object code. There are some special options that need to be set for the compiler using command line switches. Compile with the following compile line (Microsoft compiler assumed):
cl hello.c /c /AS /Gs
The compile line options have the following significance:
hello.c | the program to compile |
/c | compile to object code (do not automatically link) |
/AS | use the small memory model |
/Gs | no stack checking |
The compilation process creates an object module (HELLO.OBJ). The next step is to create an executable from the STARTUP.OBJ, your program, and REVMSC library. Type in the following LINK line:
link startup hello,hello,hello,revmsc /NOE
Note: there should be no spaces in the string "hello … revmsc".
The command line options, delimited with spaces and commas, have the following significance:
startup hello | link the STARTUP.OBJ and the HELLO.OBJ files. The link order is critical. STARTUP must always be first. |
hello | produce an executable named HELLO.EXE |
hello | produce a link map named HELLO.MAP |
revmsc | link with the REVMSC.LIB library |
/NOE | turn off external dictionary searches |
The HELLO.EXE file must be converted to a .BIN file using the DOS EXE2BIN utility. Execute the conversion with the following command line:
exe2bin hello.exe
This will produce a file called HELLO.BIN, assuming that no fixup errors were reported. If you see any fixup errors, your code is not relocatable and cannot be used. While this example should not produce any fixup errors, some of your later efforts might. Common culprits of non-relocatable code are graphics libraries, floating point libraries, and other functions from the standard library that have absolute segment references.
Note: Any program that uses the int86( ) function will carry an absolute segment reference; you therefore cannot use this function (see "Executing Software Interrupts", below).
The last transformation is performed by the FIXASM utility provided on the C Interface distribution diskette. Run FIXASM; it will prompt you for the name of the .BIN file. Be sure to include the drive and path with the file name if the file is not in your default directory. FIXASM will prompt you for the destination file and record.
If you get an "Invalid Binary File" error, the most likely problem is your linking order. Relink the program, assuring that STARTUP is the first file in the linking order.
The R/BASIC source for the FIXASM program is provided on the distribution disk, so you can study (and modify) it for your own purposes.
At this point, you now have a C program that can be run just like an R/BASIC program. To test the program, at TCL type:
run filename hello
substituting your program file name for filename in the command line.
Having established the ground rules for using C routines in Advanced Revelation, you are now ready to do some real work with the C Interface. A likely scenario is that you will need to get values to and from your C program. Unlike a stand-alone C program, the C programs that you write for the C Interface do not pass values to the main routine using the main(argc, argv) convention (if this makes no sense, please refer to your C Language reference manual). Instead, the Interface library provides specialized functions to store and retrieve arguments passed to the C program. This facility provides some flexibility in that the number of parameters passed need not be fixed and can be determined from within the program.
The C Interface provides two flavors of storing and retrieving parameters: Position Reference and Descriptor Reference.
Position Reference simply means that you specify the number of the argument that you wish to access. If you want the nth argument, pass n to a C Interface function, which instructs it to fetch the nth value for a particular data type. For example, if you call your C program from R/BASIC as:
n1 = 100 n2 = 200 s1 = "Mission Critical" call my_c_prog(n1, n2, s1)
you could use the C Interface function rev_fetch_integer to get n2. The variable n2 is the second argument, so the C code to get that integer would look like:
intn2; /* vars must be declared */ n2 = rev_fetch_integer(2);
There are functions for fetching all data types, including long integers and strings. There are functions for fetching doubles, but remember that you cannot use floating point with the C Interface 2.0 and the Microsoft compiler.
The code in Figure 2 makes use of the ability to pass a variable number of arguments to your C program. This is accomplished with the global variable no_args, established by the startup code of the Interface.
The Descriptor Reference method of accessing parameters is the preferred and more efficient method of access. However, it requires a little bit more understanding of the way that Advanced Revelation handles variables and string space. Having a handle on these concepts will enhance your understanding of the Interface and Advanced Revelation in general.
To understand Descriptor References, a small discussion of descriptors is warranted. This discussion will give you the fundamentals, and you are encouraged to explore further on your own, using the clues provided in the REVINTRF.H header file.
Advanced Revelation maintains a block of memory, referred to as the Descriptor Table, that is used to keep track of variables. It maintains information on variable types and on where the actual information is stored. The Descriptor Table is divided into ten-byte structures, each of which keeps track of one variable.
The descriptor type is determined by the last two bytes of the descriptor. The meaning of the remaining eight bytes depends on the descriptor type. For example, a short string (less than 9 bytes long) is stored in the descriptor itself. A long string, however, uses the descriptor to store a pointer to the location of the string in string space. An unassigned descriptor obviously stores nothing meaningful beyond being unassigned. A slice of the descriptor table might look something like the illustration in Figure 3.
So the descriptor table is really the roadmap to any variable in Advanced Revelation. The C Interface can use the information stored in the descriptors to store and fetch values. All you need to do is get a pointer to the particular descriptor that you want to access. In the case of fetching an argument passed to your routine, you can use a library function called RevLocateArgs( ) to get information about the arguments passed.
RevLocateArgs returns a structure called a HANDLE (defined in REVINTRF.H). A HANDLE structure is composed of an fpDESCRIPTOR, which is a far pointer to a descriptor, and an integer. The fpDESCRIPTOR points to the descriptor of the first argument in the argument list, and the integer contains the number of arguments passed (same as no_args). The descriptors for the values in the argument list occur in a block, so you can simply use pointer arithmetic to access each argument. For example, if your C program were called from R/BASIC as:
call my_c_prog(var1,var2,var3)
you get a pointer to the first descriptor with RevLocateArgs, and then step your way through the descriptors to access the other arguments, as illustrated in Figure 4.
Once you have a pointer to the descriptor, you can then use that pointer to fetch the value referenced by the descriptor. As with the position reference functions, there are several descriptor reference functions for fetching and storing different variable types. The example program in Figure 5 uses the RevFetchInteger function, which is the descriptor reference counterpart of the rev_fetch_integer function used in the previous example. Compare the two programs; they are identical in function and differ only by the method of accessing variables.
The C Interface allows you to make your C program behave like an R/BASIC function and directly return a value to the calling routine. To do that, your C program must allocate a new descriptor from the descriptor table, assign a value to that descriptor, and then instruct Advanced Revelation to store that descriptor as the returned value.
New descriptors are allocated with a call to the AllocateDescriptors function. That function returns a HANDLE structure, which can be used to get a pointer to the first descriptor in the block. If you asked AllocateDescriptors to allocate more than one descriptor, you can use pointer arithmetic to access other descriptors in the block.
A value must then be assigned to the allocated descriptor. Use the RevStore… functions to bind a value to the descriptor.
The last step is to notify Advanced Revelation that you have a return value. Call the RevStoreReturnValue function to do that. Put together, the steps look something like this:
main(); { HANDLE hand; hand = AllocateDescriptors(1); RevStoreInteger(0xffff, hand.block); RevStoreReturnValue(hand.block); }
The C Interface provides a function for executing interrupts, which can give you access to BIOS and DOS services unavailable from R/BASIC. This is where the value of the C Interface can really be appreciated.
The example in Figure 6 uses a software interrupt to check for the presence of a mouse. Note that interrupts are always invoked using the intfunc or dosfunc functions. Never use the int86( ) function! The call to intfunc is set up by loading global variables that emulate the CPU registers with appropriate values. After the call to intfunc, any values that would be returned in CPU registers will be in the corresponding global variables.
The example calls interrupt 33H, function 0H to check if a mouse is available. Note that the function number is loaded into the ax_reg variable. The interrupt returns the mouse status in ax_reg: FFFFH if a mouse is available, and 0 if not. The ax_reg value is passed back to the calling routine.
The C Interface allows you to call R/BASIC (or other programs written with the C Interface) from a C program. This can include system subroutines, your own programs, or recursive calls to a C routine.
The RevCallBasic function requires that arguments passed to the R/BASIC routine be referenced as fpDESCRIPTORs. Use AllocateDescriptors to allocate new descriptors, and be certain to assign a value to those descriptors before calling RevCallBasic.
The sample routine in Figure 7 demonstrates a call to the system subroutine MSG from C.
The C Interface will allow you to directly access the strings stored in string space. This can provide you with a great deal of power, but you must tread very carefully, because the consequences of a mis-step can be severe.
The function RevFetchString will return a far character pointer to the beginning of a string in memory (referenced by the descriptor that you pass to RevFetchString). You may then use that pointer to read or modify that string. You must not, however, write past the end of the string without extending the string space. (Failing to do so will probably have you looking at the DOS prompt).
You must also be cautious when calling C Interface functions that could result in garbage collection. A garbage collect can and will move the string in memory, and the pointer that you fetched will now be invalid. Figure 8 illustrates an example of an accident waiting to happen. Moral: always re-fetch strings when a garbage collection may have occurred.
As a C programmer, you will need to take special precautions when you are writing routines that will be run when expanded memory is being used in Expanded mode (the X option) by Advanced Revelation. If you fail to take precautions, you can have invalid data, or worse, wind up at the DOS prompt with a "String Space Format Error" on the screen.
Advanced Revelation uses two 64K overflow buffers for fetching strings and programs from expanded memory. Those buffers must be located below the 1MB point, since real mode programs cannot access memory above 1MB. When Advanced Revelation makes a request to the expanded memory manager to fetch an object out of expanded memory, the expanded memory manager grabs the desired object out of expanded memory, and kindly places the object in one of those overflow buffers. But since there are only two buffers, you are limited to having at most two pointers into Advanced Revelation string space.
Consider the following code fragment:
char far one, two, three; one = RevFetchString(descrip); two = RevFetchString(descrip+1); three = RevFetchString(descrip+2);
Figure 9 illustrates what happens. The net result is that the first pointer to string space will be invalid, because the third string has been fetched into that overflow buffer that stored the first string.
#define MSC 1 #include <revintrf.h> main() { rev_print_string("Hello, World!"); } /* main */
/* PROGRAM: argpass2.c PURPOSE: Demonstrate referencing parameters by position THEORY: This program demonstrates how parameters passed to a C subroutine can be referenced by their position in the argument list. The function rev_fetch_integer(n) returns the integer value contained in the nth parameter passed. The external no_args variable is set to the number of parameters passed to the C routine. */ #define MSC 1 #include <revintrf.h> #include <string.h> main() { static char crlf[] = { 13,10,0 }; char string[40]; int temp,loop; int ttl = 0; itoa(no_args,string,10); strcat(string," arguments passed."); strcat(string,crlf); rev_print_string(string); for (loop = 1; loop <= no_args; loop ++) { temp = rev_fetch_integer(loop); ttl += temp; itoa(temp,string,10); strcat(string,crlf); rev_print_string(string); } /* for */ itoa(ttl,string,10); strcat(string," is the total of the arguments"); strcat(string,crlf); rev_print_string(string); } /* main */
0 1 2 3 4 5 6 7 8 9 (descriptor info) (data type) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄ¿ 1) ³ H E L L O ³ short str ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄ´ 2) ³ (long int) ³ int ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄ´ 3) ³ pointer, length ³ long str ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÙ 1) Short string, stored directly in the descriptor. 2) Whole number, stored as a long (8-byte) integer. 3) Long string, stored as a pointer into string space, with the string length stored in the descriptor.
HANDLE hand; hand = RevLocateArgs; 0 1 2 3 4 5 6 7 8 9 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄ¿ hand.block ³ var1 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄ´ (hand.block+1) ³ var2 ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄ´ (hand.block+2) ³ var3 ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÙ
/* PROGRAM: argpass1.c PURPOSE: Demonstrate referencing parameters by fpDESCRIPTOR THEORY: RevLocateCommon(), RevLocateArgs(), and AllocateDescriptors() all return a HANDLE structure. A HANDLE is composed of an integer containing the number of structures within the block and a far pointer (fpDESCRIPTOR) to the first argument in the block. All items in the block can be accessed by using simple pointer arithmetic. This example demonstrates how to access the parameters passed to the C program by using the HANDLE returned by RevLocateArgs(). */ #define MSC 1 #include <revintrf.h> #include <string.h> main() { static char crlf[] = { 13,10,0 }; char string[40]; int temp,loop,number_args; int ttl = 0; HANDLE hand; hand = RevLocateArgs(); number_args = hand.size; itoa(number_args,string,10); strcat(string," arguments passed."); strcat(string,crlf); rev_print_string(string); for (loop = 0; loop < number_args; loop ++) { temp = RevFetchInteger(hand.block+loop); ttl += temp; itoa(temp,string,10); strcat(string,crlf); rev_print_string(string); } /* for */ itoa(ttl,string,10); strcat(string," is the total of the arguments"); strcat(string,crlf); rev_print_string(string); } /* main */
/* (c) Revelation Technologies, Inc. 1991 PROGRAM: ifmouse.c PURPOSE: check for presence of mouse THEORY: function returns boolean expression for presence of mouse. uses int33h funct 0. sample usage: declare function ifmouse if ifmouse() then ... end else ... end */ #define MSC 1 #include <revintrf.h> main() { HANDLE hand; hand = AllocateDescriptors(1); ax_reg = 0x00; intfunc(0x33); RevStoreInteger(ax_reg,hand.block); RevStoreReturnValue(hand.block); } /* main */
/* PROGRAM: callrbas.c PURPOSE: demonstrate calling rbasic program from c THEORY: uses RevCallBasic function provided by interface. Need to make sure that all variables (pointed to by fpDESCRIPTORS) are at least initialized to nul. This routine calls MSG and elicits a response. After the first call, MSG is called again to display the response. */ #define MSC 1 #include <revintrf.h> #include <string.h> main() { HANDLE hand; fpDESCRIPTOR desc; int loop; char buffer[30]; char nul[2]; nul[0] = 0; hand = AllocateDescriptors(4); desc = hand.block; CToRev("Hello! What is your name?",desc,0,0,0); CToRev("R",desc+1,0,0,0); CToRev(nul,desc+2,0,0,0); CToRev(nul,desc+3,0,0,0); RevCallBasic("MSG",4,desc,(desc+1),(desc+2),(desc+3)); CToRev("Nice to meet you, %1%",desc,0,0,0); CToRev(nul,desc+1,0,0,0); RevCallBasic("MSG",4,desc,(desc+1),(desc+3),(desc+2)); } /* main */
char far mystring; HANDLE hand; /* get pointer */ mystring = RevFetchString(mydescript); hand = AllocateDescriptors(2); /* do something that will force garbage collection... */ modify_string(mystring); /* "unpredictable results" here*/
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄ string 1 ³ ³ ³ ³ ³ ÚÄÄÄÄÄÅÄÄ string 2 ³ ³ ³ ³ ³ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ string³ ³ string³ ³ ³ EXPANDED ³ ³ ³ 1 ³ ³ 2 ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ MEMORY ³ ³ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ ³ OV1 OV2 ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄ string 3 ³ ³ ³ ³ ³ ³ ³ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ ³ ³ ³ ³ string³ ³ string³ ³ ³ ³ 3 ³ ³ 2 ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ ³ ³ OV1 OV2 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ