Previous chapter is Chapter 2, "The OmniMark Interface to External Functions".
Next chapter is Chapter 4, "External Function API Reference".
As a by-product of using the C language, the OmniMark external function programmer can know more about the internals of the objects than OmniMark Technologies would like. This is not because we are secretive. Rather we fear that clever programmers will try to exploit this information in ways that will become invalid in future releases.
Because of this, OmniMark Technologies asks application programmers not to use undocumented features. The OmniMark External Functions Programmer's Reference is the only authoritative source of information about the OmniMark External Function Interface. OmniMark Technologies can and will change the implementation as needed, without regard to the effect on any undocumented characteristics of the API. If the documented programming interface does not provide as much functionality as required or if it is found to be too slow please contact OmniMark Technologies and we will address the concerns that are raised.
The external function API uses shared libraries on every supported platform. Each external function in an OmniMark script is linked, upon the first call to the function at run time, to a corresponding entry point in a shareable dynamically linked library. If the OmniMark program subsequently assigns a different entry point in the same or another library, the next call to the external function will use the new entry point.
The shareable libraries are written according to the conventions and methods for your particular platform, as described in platform-specific documents, which are available online from our web site (http://www.omnimark.com). The documents contain examples and information to help you write and compile external function libraries as easily as you would write an ordinary executable program in C. The principal differences are
The external function API is defined only in C. If you want to use C++ code, you will need to use the extern "C" calling convention at the API. If you want to use another language, you will need to call functions in the other language from C.
Every external function entry point is a C function that receives a single argument, which is a pointer to an OMXF_Environment_type object. All of the information about external function arguments, and all of the tools for dealing with those arguments, are obtained indirectly through the methods (or functions) encapsulated in the OMXF_Environment_type object. This approach gives you much better access to OmniMark data than you would have if the data were passed directly as C arguments. You can query the OMXF_Environment_type object to get information about the OmniMark arguments given to the external function, including
You can also query the OMXF_Environment_type object to get access to other objects that are appropriate to particular purposes (see Section 3.4.2, "Getting API Objects"". For example, you can get an OMXF_StreamShelf_type object, which encapsulates a full set of methods for dealing with a stream shelf argument.
All External functions are void C functions. Any external function that returns OmniMark data does so by invoking methods in the external function API. Using the API instead of a C return command makes it possible to return the kinds of data that are familiar to OmniMark programmers.
External sources do not return any data during initial execution. Instead, a read subfunction returns data at the times required by subsequent execution of an OmniMark program.
External outputs return a pointer to an external output object that was created and initialized during the initial execution of the external output.
Opaque data functions create an opaque data item during initial execution, and return a pointer to that item.
External functions can also write to the OmniMark current output, that is, to the same place that an OUTPUT action would send data.
External Outputs, External Sources and Opaque functions all make a service available to OmniMark on demand, at the times required by the execution of OmniMark code:
Consequently, the initial execution of external outputs, external source, and opaque data must install new functions that can be called after the initial execution is complete, to continue the actual work at the times required by the subsequent execution of an OmniMark program. For example, each of the actions in the list above is supported by a corresponding subfunction:
You can signal an exception from any function or subfunction in an external function library. Doing so will terminate the OmniMark program. You use OMXF_SignalExternalFunctionException to tell OmniMark that a termination is required, and to give an explanatory message:
/* p_Object is the (OMXF_Environment_type *) or (OMXF_Environment_type *)
pointer received by the funciton or subfunction */
OMXF_SignalExternalFunctionException(p_Object, "Your message");
When the function or subfunction returns to OmniMark, OmniMark will display the explanatory message, report a fatal error, and terminate.
Two constants identify the version of the OmniMark External Function API which is being used. OMXF_API_VersionNumber is a numeric constant. and OMXF_API_VersionString is a descriptive string that includes the version number.
There are two primitive data types, OMXF_int and OMXF_char, and one structure type, OMXF_String_type. These types describe data that will be used by both OmniMark and your external functions. It is very important that you use these types, instead of common C types (like char, int and long), even if the C types happen to be equivalent to some of the OmniMark types on your current platform.
OMXF_int is a 32-bit signed integer, on every platform, whether or not an int on that platform is 32 bits. OMXF_int will be a 32-bit integer in future versions of OmniMark. Using OMXF_int even when your platform has an equivalent definition (like into or long) will help to make your external functions portable across platforms, and across operating system upgrades.
OMXF_char will store a single character, in the format that OmniMark uses to store characters. OMXF_String_type stores a string as an array of OMXF_char. Unlike string arrays in C, an OmniMark string can contain null characters, and the OmniMark string includes a size, which is the number of OMXF_char that it holds.
In order to provide for future enhancements, OmniMark's internal storage format for characters is subject to change. The storage format may differ between different versions of OmniMark, or even between the same version of OmniMark running on different platforms. Moreover, an OMXF_String_type is not just an array of OMXF_char, it also contains a field that gives the number of characters in the string. Consequently, good programming practice requires that:
It is particularly necessary to follow these rules if you want your external functions to be portable across platforms, or upwardly compatible with future versions of OmniMark.
The API uses the following enumerated types:
The values of each enumerated type have descriptive names, which are listed in the OmniMark External Function Programmer's Reference.
The external function API is encapsulated as a set of objects with methods. Almost all of the API functions take a pointer to an object as their first argument. The function will invoke the appropriate method in that object.
The first argument of every external function and every subfunction is a pointer to one of OMXF_Environment_type or OMXF_SubEnvironment_type. Some subfunctions receive additional arguments. For example, an external output function might be declared as
void output_test(OMXF_Environment_type *p_Environment);
and the write subfunction might be declared as
static void
Write_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
char * p_OutputDataBuffer,
OMXF_int AmountInDataBuffer);
Nearly all of the API functions are defined only in relation to a specific object, which must be specified as the first argument of the API function. The form of invocation is method(object) or method(object,parameters) where parameters represents any additional information that a particular method may need. For example, to install the write subfunction in an external output, you must pass the OMXF_Environment_type object that was received by the external output function:
status = OMXF_SetWriteFunOfExternalOutput (p_Environment, Write_fun);
The above command attaches your write subfunction to the object that OmniMark has created for controlling a specific external output function.
External functions, external sources and external outputs receive a pointer to an OMXF_Environment_type object that encapsulates a set of methods. Opaque data functions receive a pointer to an OMXF_SubEnvironment_type object that encapsulates set of methods. In both cases, you can get additional objects from the original object. You will generally need some of these additional objects to carry out the purpose of your functions,
The objects that can be obtained from an OMXF_Environment_type object are:
For operations on streams, temporary objects can be allocated by methods in the OMXF_StreamShelf_type object:
The temporary objects will be freed when the external function exits. You can also invoke methods in the OMXF_Environment_type object to free these objects prior to exit from the external function. It is necessary to free OMXF_Sink_type objects before you can close the corresponding stream items, and to free OMXF_Source_type objects before you can open the corresponding stream items.
In opaque data functions, you will need to obtain an OMXF_OpaqueHeader_type object from the OMXF_SubEnvironment_type object. The opaque header is an object defined by the API, but it is created by the user as part of a user-defined opaque value. The OMXF_OpaqueHeader_type contains methods to copy and to free the corresponding opaque data item (see Section 3.7, "Opaque Data Functions").
Subfunctions receive a pointer to an OMXF_SubEnvironment_type object. If a subfunction needs additional objects, it receives pointers to the necessary objects. For example, opaque copy subfunctions (see Section 3.13.2, "Opaque Data Copy Subfunction") get pointers to an OMXF_SubEnvironment_type object and the user-created opaque data value that they are being asked to duplicate.
This example illustrates one object can be obtained from another, and the use of the new object to get a value from an argument that was given to an external function. More complete information about the derivation of objects will be given in the context of their use in the different types of external functions.
Suppose that you have written an external function that takes a counter shelf as its single argument:
DEFINE EXTERNAL COUNTER FUNCTION counter-test (MODIFIABLE COUNTER value)
AS "counter_test" IN FUNCTION-LIBRARY "omdlltst"
The corresponding C declaration in the external function library is
void counter_test(OMXF_Environment_type *p_Env);
To read the value of the the default item on the counter shelf, you first use the OMXF_GetCounterArgumentByPosition method in the OMXF_Environment_type object to get the OMXF_CounterShelf_type object that corresponds to that counter:
OMXF_CounterShelf_type * p_counter;
p_counter = OMXF_GetCounterArgumentByPosition(p_Env, 1);
Next, you find out which item on the counter shelf is the default item, using the OMXF_GetDefaultItem method in OMXF_CounterShelf_type object,
OMXF_int item;
item = OMXF_GetDefaultItem(p_counter);
Finally, you obtain the counter item value by using the OMXF_GetCounterItemValue method in the OMXF_CounterShelf_type object:
OMXF_int countervalue;
countervalue = OMXF_GetCounterItemValue(p_counter, item);
Each object has its own failure indicator which records either the success, or the reason for failure, of the most recent operation on that object. OMXF_GetFailureIndicator returns the value of the failure indicator. The failure indicator is an enumerated type (OMXF_FailureIndicator_type), and the values have descriptive names. In the following example, OMXF_GetFailureIndicator will return OMXF_fi_Success if the default item is on a counter shelf, or OMXF_fi_DefaultItemNotInShelf if the counter shelf has no items (as when the shelf has just been cleared).
OMXF_CounterShelf_type * p_counter;
OMXF_int item;
OMXF_FailureIndicator_type status;
item = OMXF_GetDefaultItem(p_counter);
status = OMXF_GetFailureIndicator(p_counter);
Most of the examples given in this document do not show any check for errors. However, all of the API methods will report a failure if one occurs, and you should always check for failure at any point where it is possible to get a failure. If you want to check for failure, you can use the following rules as a guide:
If your external function detects a serious error, you can use the OMXF_SignalExternalFunctionException method to terminate the OmniMark run with a message. In the above example, if p_Env was passed in to the external function as an OMXF_Environment_type object, you could use the following code to terminate the OmniMark run when there was no item on the counter shelf:
if (status == OMXF_fi_DefaultItemNotInShelf)
OMXF_SignalExternalFunctionException(p_Env, "No items on counter shelf");
Your external function should always return to OmniMark, even after a serious failure or exception -- never use the C functions abort() or exit() from an external function or subfunction. Failing to return to OmniMark under any conditions may cause serious problems. Using OMXF_SignalExternalFunctionException to terminate gracefully will
The API provides two methods for getting version information and comparing the version of the API used by OmniMark to the version of the API that your external function library expects.
OMXF_CheckOmniMarkVersion compares the version that your library expects (OMXF_API_VersionNumber) to the version that is being used by OmniMark, returning a nonzero value if and only if the two versions are compatible:
OMXF_int status;
status = OMXF_CheckOmniMarkVersion(p_Obj, OMXF_API_VersionNumber);
where p_Obj is a pointer to the OMXF_Environment_type or OMXF_SubEnvironment_type object that was passed in to your external function or subfunction.
OMXF_GetOmniMarkVersion gets the string that describes the version of the API that OmniMark is using. Note that this may be different from the string OMXF_API_VersionString that describes the version that your external function library was compiled for.
A number of the methods in the API require OmniMark strings. However, external function developers are likely to obtain these values as arrays of C characters. The API provides two kinds of temporary OmniMark string, and a set of methods for converting arrays of C characters to OmniMark strings.
One kind of temporary OmniMark string is allocated when needed, and initialized to the OmniMark equivalent of the array of C characters. These strings can be explicitly freed at any time. Any unfreed strings will be freed when the external function exits.
There is also one permanently allocated temporary OmniMark string, which can be initialized at any time. Initializing this string destroys the previous value, and also relocates the string in memory. The string is not available for use until it has been initialized at least once during the current invocation of the external function. The principal use of this string is to produce an OmniMark string for one-time use without the overhead of allocating and freeing a new string.
The string allocator methods are
You can write to the OmniMark current output, just as you would with an OUTPUT action in OmniMark code.
You would use the OMXF_Write_C_chars_ToCurrentOutput method to write C characters to current output (p_Environment is the pointer to OMXF_Environment_type that your external function received):
char * C_CharArray;
OMXF_int NumberToWrite;
OMXF_int Status;
Status = OMXF_Write_C_chars_ToCurrentOutput(p_Environment, NumberToWrite,
C_CharArray);
You would use the OMXF_Write_OMXF_chars_ToCurrentOutput method to write C characters to current output (p_Environment is the pointer to OMXF_Environment_type that your external function received):
OMXF_char * OMXF_CharArray;
OMXF_int NumberToWrite;
OMXF_int Status;
Status = OMXF_Write_OMXF_chars_ToCurrentOutput(p_Environment, NumberToWrite,
OMXF_CharArray);
In order to impose the least possible constraint on what an external function might do, the arguments are presented in a way that allows full access to all of the shelf management facilities in OmniMark. You can obtain and modify item values, add items and remove items on a shelf.
An external function always receives a single C argument. That argument is a pointer to an OMXF_Environment_type object. All of the OmniMark arguments that were given by OmniMark code are accessed indirectly from the OMXF_Environment_type object, in a sequence of steps that begins with accessing a specific shelf, and ends with getting or modifying the value of a shelf item.
The API allows for the possibility that the same external function in your library may be declared as several different external functions in OmniMark code, each having a different list of arguments. In such cases, you will use the API to obtain descriptive information about each OmniMark argument, prior to trying to access those arguments.
Each shelf in the OmniMark argument list of an external function has an OMXF_ArgumentInfo_type object with methods that give information about the argument:
There are two ways to get the OMXF_ArgumentInfo_type object. depending on whether or not you already have the API shelf object of the corresponding OmniMark shelf.
If you already have the shelf object, you can use OMXF_GetArgumentInfo method on that shelf object. If p_CounterShelf is a pointer to a OMXF_CounterShelf_type object, the following code will get the OMXF_ArgumentInfo_type object for the corresponding OmniMark counter shelf argument:
OMXF_ArgumentInfo_type *p_ArgumentInfo;
p_ArgumentInfo = OMXF_GetArgumentInfo(p_CounterShelf);
If you do not have the shelf object, you can get the OMXF_ArgumentInfo_type object from the position of the shelf argument in the OmniMark external function declaration. If the OMXF_Environment_type pointer received by your external function is p_Environment, the following code will get the OMXF_ArgumentInfo_type object for the second shelf argument:
OMXF_ArgumentInfo_type *p_ArgumentInfo;
p_ArgumentInfo = OMXF_GetArgumentInfoByPosition(p_Environment, 2);
In order to get the API shelf object for an OmniMark shelf argument, you must know what type of object (like OMXF_CounterShelf_type) to ask for. If you do not know what type of shelf was given as an argument, you must first get a OMXF_ArgumentInfo_type object for the shelf. Suppose that
The following code will get an appropriate shelf object:
OMXF_ArgumentInfo_type *p_ArgumentInfo;
OMXF_ArgumentInfo_type ShelfType;
OMXF_CounterShelf_type *p_CounterShelf;
OMXF_SwitchShelf_type *p_SwitchShelf;
p_ArgumentInfo = OMXF_GetArgumentInfoByPosition(p_Environment, 1);
ShelfType = OMXF_GetArgumentInfo(p_ArgumentInfo);
switch (ShelfType)
{ OMXF_aot_CounterShelf:
/* code to get an OMXF_CounterShelf_type object */
break;
OMXF_aot_SwitchShelf:
/* code to get an OMXF_SwitchShelf_type object */
break;
default:
/* code to signal an exception and give an error message */
}
Accessing data in the items of a shelf is a multistage process:
When you want access to any particular shelf in the argument list of an external function, you can identify the shelf by its position in the argument list (see Section 3.5.2.1.1, "Getting a Shelf Object By Position and Type"). You can also find out if an OPTIONAL argument is specified before you try to access it (see Section 3.5.2.1.2, "Optional Arguments").
If p_Environmentis a pointer to the OMXF_Environment_type object received by your external function, you can use the following code to get objects that correspond to counter, opaque, stream or opaque shelves:
OMXF_CounterShelf_type * p_CounterShelf;
OMXF_int CounterArgumentPosition;
OMXF_OpaqueShelf_type * p_OpaqueShelf;
OMXF_int OpaqueArgumentPosition;
OMXF_StreamShelf_type * p_StreamShelf;
OMXF_int StreamArgumentPosition;
OMXF_SwitchShelf_type * p_SwitchShelf;
OMXF_int SwitchArgumentPosition;
p_CounterShelf = OMXF_GetCounterArgumentByPosition(p_Environment,
CounterArgumentPosition);
p_OpaqueShelf = OMXF_GetOpaqueArgumentByPosition(p_Environment,
OpaqueArgumentPosition);
p_StreamShelf = OMXF_GetStreamArgumentByPosition(p_Environment,
StreamArgumentPosition);
p_SwitchShelf = OMXF_GetSwitchArgumentByPosition(p_Environment,
SwitchArgumentPosition);
You can use the OMXF_ArgumentInfo_type object to find out the shelf type of an argument before you try to access it (see Section 3.5.1, "Getting Descriptive Information About Shelf Arguments").
You can only access an optional argument when the argument has been given in the OmniMark invocation of your external function. You can use the OMXF_ArgumentIsOptional method (See Section 3.5.1.1, "The ArgumentInfo Object" to see if the argument has been declared as an optional argument in OmniMark. If the argument has been declared as optional, it is possible to get a OMXF_fi_OptionalArgumentNotSpecified error (see Section 3.4.3, "Failure Indicators and Exceptions") when you try to access the argument.
Assuming that a counter shelf is an optional argument, you could use the following code to either get the value of a default counter item, or to substitute your own value when no counter argument was given:
OMXF_CounterShelf_type * p_CounterShelf;
OMXF_int CounterArgumentPosition;
OMXF_int CounterValue;
/* p_Environment is the (OMXF_Environment_type *) pointer received by
the external function */
p_CounterShelf = OMXF_GetCounterArgumentByPosition(p_Environment,
CounterArgumentPosition);
if (p_CounterShelf)
CounterValue = OMXF_GetCounterItemValue(p_Environment,
OMXF_GetDefaultItem(p_CounterShelf));
else
if (OMXF_GetFailureIndicator(p_Environment)
== OMXF_fi_OptionalArgumentNotSpecified)
/* set own value *.
CounterValue = 0;
else
/* report an error */
After selecting the item you want (see Section 3.5.3.1, "Choosing a Shelf Item"), you use access methods that depend on the type of the shelf. You can also remove data by removing an item, or add data by adding and initializing and item (see Section 3.5.3, "Managing a Shelf").
After getting a OMXF_CounterShelf_type object (see Section 3.5.2.1, "Getting the Shelf Object for an Argument") you can select an item (see Section 3.5.3.1, "Choosing a Shelf Item"), to read or modify, and you can add or remove items (see Section 3.5.3.2, "Inserting or Removing Shelf Items").
You use the OMXF_GetCounterItemValue method to get a value from a counter item and OMXF_SetCounterItemValue method to change the value in a MODIFIABLE counter item:
OMXF_CounterShelf_type * p_CounterShelf;
OMXF_int CounterItem;
OMXF_int CounterValue;
OMXF_int Status;
CounterValue = OMXF_GetCounterItemValue(p_Environment, CounterItem);
CounterValue++;
Status = OMXF_SetCounterItemValue(p_Environment, CounterItem, CounterValue);
After getting a OMXF_OpaqueShelf_type object (see Section 3.5.2.1, "Getting the Shelf Object for an Argument") you can select an item (see Section 3.5.3.1, "Choosing a Shelf Item"), to read or modify, and you can add or remove items (see Section 3.5.3.2, "Inserting or Removing Shelf Items").
If you want to read or modify the user-defined data for an opaque shelf item, you can use the OMXF_GetOpaqueItemReference method (see Section 3.5.5.1, "Accessing User-Defined Data in Opaque Objects" to get a pointer to the corresponding opaque value.
OMXF_OpaqueShelf_type * MyOpaqueShelf;
MyOpaqueData_type * MyOpaqueData;
OMXF_int ShelfItemNumber;
OMXF_int Status;
MyOpaqueData = (MyOpaqueData_type*) OMXF_GetOpaqueItemReference
(MyOpaqueShelf, ShelfItemNumber)
/* use the pointer MyOpaqueData to read or change user-defined data */
You can use the OMXF_SetOpaqueItem method (see Section 3.5.5.2, "Replacing Opaque Data in an Opaque Shelf Item") to replace the current opaque value in a MODIFIABLE opaque shelf item by a new opaque value. The previous opaque value is released automatically by OmniMark. The new object must be owned by your external function, either because you created it, or because you copied it from another object (see Section 3.5.5.3, "Copying and Releasing Opaque Data Objects").
OMXF_OpaqueShelf_type * MyOpaqueShelf;
MyOpaqueData_type * MyNewOpaqueData; /* new object to replace old one */
OMXF_int ShelfItemNumber;
OMXF_int Status;
/* replace the old opaque object by the new one */
Status = OMXF_SetOpaqueItem(MyOpaqueShelf, ShelfItemNumber,
(OMXF_OpaqueShelf_type*) MyNewOpaqueData)
After getting a OMXF_StreamShelf_type object (see Section 3.5.2.1, "Getting the Shelf Object for an Argument") you can select an item (see Section 3.5.3.1, "Choosing a Shelf Item"), to read or modify, and you can add or remove items (see Section 3.5.3.2, "Inserting or Removing Shelf Items").
Because streams are more flexible than counters or switches, a larger collection of methods is required to handle stream items (see Section 3.5.4, "Managing Stream Items". A stream item must be closed for reading, and open for writing (see Section 3.5.4.1, "Opening, Closing and Discarding Stream Items". You read incrementally from a closed stream item, specifying the maximum number of characters that you want on each read. You write incrementally to a MODIFIABLE stream item, specifying the number of characters that you want to output on each write.
To write to a MODIFIABLE stream item, you can open the item, attaching it to a buffer or file (see Section 3.5.4.1.2, "Opening and Reopening Stream Items"). You then get a OMXF_Sink_type object from the item, and use the OMXF_Sink_type object as a handle for writing to the stream item (see Section 3.5.4.3, "Writing Stream Data"). When you have finished writing, you close the stream item (see REF ID="ClosingandDiscardingStreams">).
To read from a closed stream item (previously attached to some data), you get a OMXF_Source_type object from the item, and use the OMXF_Source_type object as a handle for reading from the stream item (see Section 3.5.4.2, "Reading Stream Data").
To illustrate a complete cycle of writing data to a stream item and reading data back from a stream item, suppose that you want to convert C characters to OMXF_char (there are easier ways to convert the characters, see Section 3.4.5, "String Allocator Methods"). You can open one of the items in a MODIFIABLE stream argument, write C characters to the item, close the item, and read OMXF_char from the item:
OMXF_int status;
OMXF_int ItemIndex;
OMXF_String_type FileName;
OMXF_StreamShelf_type * p_StreamShelf;
OMXF_Sink_type * p_Sink;
OMXF_Source_type * p_Source;
OMXF_char OMXF_char_buffer[100];
char * C_string = "Some C characters"
OMXF_int DataLength;
OMXF_int LengthToRead;
/* open and attach to buffer (assume item is not already open!) */
status = OMXF_OpenStreamItemAsBuffer(p_StreamShelf, ItemIndex, NULL);
/* get an OMXF_Sink_type object */
p_Sink = OMXF_GetSinkFromStreamItem(p_StreamShelf, ItemIndex);
/* write C character data */
status = OMXF_Write_C_chars_ToSink(p_Sink, strlen(C_string), C_string);
/* close the stream item */
status = OMXF_CloseStreamItem(p_StreamShelf, ItemIndex);
/* get a source object */
p_Source = OMXF_GetSourceFromStreamItem(p_StreamShelf, ItemIndex);
/* see how many characters in buffer, read as many as we can */
DataLength = OMXF_GetInitialLengthOfSource(p_Source);
LengthToRead = (DataLength < sizeof(OMXF_char_buffer) ?
DataLength : sizeof(OMXF_char_buffer) );
/* read data from the source to OMXF_char_buffer*/
status = OMXF_ReadDataFromSource(p_Source, LengthToRead, OMXF_char_buffer);
After getting a OMXF_SwitchShelf_type object (see Section 3.5.2.1, "Getting the Shelf Object for an Argument") you can select an item (see Section 3.5.3.1, "Choosing a Shelf Item"), to read or modify, and you can add or remove items (see Section 3.5.3.2, "Inserting or Removing Shelf Items").
You use the OMXF_GetSwitchItemValue method to get a value from a counter item, and the OMXF_SetSwitchItemValue or OMXF_ToggleSwitchItemValue methods to change a value in a MODIFIABLE counter item:
OMXF_SwitchShelf_type * p_SwitchShelf;
OMXF_int SwitchItem;
OMXF_SwitchValue_type SwitchValue;
OMXF_int Status;
/* Get a switch value */
SwitchValue = OMXF_GetSwitchItemValue(p_Environment, SwitchItem);
/* Set a switch value */
SwitchValue = OMXF_sv_True;
Status = OMXF_SetSwitchItemValue(p_Environment, SwitchItem, SwitchValue);
/* Toggle a switch between OMXF_sv_True and OMXF_sv_False values */
Status = OMXF_ToggleSwitchItemValue(p_Environment, SwitchItem);
External functions can do all of the shelf operations that can be done in OmniMark, except that
Shelves are persistent objects that have existed prior to entering an external function, and that will remain in existence after leaving an external function. You should take care not to change the state of a shelf in ways that will be unexpected by the OmniMark code that invoked your external function. For example, you should not remove a shelf item (or insert a new item) if your external function will be invoked from OmniMark code that expects no changes in the number of items on a shelf.
You can
Before attempting to choose an item, you may want to know how many items are on the shelf. You can do this by using the OMXF_GetNumberOfShelfItems method for the shelf object:
OMXF_CounterShelf_type * p_CounterShelf;
OMXF_int NumberOfItems;
NumberOfItems = OMXF_GetNumberOfShelfItems(p_CounterShelf);
You can identify the default item by using the OMXF_GetDefaultItem method for the shelf object:
OMXF_StreamShelf_type * p_StreamShelf;
OMXF_int DefaultItem;
DefaultItem = OMXF_GetDefaultItem(p_StreamShelf);
Many of the API functions functions require the item number to select a particular item. If you have a key instead, you can get the item number by using the OMXF_GetItemNumberOfKey method for the shelf object:
OMXF_SwitchShelf_type * p_SwitchShelf;
OMXF_int ItemNumber;
OMXF_StringType * ShelfKey;
/* convert C string to temporary OmniMark string, for use as a key */
ShelfKey = OMXF_Copy_C_StringToTmp_OMXF_String(p_SwitchShelf, "mykeyvalue");
/* get the item number from the key */
ItemNumber = OMXF_GetItemNumberOfKey(p_SwitchShelf, ShelfKey);
When you add or remove an opaque shelf item, a corresponding opaque data object will be created (as the initial value of the item) or destroyed. This is done automatically by OmniMark, so the addition or removal of opaque shelf items is done in the same way as the addition or removal of counter, stream and switch items.
You can insert a new item as the sixth item on a MODIFIABLE shelf by inserting it after the fifth item, with the OMXF_InsertNewShelfItem method:
OMXF_int status;
OMXF_int AfterItemNumber;
OMXF_StreamShelf_type * p_StreamShelf;
AfterItemNumber = 5;
status = OMXF_InsertNewShelfItem(p_StreamShelf, AfterItemNumber);
To insert the first item on an empty shelf, or to insert ahead of the first item on a shelf that already has items, simply insert an item after the (nonexistent) zero item:
OMXF_int status;
OMXF_SwitchShelf_type * p_SwitchShelf;
status = OMXF_InsertNewShelfItem(p_SwitchShelf, 0);
You can remove a single item from a MODIFIABLE shelf with the OMXF_RemoveShelfItem method:
OMXF_int status;
OMXF_int ItemToRemove;
OMXF_CounterShelf_type * p_CounterShelf;
ItemToRemove = 5;
status = OMXF_RemoveShelfItem(p_CounterShelf, ItemToRemove);
You can remove all items from a MODIFIABLE shelf with the OMXF_ClearShelf method:
OMXF_int status;
OMXF_StreamShelf_type * p_StreamShelf;
status = OMXF_ClearShelf(p_StreamShelf);
You can remove a key from a item on a MODIFIABLE shelf with the OMXF_RemoveKeyOfShelfItem method:
OMXF_int status;
OMXF_int ShelfItemToRemoveKeyFrom;
OMXF_SwitchShelf_type * p_SwitchShelf;
ShelfItemToRemoveKeyFrom = 4;
status = OMXF_RemoveKeyOfShelfItem(p_SwitchShelf, ShelfItemToRemoveKeyFrom);
You can set the key of an item on a MODIFIABLE shelf with the OMXF_SetKeyOfShelfItem method:
OMXF_int status;
OMXF_SwitchShelf_type * p_SwitchShelf;
OMXF_int ShelfItemIndex;
OMXF_StringType * ShelfKey;
/* convert C string to temporary OmniMark string, for use as a key */
ShelfKey = OMXF_Copy_C_StringToTmp_OMXF_String(p_SwitchShelf, "newkeyvalue");
/* set the key of the desired item to the new key */
ShelfItemIndex = 2;
status = OMXF_SetKeyOfShelfItem(p_SwitchShelf, ShelfItemIndex, ShelfKey);
You can obtain the key of an item on a shelf with the OMXF_GetKeyOfShelfItem method:
OMXF_Source_type KeySource;
OMXF_int KeyLength;
OMXF_char *p_ItemKey;
OMXF_int status;
OMXF_CounterShelf_type * p_CounterShelf;
OMXF_int ShelfItemIndex;
OMXF_StringType * ShelfKey;
/* get a source that holds the key of the desired item */
ShelfItemIndex = 3;
KeySource = OMXF_GetKeyOfShelfItem(p_CounterShelf, ShelfItemIndex);
/* Find out how big the key is */
Keylength = OMXF_GetInitialLengthOfSource(KeySource);
/* allocate space for the key, and copy the key from the source */
p_ItemKey = malloc(KeyLength * sizeof(OMXF_char));
status = OMXF_ReadDataFromSource(KeySource, KeyLength, p_ItemKey);
/* free source, instead of leaving it allocated until function exits */
OMXF_FreeSource(KeySource);
To remove an item when you have the key for that item, you first get the item number, and then remove the item:
OMXF_SwitchShelf_type * p_SwitchShelf;
OMXF_int ItemNumber;
OMXF_StringType * ShelfKey;
/* convert C string to temporary OmniMark string, for use as a key */
ShelfKey = OMXF_Copy_C_StringToTmp_OMXF_String(p_SwitchShelf, "mykeyvalue");
/* get the item number from the key */
ItemNumber = OMXF_GetItemNumberOfKey(p_SwitchShelf, ShelfKey);
/* Remove the item */
status = OMXF_RemoveShelfItem(p_CounterShelf, ItemNumber);
To insert a keyed item, you first insert the item, and then set the key:
OMXF_int status;
OMXF_int ItemToInsertAfter;
OMXF_CounterShelf_type * p_CounterShelf;
OMXF_int ShelfItemIndex;
OMXF_StringType * ShelfKey;
/* Insert the new item */
ItemToInsertAfter = 3;
status = OMXF_InsertNewShelfItem(p_CounterShelf, ItemToInsertAfter);
/* convert C string to temporary OmniMark string, for use as a key */
ShelfKey = OMXF_Copy_C_StringToTmp_OMXF_String(p_CounterShelf, "newkeyvalue");
/* set the key of the desired item to the new key */
ShelfItemIndex = 2;
status = OMXF_SetKeyOfShelfItem(p_CounterShelf, ShelfItemIndex, ShelfKey);
An OmniMark stream item is not, like a counter item or a switch item, simply a place to put a single value, or a small amount of data. Instead, the stream item can accept or supply data on an incremental basis, possibly in very large amounts. Consequently, you access stream data by using an object that behaves somewhat like a file stream handle in C. The object is a target for external function API operators that
The objects used to access stream data are different from C file stream handles:
Stream items are persistent objects that may have existed prior to entering an external function, and that can remain in existence after leaving an external function. You should take care not to change the state of a stream item in ways that will be unexpected by the OmniMark code that invoked your external function. For example, you should not close a stream item from an external function, when OmniMark code has invoked the external function after opening the stream item and will expect the stream item to still be open when the stream returns.
You can choose to open a stream item with a set of default options, or you can create a OMXF_StreamOpenOptionBlock_type object with the OMXF_AllocateStreamOpenOptionBlock method:
OMXF_StreamShelf_type * p_Stream;
OMXF_StreamOpenOptionBlock_type * StreamOpenOptionBlock;
StreamOpenOptionBlock = OMXF_AllocateStreamOpenOptionBlock(p_Stream);
After allocating the OMXF_StreamOpenOptionBlock_type object, you can invoke the methods in that object to configure a stream to the state that you want when you open the stream item. For example, you may want the stream to be opened as a domain free stream item:
OMXF_StreamOpenOptionBlock_type * StreamOpenOptionBlock;
OMXF_SetDomainFree(StreamOpenOptionBlock);
A stream item can be opened or reopened with the default options by using a NULL pointer for the OMXF_StreamOpenOptionBlock_type argument in the methods OMXF_OpenStreamItemAsBuffer, OMXF_OpenStreamItemAsFile and OMXF_ReopenStreamItem:
OMXF_int status;
OMXF_int ItemIndex;
OMXF_String_type FileName;
OMXF_StreamShelf_type * p_Stream;
/* open and attach to buffer */
status = OMXF_OpenStreamItemAsBuffer(p_Stream, ItemIndex, NULL);
/* open and attach to file named "myfile" */
FileName = OMXF_Copy_C_StringToTmp_OMXF_String(p_Stream, "myfile");
status = OMXF_OpenStreamItemAsFile(p_Stream, ItemIndex, FileName, NULL);
/* reopen with previous attachment */
status = OMXF_ReopenStreamItem(p_Stream, ItemIndex, NULL);
A stream item can be opened or reopened with the options you want by creating and initializing a OMXF_StreamOpenOptionBlock_type object and using it as an argument in the methods OMXF_OpenStreamItemAsBuffer, OMXF_OpenStreamItemAsFile and OMXF_ReopenStreamItem:
OMXF_int status;
OMXF_int ItemIndex;
OMXF_String_type FileName;
OMXF_StreamShelf_type * p_Stream;
OMXF_StreamOpenOptionBlock_type * StreamOpenOptionBlock;
/* create the option block and modify the options as required */
StreamOpenOptionBlock = OMXF_AllocateStreamOpenOptionBlock(p_Stream);
OMXF_SetDomainFree(StreamOpenOptionBlock);
/* open and attach to buffer */
status = OMXF_OpenStreamItemAsBuffer(p_Stream, ItemIndex,
StreamOpenOptionBlock);
/* open and attach to file named "myfile" */
FileName = OMXF_Copy_C_StringToTmp_OMXF_String(p_Stream, "myfile");
status = OMXF_OpenStreamItemAsFile(p_Stream, ItemIndex, FileName,
StreamOpenOptionBlock);
/* reopen with previous attachment */
status = OMXF_ReopenStreamItem(p_Stream, ItemIndex,
StreamOpenOptionBlock);
You cannot open a stream item until you have freed all the OMXF_Source_type objects that are associated with that stream item (see Section 3.5.4.2, "Reading Stream Data").
You can close and discard stream items in external functions. However, a stream item is a persistent object that may have existed prior to entering an external function, and may continue to exist after an exit from the external function. You should therefore take some care not to close or discard a stream before returning to OmniMark code that expects the stream to remain open, or to at least remain in existence. You should not try to open a stream item that has already been opened in OmniMark code, and should not leave a stream open when returning to OmniMark code that expects the stream to be closed.
You can close and discard a stream item with the OMXF_DiscardStreamItem and OMXF_CloseStreamItem methods:
OMXF_int status;
OMXF_int ItemIndex;
OMXF_StreamShelf_type * p_Stream;
/* close a stream item */
status = OMXF_CloseStreamItem(p_Stream, ItemIndex);
/* discard a stream item */
status = OMXF_DiscardStreamItem(p_Stream, ItemIndex);
You cannot close a stream item until you have freed all the OMXF_Sink_type objects that are associated with that stream item (see Section 3.5.4.3, "Writing Stream Data").
You can read data from a stream item that is currently closed, if the stream item is attached to a source of data, such as a file or a buffer.
To read data from a stream item you first get a OMXF_Source_type object, and then use the OMXF_ReadDataFromSource method to read from the source. When you have finished reading, you can invoke the OMXF_FreeSource method to free the OMXF_Source_type object, or you can let OmniMark free the object when the external function exits.
OMXF_int status;
OMXF_StreamShelf_type * p_StreamShelf;
OMXF_int ItemIndex;
OMXF_Source_type * p_Source;
OMXF_char OMXF_char_buffer[100];
/* get a source object */
p_Source = OMXF_GetSourceFromStreamItem(p_StreamShelf, ItemIndex);
/* read data from the source to the buffer */
status = OMXF_ReadDataFromSource(p_Source,
sizeof(OMXF_char_buffer)/sizeof(OMXF_char), OMXF_char_buffer);
/* free source, instead of leaving it allocated until function exits */
OMXF_FreeSource(p_Source);
You can find out if a source object holds any more data with the OMXF_SourceAtEnd method:
OMXF_int Status;
OMXF_Source_type * p_Source
Status = OMXF_SourceAtEnd(p_Source);
if (Status == 0)
/* source contains more data */
else
/* source has no more data */
You can retrieve the initial length of a source with the OMXF_GetInitialLengthOfSource method, if the source is attached to something (like a buffer) in which the length of data can be determined prior to reading any of the data. If you get a zero length, you need to check the error indicator to see whether the zero occurs because of an error, or only because of the absence of any data:
OMXF_int Length;
OMXF_Source_type * p_Source
OMXF_FailureIndicator_type result;
Length = OMXF_GetInitialLengthOfSource(p_Source);
if (Length == 0) {
result = OMXF_GetFailureIndicator(p_Source);
if (result == OMXF_fi_LengthNotAvailable)
/* length of data reported as zero because no length is available */
}
Before you can open a closed stream item, you must free all the OMXF_Source_type objects that are associated with that stream item.
You can write data to a stream item that is currently open.
To write data to a stream item you first get a OMXF_Sink_type object, and then use the OMXF_Write_C_chars_ToSink method to write C characters or the OMXF_Write_OMXF_chars_ToSink method to write OmniMark characters. When you have finished writing, you can invoke the OMXF_FreeSource method to free the OMXF_Source_type object, or you can let OmniMark free the object when the external function exits.
OMXF_int status;
OMXF_StreamShelf_type * p_StreamShelf;
OMXF_int ItemIndex;
OMXF_Sink_type * p_Sink;
/* assume each buffer is full */
OMXF_char OMXF_char_buffer[100];
char C_char_buffer=[100];
/* get a sink object */
p_Sink = OMXF_GetSinkFromStreamItem(p_StreamShelf, ItemIndex);
/* write C character data */
status = OMXF_Write_C_chars_ToSink(p_Sink,
sizeof(C_char_buffer)/sizeof(char), C_char_buffer);
/* write OmniMark character data */
status = OMXF_Write_OMXF_chars_ToSink(p_Sink,
sizeof(OMXF_char_buffer)/sizeof(OMF(char), OMXF_char_buffer);
/* free sink, instead of letting OmniMark free it when function exits */
so that we can close the stream item */
OMXF_FreeSink(p_Sink);
Before you can close a open stream item, you must free all the OMXF_Sink_type objects that are associated with that stream item.
You can get information about a stream item with several different methods in the API:
Opaque values have an OmniMark component and a user-defined component. The user defined component (which is completely unknown to OmniMark) contains the actual data. Since OmniMark does not know what is in the user-defined component, any changes to the data of an existing opaque value are made by code in a user-written external function.
An opaque data item holds a pointer to a OMXF_OpaqueHeader_type object, which is the OmniMark component of an opaque value. Since the header is the first part of the object, the header pointer can also be used as a pointer to the object. The opaque value is defined as a C struct:
typedef MyOpaqueData_type;
struct MyOpaqueData_type {
OMXF_OpaqueHeader_type header;
/* user-defined data area */
};
You can use the OMXF_GetOpaqueItemReference method to get an an opaque data pointer from an opaque shelf item, and use the pointer to access the user-defined data:
OMXF_OpaqueShelf_type * MyOpaqueShelf;
MyOpaqueData_type * MyOpaqueData;
OMXF_int ShelfItemNumber;
OMXF_int Status;
MyOpaqueData = (MyOpaqueData_type*) OMXF_GetOpaqueItemReference
(MyOpaqueShelf, ShelfItemNumber)
You can replace one opaque value by another in an opaque item. The OMXF_SetOpaqueItem method will free the current opaque value, and insert a new pointer into an opaque shelf item:
OMXF_OpaqueShelf_type * MyOpaqueShelf;
MyOpaqueData_type * MyNewOpaqueData;
OMXF_int ShelfItemNumber;
OMXF_int Status;
/* allocate and initialize a new MyOpaqueDataType object, with pointer
MyNewOpaqueData */
/* now replace the old opaque object by the new one */
Status = OMXF_SetOpaqueItem(MyOpaqueShelf, ShelfItemNumber,
(OMXF_OpaqueShelf_type*) MyNewOpaqueData)
Every opaque value has a copy subfunction and a release subfunction. These subfunctions can be different for different instances of the opaque data object. If the subfunctions differ between instances of an opaque data object, you will use the OMXF_GetOpaqueCopySubfunction method to get the appropriate copy subfunction from the opaque value, and the OMXF_GetOpaqueReleaseSubfunction method to get the appropriate copy subfunction. Once you have these subfunctions, you can copy or release the opaque value. When you copy an opaque value, you need to initialize the header of the new object with the copy and release subfunctions of the original object.
The following example illustrates how to copy and release opaque values. Note that all of the (possibly complex) work is in the opaque data function, hidden behind the copy and release subfunctions.
/* Assume that p_Environment points to the environment received by the
external function. We will typecast it to (OMXF_SubEnvironment_type*).
We typecast the opaque value pointers to (OMXF_OpaqueHeader_type*)
when we invoke API methods that require an Opaque Header.
*/
MyOpaqueData_type *p_OpaqueObjectA; /* existing object */
MyOpaqueData_type *p_OpaqueObjectB; /* a new object */
OMXF_OpaqueHeader_type *(*p_OpaqueCopyFunction)
(OMXF_SubEnvironment_type* p_SubEnvironment,
OMXF_OpaqueHeader_type* p_Obj);
void (*p_OpaqueReleaseFunction)
(OMXF_SubEnvironment_type* p_SubEnvironment,
OMXF_OpaqueHeader_type* p_Obj);
/* get the copy and release subfunctions from the original object */
p_OpaqueCopyFunction = OMXF_GetOpaqueCopySubfunction
((OMXF_SubEnvironment_type*) p_Environment,
(OMXF_OpaqueHeader_type*) p_OpaqueObjectA);
p_OpaqueReleaseFunction = OMXF_GetOpaqueReleaseSubfunction
((OMXF_SubEnvironment_type*) p_Environment,
(OMXF_OpaqueHeader_type*) p_OpaqueObjectA);
/* create a new object, and copy data from the original object*/
p_OpaqueObjectB = (MyOpaqueData_type*) p_OpaqueCopyFunction
( (OMXF_SubEnvironment_type*) p_Environment,
(OMXF_OpaqueHeader_type*) p_OpaqueDataObjectA
);
/* Release the original object */
p_OpaqueReleaseFunction((OMXF_SubEnvironment_type*) p_Environment,
(OMXF_OpaqueHeader_type*) p_OpaqueDataObjectA );
External counter functions return a counter item to OmniMark when invoked by OmniMark code. You can use OMXF_SetCounterReturnValue any number of times to set the return value of a counter function. OmniMark will report an error if you return without setting a return value.
The following code will add the values of all the items in a MODIFIABLE counter shelf, insert the total as the value of a new first item on the shelf, and also return the total as the return value of the function.
/*
* DEFINE EXTERNAL COUNTER FUNCTION counter-test (MODIFIABLE COUNTER value)
* AS "counter_test"
* IN FUNCTION-LIBRARY "xfndemos"
*
* sum all the counters on the shelf, report the total, and insert the new
* total as the first counter on the shelf.
*/
void counter_test
(OMXF_Environment_type *p_Env)
{
OMXF_int itemcount;
OMXF_int total;
OMXF_int item;
OMXF_CounterShelf_type *p_Value;
total = 0;
itemcount = 0;
/* shelf is first argument */
p_Value = OMXF_GetCounterArgumentByPosition(p_Env, 1);
if (p_Value) { /* shelf exists, as an argument of the function */
/* total the counters on the shelf */
itemcount = OMXF_GetNumberOfShelfItems(p_Value);
for (item = itemcount; item>0 ; item--) {
total += OMXF_GetCounterItemValue(p_Value, item);
}
/* try to insert the total as a new leading item on the shelf */
if ( OMXF_InsertNewShelfItem(p_Value,0))
OMXF_SetCounterItemValue(p_Value,1,total);
}
/* set the function return value */
OMXF_SetCounterReturnValue(p_Env, total);
}
Opaque data functions return an opaque item to OmniMark when invoked by OmniMark code. You can use the OMXF_SetOpaqueReturnValue method any number of times to set the return value of an opaque function. OmniMark will report an error if you return without setting a return value.
Unlike functions that return counters, switches, and streams, an opaque data function returns a pointer to an opaque value, instead of returning the actual value. This means that you must take some precautions that are not necessary with counter switch and stream functions:
In general, the pointer that you set with the OMXF_SetOpaqueReturnValue method must point to an opaque value the was created within the opaque data function, either by creating the opaque value, or by copying the opaque value from an opaque shelf item (see Section 3.5.2, "Shelf Access" and Section 3.5.5, "Managing Opaque Data Items"). You loose ownership of the pointer and the associated opaque value as soon as you invoke the OMXF_SetOpaqueReturnValue method to give the pointer to OmniMark.
The following example illustrates how an opaque data item might be set by an opaque data function that takes a stream shelf as an argument.
Note that this example does not show any error checking, although you should check for error after each step (see Section 3.4.3, "Failure Indicators and Exceptions" and the xfoutput example).
/*
* DEFINE EXTERNAL FloatVar FUNCTION SetFloat
* (READ-ONLY STREAM FloatVal)
* AS "SetFloat"
* IN FUNCTION-LIBRARY "xfopaque"
*/
/* The opaque data value */
typedef struct FloatVar_type FloatVar_type;
struct FloatVar_type
{
OMXF_OpaqueHeader_type Header; /* required by API */
double floatnum; /* user-defined */
};
/* Set a FloatVar from a string representation of the value */
void
SetFloat (OMXF_Environment_type *p_Env)
{
OMXF_StreamShelf_type *p_stream; /* Stream Shelf Argument */
OMXF_int DefaultItem; /* default stream item */
OMXF_Source_type *p_Source; /* source attached to stream */
FloatVar_type *p_FloatVar; /* Function return value */
char buffer[128]; /* Temporary C string */
OMXF_int BufferStringLength;
OMXF_int fi; /* Failure Indicator */
OMXF_int status;
/* Get a pointer to the stream shelf argument */
p_stream = OMXF_GetStreamArgumentByPosition (p_Env, 1);
/* Find the default item on the stream shelf */
DefaultItem = OMXF_GetDefaultItem(p_stream);
/* Get the data source that should be attached to the default item */
p_Source = OMXF_GetSourceFromStreamItem(p_stream,DefaultItem);
/* Copy data from the default item to a C string */
BufferStringLength = OMXF_ReadDataFromSource(p_Source,
sizeof(buffer)-1,buffer);
buffer[BufferStringLength]='\0';
/* Create a FloatVar to use as the function return value */
p_FloatVar = malloc(sizeof(struct FloatVar_type));
/* Initialize the OmniMark control header. Otherwise, OmniMark
will crash on the first reference to the new opaque item. */
status = OMXF_InitializeOpaqueHeader
( p_Env, & p_FloatVar->Header, OMXF_API_VersionNumber,
FloatVarCopy, FloatVarRelease );
/* Set the user data to the desired values */
p_FloatVar->floatnum = atof(buffer);
/* Pass the new FloatVar as a return value to OmniMark. */
OMXF_SetOpaqueReturnValue (p_Env, & p_FloatVar->Header);
}
External switch functions return a switch item to OmniMark when invoked by OmniMark code. You can use OMXF_SetSwitchReturnValue any number of times to set the return value of a switch function. OmniMark will report an error if you return without setting a return value.
The following code will check all the items in a MODIFIABLE switch shelf, returning true for even parity and false for odd parity, and will also insert the return value as the value of a new first item on the shelf:
/*
* DEFINE EXTERNAL SWITCH FUNCTION switch-test (MODIFIABLE SWITCH value)
* AS "switch_test"
* IN FUNCTION-LIBRARY "xfndemos"
*
* Report the parity of a shelf of switches. Return true for even parity and
* false for odd parity. Insert the result at the beginning of the shelf.
*/
void switch_test
(OMXF_Environment_type *p_Env)
{
OMXF_int itemcount;
int result;
OMXF_int item;
OMXF_SwitchShelf_type *p_Value;
result = 1;
itemcount = 0;
/* shelf is first argument */
p_Value = OMXF_GetSwitchArgumentByPosition(p_Env, 1);
if (p_Value) { /* shelf exists, as an argument of the function */
/* get the parity of all the switches, taken together */
itemcount = OMXF_GetNumberOfShelfItems(p_Value);
for (item = itemcount; item>0 ; item--) {
if (OMXF_GetSwitchItemValue(p_Value, item)
==OMXF_sv_True) {
result = ( result ? 0 : 1 ); /* toggle */
}
}
/* try to insert the result as a new leading item on the shelf */
if ( OMXF_InsertNewShelfItem(p_Value,0))
OMXF_SetSwitchItemValue(p_Value,1,
(result ? OMXF_sv_True : OMXF_sv_False ) );
}
/* set the function return value */
OMXF_SetSwitchReturnValue(p_Env,
(result ? OMXF_sv_True : OMXF_sv_False ));
}
External stream functions return a stream item to OmniMark when invoked by OmniMark code. In contrast to external counter and switch functions, you write character data incrementally to the stream return value, instead of writing the entire value at once. You can write C characters with the OMXF_Write_C_chars_ToStreamReturnValue method or OmniMark characters (OMXF_char) with the OMXF_Write_OMXF_chars_ToStreamReturnValue method, and you can alternate between C characters and OmniMark characters if you wish. If you decide that you want to begin writing a different return value, you can remove all of the previously written data with the OMXF_DiscardReturnValue method.
The following code will concatenate the strings in all of the items of a MODIFIABLE stream shelf into a new string, and return the concatenated string as the return value of the function. The code also constructs a short description of the original shelf, and inserts the description as the string value in a new first item on the shelf.
/*
* DEFINE EXTERNAL STREAM FUNCTION stream-test (MODIFIABLE STREAM value)
* AS "stream_test"
* IN FUNCTION-LIBRARY "xfndemos"
*
* count the streams on the shelf, report the total, and insert the new
* total as the first stream on the shelf.
* Ignores any stream that is open or not attached.
*/
void stream_test
(OMXF_Environment_type *p_Env)
{
OMXF_int itemcount;
OMXF_int item;
OMXF_char p_buffer[p_buflen];
char buffer[buflen];
OMXF_int length;
OMXF_StreamShelf_type *p_stream;
OMXF_Source_type *p_Source;
OMXF_Sink_type *p_Sink;
p_buffer[0] = '\0';
itemcount = 0;
/* shelf is first argument */
p_stream = OMXF_GetStreamArgumentByPosition(p_Env, 1);
/* Set return value to an empty string */
OMXF_Write_C_chars_ToStreamReturnValue(p_Env, 0, "");
if (p_stream) {
/* shelf exists, as an argument of the function */
itemcount = OMXF_GetNumberOfShelfItems(p_stream);
for (item = 1; item<=itemcount ; item++) {
/* concatenate the string to the return value */
p_Source = OMXF_GetSourceFromStreamItem(p_stream,item);
if ( ! p_Source )
continue;
while ((length = OMXF_ReadDataFromSource(
p_Source,p_buflen,p_buffer)
))
OMXF_Write_OMXF_chars_ToStreamReturnValue(
p_Env, length, p_buffer);
}
/* try to insert the stream as a new leading item on the shelf */
sprintf(buffer, "%d stream items input", (int) itemcount);
if ( OMXF_InsertNewShelfItem(p_stream, 0)) {
if ( OMXF_OpenStreamItemAsBuffer(p_stream, 1, NULL ) ) {
p_Sink = OMXF_GetSinkFromStreamItem(p_stream,1);
if (p_Sink) {
OMXF_Write_C_chars_ToSink(
p_Sink,strlen(buffer),buffer);
}
OMXF_FreeSink(p_Sink);
OMXF_CloseStreamItem(p_stream, 1);
}
} /* if ( OMXF_InsertNewShelfItem(p_stream, 0) */
} /* if (p_stream) */
/* function return value has been set above */
}
Some external functions do not return any value, but they can still modify one or more MOFIFIABLE shelf arguments, and they can still write to the OmniMark current output.
The following code returns no value, but does list the values in the items of a stream shelf:
/*
* DEFINE EXTERNAL FUNCTION void-test (MODIFIABLE STREAM value)
* AS "void_test"
* IN FUNCTION-LIBRARY "xfndemos"
*
* Send the value in each stream, one line each, to current output.
* Ignores any stream that is open or not attached.
*/
void void_test
(OMXF_Environment_type *p_Env)
{
OMXF_int itemcount;
OMXF_int item;
OMXF_char p_buffer[p_buflen];
char buffer[buflen];
OMXF_int length;
OMXF_StreamShelf_type *p_stream;
OMXF_Source_type *p_Source;
p_buffer[0] = '\0';
itemcount = 0;
/* shelf is first argument */
p_stream = OMXF_GetStreamArgumentByPosition(p_Env, 1);
if (p_stream) {
/* shelf exists, as an argument of the function */
itemcount = OMXF_GetNumberOfShelfItems(p_stream);
for (item = 1; item<=itemcount ; item++) {
/* send each value to a separate line in current output */
p_Source = OMXF_GetSourceFromStreamItem(p_stream,item);
if ( ! p_Source )
continue;
sprintf(buffer, "stream %d =", (int) item);
OMXF_Write_C_chars_ToCurrentOutput(
p_Env, strlen(buffer), buffer);
while ((length = OMXF_ReadDataFromSource(
p_Source,p_buflen,p_buffer)
))
OMXF_Write_OMXF_chars_ToCurrentOutput(
p_Env, length, p_buffer);
sprintf(buffer, "\n");
OMXF_Write_C_chars_ToCurrentOutput(
p_Env, strlen(buffer), buffer);
}
}
/* function does not return a value */
}
External source functions provide a data source that can be read incrementally (See Section 2.3.2, "External Source").
External sources have one main function (the external source function that is declared in OmniMark), and two subfunctions (declared only in the external function library):
OmniMark waits for the completion of a request for incremental data. If the external source returns zero characters, OmniMark takes that as an indication that the source has been exhausted, and invokes the OMXF_Terminate_fun subfunction. Consequently, if the source is designed to read data that arrives sporadically, the OMXF_Read_fun subfunction must not return until at least one character can be returned to OmniMark.
The external source function
The external source function is invoked before the first attempt to read from the corresponding external source. It will not be invoked on any subsequent attempt to read, until after OmniMark has invoked the Terminate subfunction.
The OMXF_Read_fun subfunction is invoked upon every attempt to read an external source, and receives the following arguments:
When the OMXF_Read_fun subfunction returns with zero characters, OmniMark assumes that all input operations are complete and invokes the OMXF_Terminate_fun subfunction.
A typical OMXF_Read_fun subfunction declaration would be:
static void
Read_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
OMXF_int p_AmountOfDataToRead,
char * p_InputDataBuffer,
OMXF_int * p_AmountThatWasRead);
The OMXF_Read_fun subfunction reads the requested number of characters, or some lesser amount, into the buffer, and reports the number of characters that were actually read.
The OMXF_Terminate_fun subfunction is invoked after the OMXF_Read_fun subfunction returns to OmniMark without any data. OmniMark takes this as an indication no more data can be expected, and terminates the external source. The OMXF_Terminate_fun subfunction receives the following arguments:
The OMXF_Terminate_fun subfunction deallocates any resources attached to the external source, and returns to OmniMark.
A typical OMXF_Terminate_fun subfunction declaration would be:
static void
Terminate_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo);
Suppose that you want to read a file using an external source, instead of reading the file directly from OmniMark (you might do this if you wanted to exploit special non-portable features of a file system on some platforms). Note that this example does not show any error checking, although you should check for error after each step (see Section 3.4.3, "Failure Indicators and Exceptions" and the xfsource example).
/*
* DEFINE EXTERNAL SOURCE FUNCTION external-file-read(VALUE STREAM filename)
* AS "ExternalFileRead"
* IN FUNCTION-LIBRARY "extfile"
*/
/* User-defined information, structure will be allocated by malloc() */
typedef struct UserInfo_t UserInfo_t;
struct UserInfo_t {
/* File information */
FILE * InputFile;
size_t InputFileNameSize;
char * InputFileName;
};
/* Read function, invoked for each incremental read */
static void
Read_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
OMXF_int p_AmountOfDataToRead,
char * p_InputDataBuffer,
OMXF_int * p_AmountThatWasRead)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
*p_AmountThatWasRead = fread(p_InputDataBuffer, sizeof(char),
AmountOfDataToRead, userinfo->InputFile);
}
/* Terminate function, invoked after all read operations are completed */
static void
Terminate_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo);
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
int status;
status = fclose(userinfo->InputFile); /* opened by ExternalFileRead() */
free(userinfo->InputFileName);
free(p_UserInfo);
}
/* The external source function is invoked just prior to the first attempt
to read from the external source, and allocates any resources that will
be required during the lifetime (or scope) of the external source */
void ExternalFileRead(OMXF_Environment_type *p_Env)
{
OMXF_StreamShelf_type *p_stream;
OMXF_ExternalSource_type *p_ExtSource;
UserInfo_t *userinfo;
OMXF_int status;
/* Ask OmniMark to create the external source object */
p_ExtSource = OMXF_CreateExternalSource (p_Env);
/* shelf is first argument */
p_stream = OMXF_GetStreamArgumentByPosition(p_Env, 1);
/* allocate a user data area for this instance of the function */
userinfo = malloc(sizeof(UserInfo_t));
/* copy argument value to user data area for use as a file name */
userinfo->InputFileNameSize = OMXF_GetInitialLengthOfSource(p_Source);
userinfo->InputFileName =
malloc(sizeof(char) * (1 + userinfo->InputFileNameSize));
FileName = malloc(sizeof(OMXF_char) * (1 + userinfo->InputFileNameSize));
length = OMXF_ReadDataFromSource(p_Source,
userinfo->InputFileNameSize,
FileName);
/* convert from OMXF_char to char */
for ( i = 0 ; i < length ; i++ )
{
userinfo->InputFileName[i] = (char) FileName[i];
}
free(FileName);
userinfo->InputFileName[length] = '\0';
/* Establish access to the data */
userinfo->InputFile = fopen(userinfo->InputFileName,"rb");
/* Install the user-defined subfunctions into the external source
object. Don't forget to set the User Info pointer! */
status = OMXF_SetUserInfoOfExternalSource (p_ExtSource, userinfo);
status = OMXF_SetTerminateFunOfExternalSource (p_ExtSource, Terminate_fun);
status = OMXF_SetReadFunOfExternalSource (p_ExtSource, Read_fun);
/* Set a pointer to the new external source object */
status = OMXF_SetExternalSourceReturnValue (p_Env, p_ExtSource);
}
External output functions provide a data destination that can be written to incrementally (See Section 2.3.1, "External Output").
External outputs have one main function (the external output function that is declared in OmniMark), and eight subfunctions (declared only in the external function library):
The external output function
The external output function is invoked when an OPEN or REOPEN action attaches a stream to an external output:
open mystream as myexternaloutput ;invokes external output function
put mystream "some data%n"
close mystream
reopen mystream as myexternaloutput ;invokes external output function
put mystream "some more data%n"
close mystream
A dynamic REOPEN action (reusing a previous stream attachment to an external output) does not invoke the external output function again:
;invoke the external function, setting up all the subfunctions
open mystream as myexternaloutput
;write some data and close the stream
put mystream "some data%n"
close mystream
;reuse the original attachment without invoking the external function again
reopen mystream
;write some data and close the stream
put mystream "some more data%n"
close mystream
An external output function may be invoked more than once, if permitted by the design of your external output. Each invocation of an external output function will be followed by the invocation of the Terminate subfunction (see Section 3.12.6, "External Output Terminate Subfunction") prior to the next invocation of the external output function.
You can get the options that were used to open the stream item attached to an external output from an OMXF_ExternalOutputOptions_type object. The object is only available during the execution of the external output function. If your subfunctions will need any of the information, you must save the information to your user info object.
You get the OMXF_ExternalOutputOptions_type object with the OMXF_GetExternalOutputOptions method, and then use the OMXF_GetAppendingOptionValue and OMXF_GetBinaryOrderingValue methods to get information from the object:
/* Your user info object */
typedef struct UserInfo_t UserInfo_t;
struct UserInfo_t {
OMXF_int AppendingOptionValue;
OMXF_int BinaryOrderingValue;
/* Any other data your subfunctions will need */
};
/* p_Environment is the (OMXF_Environment_type *) argument to the external
output function */
OMXF_ExternalOutputOptions_type * p_ExternalOutputOptions;
UserInfo_t * UserInfo;
/* Code to get OMXF_ExternalOutput_type object, allocate UserInfo_t object
and install (UserInfo_t *) pointer in OMXF_ExternalOutput_type object */
/* Get the stream open options and save them for use by the subfunctions */
p_ExternalOutputOptions = OMXF_GetExternalOutputOptions(p_Environment);
UserInfo->AppendingOptionValue
= OMXF_GetAppendingOptionValue(p_ExternalOutputOptions);
UserInfo->BinaryOrderingValue
= OMXF_GetBinaryOrderingValue(p_ExternalOutputOptions);
The Appending Option Value is nonzero when the stream was opened in append mode (with a REOPEN action) or zero when the stream was opened not in append mode (with a OPEN action).
The Binary Ordering Value gives the default binary ordering flag that is associated with all OmniMark streams. The flag describes the default ordering of a four-byte group of data. Numbering the bytes in ascending order of the significance of their values, from 0 to 3, the appending option values are:
The default binary ordering will only be useful if you know that the entire stream consists entirely of two-byte groups, or entirely of four-byte groups. This may not be the case. Depending on your application, you may be uncertain of both the length and the alignment of ordered groups of bytes that represent binary data:
The Connect and Reconnect subfunctions are invoked to prepare an external output for writing.
The OMXF_Connect_fun subfunction is initiated by an OPEN action or by a REOPEN action that attaches a stream to the external output. You can tell whether the action was OPEN or REOPEN if you saved the appending option value (see Section 3.12.2, "External Output Stream Open Options") while the external output function was executing (see Section 3.12.1, "External Output Function"). You will need this information if you want to append data when the action was REOPEN, but overwrite previous data when the action was REOPEN.
The OMXF_Reconnect_fun subfunction is initiated by a dynamic REOPEN action (reusing a previous stream attachment to an external output). The Reconnect subfunction is never initiated by an OPEN action.
After a Connect or Reconnect, neither subfunction will be invoked again until after the Disconnect subfunction has been invoked (see Section 3.12.4, "External Output Disconnect Subfunction").
The actual invocation of Connect and Reconnect subfunctions is delayed until the first attempt to write to an external output or, if no write occurs, until a CLOSE action on the stream that is attached to the external output. The following example will illustrate the relationship and timing of OmniMark actions and the API functions:
;invoke the external output function, setting up all the subfunctions
open mystream as myexternaloutput ;connect pending
;write some data and close the stream
put mystream "some data%n" ;connect subfunction invoked
close mystream ;disconnect subfunction invoked
;reuse the original attachment without invoking the external output function
reopen mystream ;reconnect pending (old attachment)
;write some data and close the stream
put mystream "some more data%n" ;invoke reconnect subfunction (old attachment)
close mystream ;disconnect subfunction invoked
;invoke the external output function, setting up all the subfunctions again
reopen mystream as myexternaloutput ;connect pending (new attachment)
put mystream "still more data%n" ;connect subfunction invoked
close mystream ;disconnect subfunction invoked
A typical Connect subfunction declaration would be:
static void
Connect_fun(OMXF_SubEnvironment_type * p_SubEnvironment, void * p_UserInfo);
A typical Reconnect subfunction declaration would be:
static void
Reconnect_fun(OMXF_SubEnvironment_type * p_SubEnvironment, void * p_UserInfo);
The Disconnect subfunction is invoked to discontinue write operations, and to do anything that should be done to complete the write operations. The Disconnect subfunction can also release any resources that would not be required (or that could be reallocated) by a subsequent Connect or Reconnect.
The OMXF_Disconnect_fun subfunction is invoked when a CLOSE action occurs in OmniMark code or when a Terminate function (see Section 3.12.6, "External Output Terminate Subfunction") is about to be executed on an external output that is attached to a currently open stream
A typical Disconnect subfunction declaration would be:
static void
Disconnect(OMXF_SubEnvironment_type *p_SubEnvironment, void *p_UserInfo);
OmniMark streams can have a name attached to them. An OmniMark programmer can test if the name is available with the HAS NAME and HASNT NAME tests, and can get the name with the NAME OF operator. External outputs support these features with three subfunctions:
Typical declarations of the name functions look like this:
static void
HasName_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
OMXF_int * p_YesOrNo);
/* set *p_YesOrNo = 0 if no name, nonzero if there is a name */
static void
NameLength_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
OMXF_int * p_NameLength);
/* set *p_NameLength to number of characters in name */
static void
NameOf_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
OMXF_char * p_NameDataBuffer,
OMXF_int MaximumDataBufferSize,
OMXF_int * p_AmountWrittenToDataBuffer);
/* copy name to p_NameDataBuffer as OMXF_char */
/* set *p_AmountWrittenToDataBuffer to amount of data actually written */
The Terminate subfunction is invoked when OmniMark recognizes that the attachment of a particular stream to an external output will not be used again. The Terminate subfunction should release all resources (like memory) that are still held by the external output.
The OMXF_Terminate_fun subfunction is invoked when an external output is attached to a stream item that is about to go out of scope, or when an OPEN or REOPEN action is done on a stream item that is attached to an external output. A locally declared stream item goes out of scope when OmniMark execution leaves the block of code in which it was declared. A globally declared stream item goes out of scope when OmniMark exits, or when the stream item is removed from a shelf. If the external output is attached to an open stream item, the Disconnect subfunction (see Section 3.12.4, "External Output Disconnect Subfunction") will be invoked before the Terminate subfunction.
A typical declaration for a Terminate subfunction is:
static void
Terminate_fun(OMXF_SubEnvironment_type * p_SubEnvironment, void * p_UserInfo);
The Write subfunction receives a buffer and the number of C characters in the buffer, and is expected to send those C characters to the destination represented by the external output.
The OMXF_Write_fun subfunction is invoked when a PUT or SET action writes data to a stream item that is attached to an external output.
A typical declaration for the Write subfunction is:
static void
Write_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
char * p_OutputDataBuffer,
OMXF_int AmountInDataBuffer);
Suppose that you want to write a file using an external output, instead of writing the file directly from OmniMark (you might do this if you wanted to exploit special non-portable features of a file system on some platforms). Note that this example does not show any error checking, although you should check for error after each step (see Section 3.4.3, "Failure Indicators and Exceptions" and the xfoutput example).
/*
* DEFINE EXTERNAL OUTPUT FUNCTION external-file-write(VALUE STREAM filename)
* AS "ExternalFileWrite"
* IN FUNCTION-LIBRARY "extfile"
*/
/* User-defined information, structure will be allocated by malloc() */
typedef struct UserInfo_t UserInfo_t;
struct UserInfo_t {
FILE * OutputFile;
size_t OutputFileNameSize;
char * OutputFileName;
};
/* Subfunctions */
static void
Connect_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
userinfo->OutputFile = fopen(userinfo->OutputFileName,"wb");
}
static void
Disconnect_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
int status;
status = fclose(userinfo->OutputFile);
}
static void
HasName_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
OMXF_int * p_YesOrNo)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
*p_YesOrNo = -1; /* has a name (nonzero means yes) */
}
static void
NameLength_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
OMXF_int * p_NameLength)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
*p_NameLength = userinfo->OutputFileNameSize;
}
static void
NameOf_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
OMXF_char * p_NameDataBuffer,
OMXF_int MaximumDataBufferSize,
OMXF_int * p_AmountWrittenToDataBuffer)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
*p_AmountWrittenToDataBuffer =
(userinfo->OutputFileNameSize > MaximumDataBufferSize
? MaximumDataBufferSize : userinfo->OutputFileNameSize);
for ( i = 0 ; i < *p_AmountWrittenToDataBuffer ; i++)
p_NameDataBuffer[i] = (OMXF_char) userinfo->OutputFileName[i];
}
static void
Reconnect_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
userinfo->OutputFile = fopen(userinfo->OutputFileName,"ab");
}
static void
Terminate_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
free(userinfo->OutputFileName);
free(p_UserInfo);
}
static void
Write_fun(OMXF_SubEnvironment_type * p_SubEnvironment,
void * p_UserInfo,
char * p_OutputDataBuffer,
OMXF_int AmountInDataBuffer)
{
UserInfo_t * userinfo = (UserInfo_t *) p_UserInfo;
size_t AmountWritten;
if ( AmountInDataBuffer < 1 ) return;
AmountWritten = fwrite(p_OutputDataBuffer, sizeof(char),
AmountInDataBuffer, userinfo->OutputFile);
}
/* The external output function is invoked just when an OPEN or REOPEN
action attaches a stream item to the external output and allocates any
resources that will be required during the lifetime of the attachment */
void ExternalFileWrite(OMXF_Environment_type *p_Env)
{
UserInfo_t *userinfo;
/* Shelf Argument Declarations */
OMXF_StreamShelf_type *p_stream; /* Pointer to Stream xvalue */
OMXF_Source_type *p_Source;
OMXF_int length;
OMXF_int fi;
OMXF_int status;
OMXF_char * FileName;
/* Pointer to external output control object */
OMXF_ExternalOutput_type *p_ExtOutput;
/* shelf is first argument */
p_stream = OMXF_GetStreamArgumentByPosition(p_Env, 1);
/* get a data source, from the default item on the stream shelf */
p_Source = OMXF_GetSourceFromStreamItem(p_stream,
OMXF_GetDefaultItem(p_stream));
/* allocate a user data area for this instance of the function */
userinfo = malloc(sizeof(UserInfo_t));
/* copy argument value to user data area for use as a file name */
userinfo->OutputFileNameSize = OMXF_GetInitialLengthOfSource(p_Source);
userinfo->OutputFileName =
malloc(sizeof(char) * (1 + userinfo->OutputFileNameSize));
FileName = malloc(sizeof(OMXF_char) * (1 + userinfo->OutputFileNameSize));
length = OMXF_ReadDataFromSource(p_Source,
userinfo->OutputFileNameSize,
FileName);
/* convert from OMXF_char to char */
for ( i = 0 ; i < length ; i++ )
{
userinfo->OutputFileName[i] = (char) FileName[i];
}
free(FileName);
userinfo->OutputFileName[length] = '\0';
/* name is the name of a file which is not yet open */
userinfo->OutputFile = NULL;
/* Ask OmniMark to create the external output control object */
p_ExtOutput = OMXF_CreateExternalOutput (p_Env);
/* Install the user-defined subfunctions into the external output
control object. Don't forget to set the User Info pointer! */
status = OMXF_SetUserInfoOfExternalOutput (p_ExtOutput, userinfo);
status = OMXF_SetConnectFunOfExternalOutput (p_ExtOutput, Connect_fun);
status = OMXF_SetDisconnectFunOfExternalOutput(p_ExtOutput,Disconnect_fun);
status = OMXF_SetHasNameFunOfExternalOutput (p_ExtOutput, HasName_fun);
status = OMXF_SetNameLengthFunOfExternalOutput(p_ExtOutput,NameLength_fun);
status = OMXF_SetNameOfFunOfExternalOutput (p_ExtOutput, NameOf_fun);
status = OMXF_SetReconnectFunOfExternalOutput (p_ExtOutput, Reconnect_fun);
status = OMXF_SetTerminateFunOfExternalOutput (p_ExtOutput, Terminate_fun);
status = OMXF_SetWriteFunOfExternalOutput (p_ExtOutput, Write_fun);
/* Return a pointer to the new output control object to OmniMark */
OMXF_SetExternalOutputReturnValue (p_Env, p_ExtOutput);
}
Opaque data functions provide data in the form that will be required by one or more external functions, and they put the data under control by the OmniMark shelf management system. Each opaque data function creates a new type that can be used in OmniMark code just like the OmniMark counter switch and stream types (See Section 2.3.3, "Opaque Data Function").
The OmniMark shelf management system manages all opaque data items, but expects the corresponding opaque values to be maintained by external functions. Consequently, OmniMark operations that create, copy and destroy opaque data will request a corresponding creation, copy or destruction of the user-defined opaque values. This is done by invoking
An example of a an opaque value is:
typedef struct FloatVar_type FloatVar_type;
struct FloatVar_type
{
OMXF_OpaqueHeader_type Header; /* required by external function API */
char * description; /* user-defined */
double floatnum; /* user-defined */
};
For the above value,
The Create function, the Copy subfunction and the Release subfunction provide all of the functions that OmniMark needs to manage opaque data items that are being created, copied or destroyed. Changes to the data can only be made by invoking external functions, because the user-defined data values are invisible to OmniMark.
The Create function is invoked whenever OmniMark actions (like NEW) or declarations (like LOCAL) require a new instance of opaque data. The Create function:
Unlike other external functions, an opaque data Create function does not return OmniMark data. Instead, the Create function has a C return value, which is a pointer to a newly created instance of opaque data (or a NULL pointer if the data could not be created). The OmniMark shelf management system uses this pointer to install the new instance of opaque data in the opaque data item that is being created by OmniMark.
An opaque data Create function does not look for OmniMark arguments, because execution is initiated by OmniMark declarations (like GLOBAL) and actions (like NEW and SET) that do not take OmniMark arguments.
The following example illustrates the relationship between OmniMark code and the invocation of the Create function for an opaque type FLoatVar:
cross-translate
declare opaque FloatVar created by "CreateFloatVar"
in function-library "xfopaque"
global FloatVar first-value size 2 ;invokes Create function twice
find-start
local FloatVar second-value ;invokes Create function once
new first-value ;invokes Create function once
The Copy subfunction is invoked whenever an OmniMark action (like SET) requires an opaque data item to be copied without changing the opaque data values.
The OMXF_OpaqueCopy_fun subfunction;
Allocation of resources may be achieved by allocating new resources, or by increasing a reference count on the resources held by the original opaque value (see Section 3.13.5, "Opaque Data Resource Management"). In the first case, duplication will require a physical copy, and a pointer will be returned to the new opaque value. In the second case, the Copy subfunction can meet all of its requirements by incrementing a reference count and returning the original pointer.
A typical declaration of an opaque data Copy function is:
static OMXF_OpaqueHeader_type*
FloatVarCopy (OMXF_SubEnvironment_type *p_SubEnv,
OMXF_OpaqueHeader_type *p_OpaqueHeader);
The Release subfunction is invoked whenever an OmniMark action (like REMOVE) requires an opaque data item to be destroyed, or when an opaque data item goes out of scope.
The OMXF_OpaqueRelease_fun subfunction receives an (OMXF_OpaqueHeader_type*) pointer to an opaque value and frees all resources associated with that pointer (see Section 3.13.5, "Opaque Data Resource Management").
Deallocation of resources may be achieved by freeing resources, or by decrementing a reference count on the resources held by the opaque value, and freeing the physical resources only when the reference count reaches zero (see Section 3.13.5, "Opaque Data Resource Management").
A typical declaration of an opaque data Release function is:
static void
FloatVarRelease (OMXF_SubEnvironment_type *p_SubEnv,
OMXF_OpaqueHeader_type *p_OpaqueHeader);
You can use or modify opaque data from any external function (see Section 3.5.2.2.2, "Access to Opaque Data") by making changes to items on opaque data shelf arguments (see Section 3.5.2, "Shelf Access"). The only other way to affect the value of an opaque data item is to return an opaque value from an external function (see Section 3.7, "Opaque Data Functions").
When you are using opaque values in external functions, you have to remember a few points:
For the same reasons, you should use the opaque value create function to create an opaque value whenever you can use the default value as a starting point for further work.
When you use opaque data, you will need to allocate and free memory, because an OmniMark opaque data item only holds a pointer to an opaque value. In many applications, you may find it convenient to use malloc() to allocate memory and get the pointer, and free() to deallocate memory. This approach to memory management is simple, but it can lead to frequent allocation and release of resources, and it will allocate a different set of resources to every instance of an opaque value. Depending on your requirements, you may prefer a different approach to managing memory and other resources.
If you have a large number of opaque values, but many of the values will be identical, you may want to use reference counts.
When using reference counts
If all of your opaque values have identical structure, and you are constantly creating and releasing opaque values, you might want to maintain a free list of released opaque values.
When using a free list
The following example illustrates how you could add floating point numbers to the data types that OmniMark can handle. Another example illustrates how the new data type can be handled by external functions (see Section 3.13.7, "Example: External Functions Using Opaque Data"). Note that this example does not show any error checking, although you should check for error after each step (see Section 3.4.3, "Failure Indicators and Exceptions" and the xfoutput example).
/*
* DECLARE OPAQUE FloatVar CREATED BY "CreateFloatVar"
* IN FUNCTION-LIBRARY "xfopaque"
*/
/* The opaque data value */
typedef struct FloatVar_type FloatVar_type;
struct FloatVar_type
{
OMXF_OpaqueHeader_type Header; /* required by API */
double floatnum; /* user-defined */
};
/* subfunction: Copy a FloatVar value (e.g. in a SET command)
This creates a new FloatVar, and duplicates the old value into the new. */
static OMXF_OpaqueHeader_type*
FloatVarCopy (OMXF_SubEnvironment_type *p_SubEnv,
OMXF_OpaqueHeader_type *p_OpaqueHeader)
{
FloatVar_type * p_NewFloatVar;
OMXF_int status;
FloatVar_type * p_OldFloatVar;
p_OldFloatVar = (FloatVar_type *) p_OpaqueHeader;
/* get memory to hold new opaque item */
p_NewFloatVar = malloc(sizeof(struct FloatVar_type));
/* Copy the OmniMark control header to the new opaque value */
memcpy( p_NewFloatVar, p_OldFloatVar, sizeof(OMXF_OpaqueHeader_type));
/* Initialize the user data */
p_NewFloatVar->floatnum = p_OldFloatVar->floatnum;
/* return a pointer to the new FloatVar */
return & p_NewFloatVar->Header;
}
/* subfunction: Release resources (e.g. when a FloatVar is removed) */
static void
FloatVarRelease (OMXF_SubEnvironment_type *p_SubEnv,
OMXF_OpaqueHeader_type *p_OpaqueHeader)
{
FloatVar_type * p_FloatVar;
p_FloatVar = (FloatVar_type *) p_OpaqueHeader;
free(p_FloatVar);
}
/* function: Create a FloatVar (e.g. in a GLOBAL, LOCAL or new command) */
OMXF_OpaqueHeader_type*
CreateFloatVar (OMXF_SubEnvironment_type *p_SubEnv)
{
FloatVar_type * p_FloatVar;
OMXF_int status;
/* get memory to hold new opaque item */
p_FloatVar = malloc(sizeof(struct FloatVar_type));
/* Initialize the OmniMark control header. Otherwise, OmniMark
will crash on the first reference to the new opaque item. */
status = OMXF_InitializeOpaqueHeader
( p_SubEnv, & p_FloatVar->Header, OMXF_API_VersionNumber,
FloatVarCopy, FloatVarRelease );
/* Initialize the user data */
p_FloatVar->floatnum = 0.0;
/* return a pointer to the new FloatVar */
return & p_FloatVar->Header;
}
The following example illustrates how you use external functions to handle one type of opaque data. Another example illustrates how to create the opaque data type (see Section 3.13.6, "Example: Opaque Data"). The example includes three external functions:
Note that this example does not show any error checking, although you should check for error after each step (see Section 3.4.3, "Failure Indicators and Exceptions" and the xfoutput example).
/* The opaque data value */
typedef struct FloatVar_type FloatVar_type;
struct FloatVar_type
{
OMXF_OpaqueHeader_type Header; /* required by API */
double floatnum; /* user-defined */
};
/* Set a FloatVar from a string representation of the value */
/*
* DEFINE EXTERNAL FloatVar FUNCTION SetFloat
* (READ-ONLY STREAM FloatVal)
* AS "SetFloat"
* IN FUNCTION-LIBRARY "xfopaque"
*/
void
SetFloat (OMXF_Environment_type *p_Env)
{
OMXF_StreamShelf_type *p_stream; /* Stream Shelf Argument */
OMXF_int DefaultItem; /* default stream item */
OMXF_Source_type *p_Source; /* source attached to stream */
FloatVar_type *p_FloatVar; /* Function return value */
char buffer[128]; /* Temporary C string */
OMXF_int BufferStringLength;
OMXF_int fi; /* Failure Indicator */
OMXF_int status;
/* Get a pointer to the stream shelf argument */
p_stream = OMXF_GetStreamArgumentByPosition (p_Env, 1);
/* Find the default item on the stream shelf */
DefaultItem = OMXF_GetDefaultItem(p_stream);
/* Get the data source that should be attached to the default item */
p_Source = OMXF_GetSourceFromStreamItem(p_stream,DefaultItem);
/* Copy data from the default item to a C string */
BufferStringLength = OMXF_ReadDataFromSource(p_Source,
sizeof(buffer)-1,buffer);
buffer[BufferStringLength]='\0';
/* Create a FloatVar to use as the function return value */
p_FloatVar = malloc(sizeof(struct FloatVar_type));
/* Initialize the OmniMark control header. Otherwise, OmniMark
will crash on the first reference to the new opaque item. */
status = OMXF_InitializeOpaqueHeader
( p_Env, & p_FloatVar->Header, OMXF_API_VersionNumber,
FloatVarCopy, FloatVarRelease );
/* Set the user data to the desired values */
p_FloatVar->floatnum = atof(buffer);
/* Pass the new FloatVar as a return value to OmniMark. */
OMXF_SetOpaqueReturnValue (p_Env, & p_FloatVar->Header);
}
/* Get a string representation of a FloatVar */
/*
* DEFINE EXTERNAL STREAM FUNCTION GetFloat
* (READ-ONLY FloatVar FloatVal)
* AS "GetFloat"
* IN FUNCTION-LIBRARY "xfopaque"
*/
void
GetFloat (OMXF_Environment_type *p_Env)
{
OMXF_OpaqueShelf_type *p_FloatValShelf; /* Opaque Shelf Argument */
OMXF_int DefaultItem; /* default opaque item */
FloatVar_type *p_FloatVar; /* opaque item value */
char buffer[128]; /* Temporary C string */
OMXF_int fi; /* Failure Indicator */
/* Get a pointer to the opaque shelf argument */
p_FloatValShelf = OMXF_GetOpaqueArgumentByPosition (p_Env, 1);
/* Find the default item on the opaque shelf */
DefaultItem = OMXF_GetDefaultItem(p_FloatValShelf);
/* Get a pointer to the default opaque item */
p_FloatVar = (FloatVar_type *)
OMXF_GetOpaqueItemReference (p_FloatValShelf, DefaultItem);
/* get a string representation of the floating-point number */
sprintf(buffer,"%lg", p_FloatVar->floatnum);
/* TODO: Return an appropriate Stream value to OM */
OMXF_Write_C_chars_ToStreamReturnValue (p_Env, strlen(buffer), buffer);
}
/* Swap two FloatVar shelf items */
/*
* DEFINE EXTERNAL FUNCTION SwapFloat
* (MODIFIABLE FloatVar a, MODIFIABLE FloatVar b)
* AS "SwapFloat"
* IN FUNCTION-LIBRARY "xfopaque"
*/
void SwapFloat(OMXF_Environment_type *p_Env)
{
OMXF_OpaqueShelf_type *p_FloatVarA;
OMXF_OpaqueShelf_type *p_FloatVarB;
OMXF_OpaqueHeader_type *p_A_Value;
OMXF_OpaqueHeader_type *p_B_Value;
OMXF_OpaqueHeader_type *p_A_Value_Copy;
OMXF_OpaqueHeader_type *p_B_Value_Copy;
OMXF_OpaqueHeader_type * (* OpaqueCopyFun)
(OMXF_SubEnvironment_type* p_SubEnvironment,
OMXF_OpaqueHeader_type* p_Obj);
OMXF_int A_Status;
OMXF_int B_Status;
/* get the shelf arguments */
p_FloatVarA = OMXF_GetOpaqueArgumentByPosition(p_Env, 1);
p_FloatVarB = OMXF_GetOpaqueArgumentByPosition(p_Env, 2);
/* Get pointers to the opaque value that is attached to the default item
on each opaque shelf */
p_A_Value = OMXF_GetOpaqueItemReference
(p_FloatVarA, OMXF_GetDefaultItem (p_FloatVarA));
p_B_Value = OMXF_GetOpaqueItemReference
(p_FloatVarB, OMXF_GetDefaultItem (p_FloatVarB));
/* swap the two opaque values. We have to make copies, because replacing
an opaque value pointer in an opaque data item causes OmniMark to
release the previous opaque value. */
OpaqueCopyFun = OMXF_GetOpaqueCopySubfunction(p_Env, p_A_Value);
p_A_Value_Copy = OpaqueCopyFun((OMXF_SubEnvironment_type*)p_Env,p_A_Value);
OpaqueCopyFun = OMXF_GetOpaqueCopySubfunction(p_Env, p_B_Value);
p_B_Value_Copy = OpaqueCopyFun((OMXF_SubEnvironment_type*)p_Env,p_B_Value);
/* complete the swap by inserting pointers to the new values */
A_Status = OMXF_SetOpaqueItem
(p_FloatVarA, OMXF_GetDefaultItem (p_FloatVarA), p_B_Value_Copy);
B_Status = OMXF_SetOpaqueItem
(p_FloatVarB, OMXF_GetDefaultItem (p_FloatVarB), p_A_Value_Copy);
}
The external function API promotes flexibility and power by giving you considerably more choice in design decisions than you would have with OmniMark. For example, an attempt to access a non-existent shelf item is a fatal error when when it occurs in OmniMark code. In your external function, you can choose to continue, or to initiate a fatal error by invoking OMXF_SignalExternalFunctionException. If you choose to continue, you will need to ensure that failure to have the item will not cause some subsequent error, either in your external function, or in the OmniMark program that invoked the function.
The external function API leaves all error checking up to the programmer. Instead of constraining you by taking any kind of action on errors, the API simply sets error indicators that you can check if you want to (see Section 3.4.3, "Failure Indicators and Exceptions". Whenever you invoke a method of the API to get something that you will use later, you need to check for errors, before depending on the result of invoking the method. For example, if you open a stream item (see Section 3.5.4.1.2, "Opening and Reopening Stream Items") to write, you should check for failure, before you try to get a sink for writing (see Section 3.5.4.1.2, "Opening and Reopening Stream Items").
The API makes extensive use of pointers, but does very little checking of the pointers. It is possible to check for NULL pointers (see Section 3.15.1, "Checking for Null Pointers"), but this involves some overhead that you would rather not have in production code. It is not possible to check non-NULL pointers for validity, since you do not find out about illegal memory references until you actually try to access the memory. Consequently, your design has to ensure that invalid pointers will not be created, and that NULL pointers will be used only in places where a NULL pointer is allowed.
Many of the data structures available to you during the execution of your external function disappear when your function exits. It is useless to save a pointer to them, because the pointer will become invalid after the function exits. This also means that you cannot return a pointer to anything from the list of OmniMark arguments that were passed to your external function.
If you pass a pointer out of your external function, that pointer must reference an object that was created within your external function. In particular, if you are returning an opaque value (or putting the value into an opaque item on the argument list), that value must have either been created by your external function, or copied from an opaque item on the argument list of your external function. In short, your external function must own anything that it passes out to OmniMark, and no longer owns it after giving it to OmniMark.
To avoid problems
You can trap NULL pointers and print a message instead of passing them to the API and causing OmniMark to crash. The overhead of checking might not be acceptable in production code, but the check can be very useful during development.
To trap null pointers, simply define the macro OMXF_SAFE_API when you compile your code. This will cause all methods to check for a NULL pointer, When a NULL pointer is received, the function OMXF_NullPointerError will be invoked, with the name of the method, and the file and line number of the offending invocation>. The function can print out a message, or take any other action that you consider appropriate.
The OMXF_NullPointerError function, unlike other API functions, is not provided in the API but is instead defined as an extern void function. You would normally define the function in a separate file, and make the compiled object file available to the linker. When you compile your external function with OMXF_SAFE_API defined, a reference would be generated, and the linker will link the OMXF_NullPointerError function, A typical function might look like this:
/* Example: format a NULL pointer message from the "safe" API */
void OMXF_NullPointerError(
char * FileName,
int LineNumber,
char * MethodName
)
{
fprintf(stderr,
"NULL pointer passed to method \"%s\" at line %d in file %s\n",
MethodName, LineNumber, FileName
);
}
Tiny OmniMark Interpreter (TOI) is an interactive tool that helps external function developers by
TOI is an interpreter that checks each action as you enter it, and executes those actions that have the correct syntax, If the syntax is wrong, you reenter the command. This makes it easy to incrementally test an external function. You can also inspect data at any time, to see how the progress of your OmniMark code affects the data.
You can also use TOI to generate skeleton code for one or more external functions, directly form the OmniMark declarations that you intend to use with the functions.
TOI is supplied with source code so that you can use TOI, with your system debugger, on your external function code.
For additional information, see the TOI Programmer's Guide and Reference.
Next chapter is Chapter 4, "External Function API Reference".
Copyright © OmniMark Technologies Corporation 1988-1999. All rights reserved.
EUM44, release 1, 1997/11/21.