SET --- AI 99 PASS --- AI FIELD SHARE? Y CALL /appx/callee,func RESIDENT? N END? N FAIL 0When APPX runs this section of ILF code, it knows how to go out and find the UNIX file /appx/callee, then locate the function "func" which is stored inside it. If you are calling a function in a Windows DLL, you would use a Windows-style pathname for the library, like "F:\appx\callee,func" instead of "/appx/callee,func". In addition to the benefit of it being easier to add new functions to APPX via "dynamic" linking than via the old static linking method, there is another significant benefit: When a new release of APPX is introduced, no re-linking is required. The new engine will dynamically locate and run the code, just as the old one did. (Of course, there may be details specific to your code that would cause it not to run under a new version of APPX, but we expect that most user-written functions would be unaffected by an upgrade.)
$ more callee.c #include <stdio.h> #ifdef __cplusplus extern "C" { #endif void func( int x ) { printf( "inside of func - called with %d\n", x ); } #ifdef __cplusplus } #endifBefore continuing, note that this really is all that you need in your C program in order to allow APPX to access 'func'. No external declarations of APPX structures or APPX typedefs, etc. Not even a 'main'. The #ifdef's above take care of telling a C++ compiler not to mangle the symbol names in this file as it is compiled. APPX expects C-style names, not those that have been altered in the usual C++ way. You can store one, or many, functions in a dynamically-linked module. Whatever makes sense for your application. The following are the cc and ld lines required to compile the above file, callee.c, into a 'callee' binary, whose 'func' function can be access from APPX as described in the ILF code listed above. You can substitute any name for "callee", as desired. Solaris, SCO, Unixware, DG/UX, NCR:
$ cc -o callee -G callee.cLinux:
$ cc -o callee -shared callee.cHP/UX:
$ cc -Aa +z -c callee.c $ ld -b -o callee callee.oAIX:
$ cat callee.exp #! func $ cc -c callee.c $ cc -g -o callee -bE:callee.exp callee.oSpecial AIX instructions: Under AIX, you must create an 'exports' file that lists the functions you want to call. My file was called 'callee.exp' and lists one function. The first line contains "#!" and the succeeding lines contain "functionname"<RETURN>. An example of what this file would look like when cat'd is below, as is the command line you would use to set up the exported functions in callee. After issuing the proper compile instructions for your platform, you will end up with a shared library called 'callee' (or whatever other name you used instead of 'callee'). Again, it can be named whatever you like (the '-o callee' argument determines the name of the library).
Start simple!
Before you write a function that does "real work", write a really simple one and verify that you can call it from APPX without encountering errors. We suggest starting out with a function with NO parameters, that does something simple like open a text file in a directory writable by any user, and write a text string like "Function OK!" into it. You can then rectly interfacing between the code and APPX.
Be careful if you write functions that expect parameters to be passed as a RECORD containing multiple data fields, rather than individually.
The struct you use in your C function should contain fields which exactly match (in record offset and size) those in the techdoc for the file whose record layout you are using as a template to pass the group of fields. This may differ between platforms, due to varying alignment requirements, so please thoroughly test your external functions which use structs as parameters when moving to a new implementation platform (machine).
Given this potential downside, why would someone want to do it anyway? APPX will accept a maximum of 16 arguments for a CALLed function (that is, 16 PASS statements). If you want to PASS more data items than that, you must PASS groups of fields, instead of PASSing all of them individually. Also, some developers may simply prefer to group related fields in a RECORD structure, as a matter of style. Your first step when writing a function which expects to receive a RECORD or RECORD pointer as a parameter should be to verify that your struct lines up with the data (perhaps via a series of fprintf's of each individual structure member, into a file you can inspect).
Doing this before you write any code which assumes the struct matches the PASSed data may save you LOTS of debugging time down the road.
Follow the usual guidelines for robust function design, such as not requiring parameters to be passed by reference unless the function must modify them, free'ing all memory you allocate in the function before you exit the function, and being very careful when using pointers.
If you have to modify an ALPHA field passed from APPX, so you pass it by reference, be warned that although it's declared as a "char *" in your function, the field IS NOT null terminated.
In order to maximize function reliability, we recommend NOT hard-coding the length of this field in your function, but rather, having the APPX designer PASS the length of that field, as another parameter, to the called function. This way, your function won't fail (or behave very unpredictably, which is worse!) when the length of that ALPHA field is changed in the APPX data dictionary.
All called functions are run in the same OS process and thread context as the APPX engine.
This makes use of UNIX exec() style OS functions a very bad idea (it'll end APPX immediately without allowing for any cleanup). Using system() instead is the easiest alternative, although the UNIX fork()/exec() should work as well if you really, really need the return code from whatever external command you're executing.
If you expect the function to ever be called within an APPX environment that uses the Presentation Client, Desktop Client or HTML Client front end, avoid any sort of terminal I/O. Error messages reported on standard output or error, as most UNIX programmers tend to do it, will never be seen by users running either of the graphical clients.
A good alternative might be passing error messages back to the caller, via a shared ALPHA work field, which can be displayed if the function's return code indicates that an error occurred. Reading from standard input also is not available. Please obtain any needed user-provided parameters via an APPX INPUT process, and pass them to the external function.
If you are creating a function to be run under APPX for Windows, and expect that function to ever be called within an APPX environment that uses the Presentation Client, Desktop Client or HTML Client front end, DO NOT use Message Boxes, Dialogs, or ANY other Windows GUI features.
If you do use any of these types of GUI elements, IF they display at all (which is very unlikely), they will display on the server, and hang the end-user's client. Another symptom of this could be a USER32.DLL error indicated on the server, with the end-user's client being hung, although USER32.DLL errors can occur for unrelated reasons as well. Still another symptom could be a hung client with no error message at all. (Hopefully this is convincing you that it is a very bad idea to attempt to use GUI features in a server-side application.)
If your extension uses third-party functions, like fax library routines, be aware that not all function libraries which run under Windows have been written to be server-aware. Some still engage in the practice of putting up their own message boxes, particularly to inform the user of error conditions. This is not appropriate for a server application. Test your external functions thoroughly, and if users experience hangs once the code using these functions is deployed, be sure to check to see if USER32.DLL errors or any unexpected windows are appearing on the server.
APPX does not reset the effective user ID prior to calling a dynamically-linked function.
Since APPX for Windows always runs with the user ID of the person who invoked APPX (either locally or via one of the GUI clients), this is relevant only to the UNIX platform.
Under APPX for UNIX (and Presentation Client and Desktop/HTML Client configurations where the APPX server runs on UNIX), functions will be invoked with an effective user ID of the owner of the 'APPX' executable, which is usually the user 'APPX'. Thus, watch for issues involving ownerships and permissions on text or other files created by dynamically-linked functions. They might not be what you would first expect. (Prior to a UNIX command being invoked by RUN, the effective user ID is reset to the logon user ID.)
Why doesn't a CALL reset it as well? Because there are many things one would want to do in a CALLed function, for which the effective user ID is irrelevant, so it's wasteful to always incur the overhead of resetting it to the logon user, then setting it back to 'APPX' after each CALL. Besides, sometimes the function developer might WANT the effective user ID to be 'APPX', so that the function could, for example, write to a shared log file owned by 'APPX'.
If you ABSOLUTELY, POSITIVELY MUST execute code in your function under the UNIX user's logon user ID, you can use the following sequence of ILF code, to set the effective user ID to the UNIX users' ID before calling your function, and to set it back to 'APPX' afterward.
CALL ,RT_SET_UID RESIDENT? Y END? N FAIL 0
CALL /appx/callee,func RESIDENT? N END? N FAIL 0
CALL ,RT_SET_APPX RESIDENT? Y END? N FAIL 0
Not including the ",rt_set_appx" line may APPEAR to work, at least for a while, but it MUST be there, and MUST be executed whether or not the CALL of your external function completed successfully.
Failure to include it WILL result in difficult-to-trace errors being encountered by the APPX engine, due to security issues. DO NOT use ",rt_set_uid" and ",rt_set_appx" unless you are completely certain that you understand the concepts of effective and real user ID's under UNIX, and you are certain that your function requires their use.
Remember that modifications to your APPX Data Dictionary which change the size or type (or, in the case of fields being passed as part of a RECORD, the offset) of a data field PASSed to an external function, may (and almost certainly will) require that the external function be updated to work with the modified field definition. APPX has no way to detect such situations and warn the APPX designer about them.
SET --- AI 99 PASS --- AI FIELD SHARE? N CALL /appx/callee,func RESIDENT? N END? N FAIL 0This would pass the integer value 99 to the called function. The APPX designer should PASS one argument for each parameter required by the C function he/she is CALLing. The PASS statements should correspond to the order in which parameters are listed in the C function declaration. (See the function's developer for details.) Be aware that if you change the data type, size (or if PASSing an entire RECORD structure, the offset) of a field being PASSed, via the APPX Data Dictionary, any external function in which that field is used should be reviewed, and any necessary changes should be made to it, in order to accommodate the change in the field definition. Specific attention should be paid to the SHARE? flag values set in PASS statements, because whether they should be set to Y or N depends on how the C function was declared. (The original developer of the C function you are CALLing is the best source for information on this.) In general, if you want to pass a copy of the original APPX field, because you do not need to modify that field in the C function, you should set "SHARE?" to "N". If you want to pass the original APPX field itself, and DO expect to modify that field in the C function, you should set "SHARE?" to "Y". In all cases, you must coordinate with the writer of the C function, to make sure both of you agree on which parameters are shared and which are not. If your SHARE? values don't match what the C programmer expected, the function will not work properly. More details on what "SHARE?" means, for various data types, follow. If your CALLed function expects a parameter which is a pointer to an unsigned integer or long, set "SHARE?" to "Y" when you PASS it. This will perform "pass by reference", sending the address of the variable to your function, instead of sending its value. One of the ways this is useful is that a CALLed function can modify that value, perhaps by doing an elaborate mathematical calculation, so that a new value is reflected in that variable when you return to APPX. If your CALLed function expects a parameter which is an actual unsigned integer, long or short, set "SHARE?" to "N" when you PASS it. This will perform "pass by value", sending the actual variable value rather than its address. You can be confident that, unless the function you are calling has a bug that results in unpredictable memory over-writes, the value of the variable in APPX will not change, if you pass it "SHARE? N". (However, the function must be written to expect "SHARE? N" for that parameter. You cannot just arbitrarily change the value of the SHARE? flag and expect the function to work properly.) The field that you PASS should be a be a 4-byte binary field for "int" and "long" C data types, and a 2-byte binary field for the "short" data type. If your CALLed function expects a parameter which is a "char *" (that is, a C string), and it DOES NOT want to modify the original APPX alpha or text field's value, set "SHARE?" to "N" when you PASS it. This will cause a pointer to a null-terminated copy of that APPX field to be passed to the CALLed function. "Null-terminated" means that a hex '00' character is appended to the end of the copy of the field. This is the C convention for working with character strings. If your CALLed function expects a parameter which is a "char " (that is, a C string), and it DOES want to modify the original APPX alpha or text field's value, set "SHARE?" to "Y" when you PASS it. This will cause a pointer to the actual APPX field to be passed to the CALLed function. IT WILL NOT BE NULL-TERMINATED, so the writer of the C function must be careful when working with it. If you PASS an APPX FILE, what the C program will receive is the full-path-name of the file, represented as a C character string. It does not receive any sort of open file pointer, record number, or record contents. You can also PASS packed-decimal fields and date fields, but it is up to you to "decode" the APPX internal format. Needless to say, we recommend passing numeric values in binary form, and dates in (probably) character string form (convert the date to alpha before PASSing the alpha) rather than using the APPX internal formats for these fields. It is also possible to PASS the actual data file record (so that you can modify it) or a copy of that record (just for reading). Again, be careful doing this, because when an APPX designer changes the record layout, the C function will malfunction in unpredictable ways. The record layout will match what is described in techdoc, in terms of offsets into the record and lengths for each field. No conversions of data are performed when a record is PASSed, and there is no way to verify from within the C function that the size and layout of the PASSed record matches what the function was written to expect. Finally, many C functions provide their callers with a "return code" value. If your CALLed function sets its return code to indicate various completion statuses, you can examine the 32-bit value returned by it (if any) by inspecting the APPX PDF "--- RETURN CODE". Here is a list of how C types correspond to APPX data types and PASS share flags:
C function parameter | APPX data type | SHARE? |
unsigned char | LOGIC (length 1) | N |
unsigned char * | LOGIC (length 1) | Y |
char * | ALPHA SUBSTRING TEXT |
Y or N(1) |
unsigned integer | NUMERIC BINARY (length 4) | N(2) |
unsigned integer * | NUMERIC BINARY (length 4) | Y(2) |
unsigned long | NUMERIC BINARY (length 4) | N(2) |
unsigned long * | NUMERIC BINARY (length 4) | Y(2) |
unsigned short | NUMERIC BINARY (length 2) | N(2) |
unsigned short * | NUMERIC BINARY (length 2) | Y(2) |
struct | (PASS as RECORD) | N |
struct * | (PASS as RECORD) | Y |
#include <stdio.h> #ifdef __cplusplus extern "C" { #endif void func( int * x ) { printf( "inside of func - called with %d\n", *x ); *x=55; } #ifdef __cplusplus } #endifYou must also change the "SHARE?" flag from 'N' to 'Y' on the PASS:
SET --- AI 99
PASS --- AI FIELD SHARE? Y
CALL /appx/callee,func RESIDENT? N END? N FAIL 0
The result of calling this function is that --- AI should be set to 55.