OSF DCE SIG | J. Harrow (DEC) | |
Request For Comments: 2.1 | July 1992 |
This document proposes enhancements to the DCE 1.0 IDL language and compiler, to be supported in the DCE 1.1 release.
IDL support for arrays in DCE V1.0 was limited to:
DCE 1.1 will remove these restrictions by supporting fully general arrays as described in the IDL functional specification. The following example includes declarations that were not supported in DCE 1.0 that will be allowed by DCE V1.1:
The <attr_var>s are in one-to-one correspondence with the dimensions of the array, starting at the first. If there are fewer <attr_var>s than the array has dimensions then the missing <attr_var>s are assumed to be null. An <attr_var> will be non-null if and only if the lower bound of the corresponding dimension is determined at runtime. Not all <attr_var>s in a min_is clause may be null.
Below are examples of the syntax. Assume values of variables are as follows: long a = -10; long b = -20; long c = -30; long d = 15; long e = 25.
Implementation Note: As C does not directly support conformant arrays or arrays with non-zero lower bounds, the C representation of such an array is a pointer to the base type of the array.
The DCE 1.0 support for [ptr] pointers is not transparent in that if a manager routine releases storage via rpc_ss_free() for an input parameter, the corresponding free call does not occur in the client code. This additional transparency will be supported by DCE 1.1. It is enabled on a per-operation basis by specifying the [reflect_deletions] operation attribute. In the following example, if rpc_ss_free() is called with the value of parameter A in the manager code, the appropriate free() routine will be invoked in the client stub with the value of parameter A in the client address space.
If, during an RPC call, application code uses the IDL-provided rpc_ss_free routine to free a node pointed to by a [ptr] pointer, the effect of that free will be duplicated on return to the caller by having the caller stub call the appropriate free routine. If the called application routine orphans a node by simply breaking the link to it instead of freeing it as described above, then the corresponding node will be orphaned at the caller. Note that this discussion applies only to nodes that are a part of the parameter either at the time of the call or at the time of the return. Nodes that are created by the callee and either deleted or orphaned before the return will not be reflected back to the caller in any way. Also, changes to orphaned nodes are not reflected back to the caller.
A union that is created without the use of the <union_switch> production is a non-encapsulated union. The discriminant of a non-encapsulated union is another parameter, if the union is a parameter, or another structure field if the union is a structure field.
When the non-encapsulated union is being declared as a type, the <union_type_switch_attr> production must be used. When a type that is a non-encapsulated type is used to declare a structure field or a parameter, the <union_instance_switch_attr> production must be used. When a non-encapsulated union is being declared directly as a structure field or a parameter, the <union_instance_switch_attr> production must be used.
Some examples of non-encapsulated unions:
The n_e_union_t construct, above, gets turned into the following C union declaration in the generated header file.
The syntax for a union definition is:
The [unique] attribute is the third and final pointer class to be supported by IDL. It provides a level of capability which can be efficiently implemented, and yet provides enough flexibility for the needs of many applications. Just as the current attributes [ref] and [ptr], it may be applied at the declaration site of a pointer in order to describe the required capabilities of the pointer.
A unique pointer is more flexible than a reference pointer, although it shares several important characteristics. The distinguishing characteristics of a unique pointer are:
A unique pointer has the following characteristics in common with reference pointers.
This extension to the IDL compiler will allow specification of a set of user-defined exceptions that may be generated by the server implementation of the interface. If an exception occurs during the execution of the server, it terminates the operation and the exception is propagated from server to client. Exceptions are declared via the exceptions interface attribute which has the following syntax:
Example of the exceptions attribute:
Since exceptions are associated with operation implementation, they are not imported into other interfaces via the import statement.
In order to make exceptions as transparent as possible, the default behavior is for IDL to define and initialize the exceptions under a once block in the generated stubs. If, however, the user wants to share exception names across multiple interfaces, or closely control their definition and initialization, they may use an ACF attribute to disable this automatic exception definition mechanism.
If [extern_exceptions] is specified without a list of exceptions, no exceptions specified in the interface are defined or initialized. If a list of exceptions are present they represent the set of exceptions that are defined external to the interface and thus are not defined or initialized. All other exceptions listed in the [exceptions] attribute of the interface are defined and initialized. This attribute indicates that the specified exceptions are defined and initialized in some other external manner prior to making a call on the interface. They may be predefined exceptions (such as exc_e_exquota), provided by some other interface, or defined and initialized explicitly by the application itself.
The set of exceptions listed in the <exceptions_attribute> of an interface will be associated in order with unsigned integer values starting with one. These integer values provide the network representation of the exceptionsfor this interface. The NDR of a user-defined exception is a fault packet containing two idl_ulong_int values. The first being the fault nca_s_fault_user_defined from ncastat.idl followed by the integer value associated with the particular exception that occurred.
The <Identifiers> specified in the <exceptions_attribute> are the names of exceptions manipulated via the dce/exc_handling.h exception package. This is necessary to provide a consistent treatment of exceptions throughout DCE and allow for a portable implementation.
The design outlined above allows specification of exceptions on a per-interface basis. If desired, exception specification could be allowed on a per-operation basis. This appears, however, to provide primarily a form of forced documentation, while providing little additional capability.
Advantages of per-operation exceptions:
Disadvantages of per-operation exceptions:
Considering the concerns regarding stub size, and relatively small advantages provided by per-operation exceptions, this design chose declaration of exceptions on a per-interface basis.
This extension to the IDL stub compiler will enable instances of one or more data types to be encoded into and decoded from a byte stream format suitable for persistent storage.
The ACF grammar will be extended by the following two attributes:
The [encode] and [decode] attributes may be applied to the interface or to operations within the interface.
Operations with the [encode] and/or [decode] attribute result in a stub generated into the client stub (cstub) file. An operation with the [encode] attribute will result in a client stub which supports the creation of the byte stream. An operation with the [decode] attribute will result in a stub that supports the retrieving the arguments from a byte stream. Applying the attribute to the interface has the same effect as applying the attribute on each operation in the interface. Although [encode] and [decode] are commonly used together, either may be omitted to reduce the size of the generated stub code.
Only parameters specified as [in] are represented in an encoding. In order to allow decoding, all parameters specified as [in] should also have the [out] attribute. Function results, [out]-only parameters, and [in]-only parameters will result in compiler diagnostics being displayed.
An operation with either the [encode] or [decode] attribute (either explicitly or implicitly by application on the interface) restricts the form of the operation. Example restrictions include:
/* ** All operations support encode/decode. */ [encode,decode] interface user_records {}
/* ** The file_ACL operation is used to encode/decode data ** possibly utilized by other operations in the interface. */ [explicit_handle] interface files { [encode,decode,comm_status,fault_status] file_ACL(); file_open( [comm_status,fault_status] est ); file_close( [comm_status,fault_status] est ); }
/* ** For clients that only create file_ACLs, we use only the ** [encode] attribute to save space in the generated stub. */ interface files { [encode] file_ACL(); }
For an operation definition which represents an RPC operation, the IDL compiler generates RPC stubs which provide transparent access to an RPC server. In a similar fashion, a stub is generated for an encode/decode operation. These encoding stubs, however, hide the encoding/decoding of a set of data values into a byte stream.
When generating RPC stubs, the handle_t data type is generated as an rpc_binding_handle_t which describes the location of the RPC server. When generating an encoding stub, the handle_t data type is mapped into a idl_es_handle_t (IDL encoding services handle) which describes which controls the encoding process.
/* ** Data types for read/write/alloc routines that manage incremental ** processing of the encoding. */ typedef void (*)( idl_void_p_t state, /* [in,out] user state */ idl_char *buf, /* [out] Address of buffer */ idl_ulong_int *size /* [in,out] Requested Size/Size of buffer */ ) idl_es_allocate_rtn_t; /* Routine to allocate a buffer */ typedef void (*)( idl_void_p_t state, /* [in,out] user state */ idl_char *buf, /* [in] Encoded data */ idl_ulong_int size /* [in] Size of encoded data */ ) idl_es_write_rtn_t; /* Routine to provide encoded data to application */ typedef void (*)( idl_void_p_t state, /* [in,out] user state */ idl_char *buf, /* [out] Data to be decoded */ idl_ulong_int *size /* [out] Size of buf */ ) idl_es_read_rtn_t; /* Routine to obtain encoded data from application */ /* ** Provides an idl_es_handle_t which allows an encoding operation to ** be performed in an incremental fashion similar to pipe processing. ** Buffers areas are requested by the stubs via the allocate routine ** as necessary and when filled, are provided to the application via ** a call to the write routine. This routine is suitable for ** encodings that can be incrementally processed by the applications ** such as when they are written to a file. The application may ** invoke multiple operations utilizing the same handle. */ void idl_es_encode_incremental( idl_void_p_t state, /* [in] user state */ idl_es_allocate_t alloc, /* [in] alloc routine */ idl_es_write_t write, /* [in] write routine */ idl_es_handle_t *h, /* [out] encoding handle */ error_status_t *st /* [out] status */ ); /* ** Provides an idl_es_handle_t which allows an encoding operation to ** be performed into a buffer provided by the application. If the ** buffer is not of sufficient size to receive the encoding the error ** rpc_s_no_memory is reported. This mechanism provides a simple, ** but high performance encoding mechanism provided that the upper ** limit of the encoding size is known. The application may invoke ** multiple operation utilizing the same handle. */ void idl_es_encode_fixed_buffer( idl_byte *ep, /* [out] pointer to buffer to recieve the encoding (must be 8-byte aligned) */ idl_ulong_int bsize, /* [in] size of buffer provided */ idl_ulong_int *esize, /* [out] size of the resulting encoding (set after execution of the stub) */ idl_es_handle_t *h, /* [out] encoding handle */ error_status_t *st /* [out] status */ ); /* ** Provides and idl_es_handle_t which provides an encoding in a ** stub-allocated buffer. Although this mechanism provides the ** simplest application interface, large encodings may incur a ** performance penalty for this convenience. The return buffer is ** allocated via the client memory allocation mechanism currently in ** effect at the time of the encoding call. */ void idl_es_encode_dyn_buffer( idl_byte **ep, /* [out] pointer to recieve the dynamically allocated buffer which contains the encoding */ idl_ulong_int *esize, /* [out] size of the resulting encoding (set after execution of the stub) */ idl_es_handle_t *h, /* [out] decoding handle */ error_status_t *st /* [out] status */ ); /* ** Provides an idl_es_handle_t which allows an decoding operation to ** be performed in an incremental fashion similar to pipe processing. ** Buffers containing portions of the encoding are provided by the ** application via calls to the read routine. This routine is ** suitable for encodings that can be incremental decoded by the ** stubs such as when they are read from a file. */ void idl_es_decode_incremental( idl_void_p_t state, /* [in] user state */ idl_es_read_t write, /* [in] routine to supply buffers */ idl_es_handle_t *h, /* [out] decoding handle */ error_status_t *st /* [out] status */ ); /* ** Provides an idl_es_handle_t which allows an decoding operation to ** be performed from a buffer containing the encoded data. There may ** be a performance penalty if the buffer provided is not 8-byte ** aligned. */ void idl_es_decode_buffer( idl_byte *ep, /* [in] pointer to buffer containing the encoding */ idl_ulong_int size, /* [in] size of buffer provided */ idl_es_handle_t *h, /* [out] decoding handle */ error_status_t *st /* [out] status */ ); /* ** Returns the rpc_if_id_t and operation number contained within the ** encoding associated with an idl_es_handle_t. This information is ** available during a decode operation, and after the operation has ** been invoked for an encode operations. If this information is not ** available, the status rpc_s_unknown_if is returned. */ void idl_es_inq_encoding_id( idl_es_handle_t h, /* [in] decoding handle */ rpc_if_id_t *if, /* [out] RPC interface identifier (including version information) */ idl_ulong_int *op, /* [out] operation number */ error_status_t *st /* [out] status */ ); /* ** Frees a idl_es_handle_t and its associated resources. */ void idl_es_handle_free( idl_es_handle_t *h, /* [in,out] handle to free */ error_status_t *st /* [out] status */ );
The user-provided allocate function is required to return the address of a buffer that is 8-byte aligned and a multiple of 8-bytes in length. This allows for optimized data manipulation in the generated stubs. The size argument provides the size requested by the stubs, the allocate routine may choose to return a buffer of a different size within the above constraints by indicating the size of the resulting buffer by modifying the value of the size argument.
The user-provided write function is required to process the provided buffer that now contains the encoded byte stream. It will be called when the active buffer has been filled, or when the end of the encoding has been reached.
The user-provided read function is required to return the address of a buffer which contains a portion of the encoded data that is 8-byte aligned and a multiple of 8-bytes in length. This allows for optimized data manipulation in the generated stubs.
In order to support a wide variety of applications, the API of an encoding interface can be used in several ways. In the most general form, processing of the byte stream is done via routine. This allows for large, dynamically sized data, to be encoded/decoded incremental.
In order to perform an encode operation, the application creates an idl_es_handle_t with the idl_es_encode_incremental routine.
Upon invocation of encode operation stub, the allocate function supplied will be called to obtain a buffer into which the byte stream will be written. If the size of all operation arguments can be determined at IDL compile time, then the requested size will be an approximation of the size of the resulting byte stream. In either case, the allocate routine may choose to provide either a larger or smaller buffer. The actual size of the buffer returned is indicated via the size output argument of the allocate routine. The stub processes each argument, encoding it into the buffer provided. After filling the buffer, or reaching the end of the byte stream, the stub calls the write routine.
Since control returns to the application after the encoding operation, it is the application's responsibility to release any resources related to the application state, and free the idl_es_handle_t.
In order to perform a decode operation, the application creates an idl_es_handle_t via the idl_es_decode_incremental routine.
Upon invocation of the decoding stub routine, the read function supplied will be called to obtain a buffer containing a portion of the byte stream. The read function will be called repeatedly until the entire byte stream has been obtained and the arguments decoded back into the operation arguments.
[uuid(689d24d8-45f0-11cb-825f-08002b107359)] interface scalars { void data_set1( [in] handle_t h, [in,out] long *a, [in,out] long *b, [in,out] double *c); }
[encode,decode] interface scalars { data_set1([comm_status] st); }
#include <stdio.h> #include "scalars.h extern my_read(); main() { idl_ulong_int a,b; idl_long_float c; FILE *x = fopen("my_scalars.dat"); idl_es_handle_t h; error_status_t st; /* Decode file x, into values */ idl_es_decode_incremental((idl_void_p_t)x, my_read, &h, &st); if (st != error_status_ok) abort(); data_set1(h, &a, &b, &c, &st); if (st != error_status_ok) abort(); idl_es_handle_free(&h, &st); if (st != error_status_ok) abort(); fclose(x); printf("My data: %d, %d, %f\en", a, b, c); }
For applications that manipulate smaller units of encoded data, and/or require less control over the allocation and manipulation of the byte stream, IDL encoding services also provides a simpler interface. Under the simplified interface the byte stream created by the encode operation is returned via single buffer. This buffer may be provided by the application (idl_es_encode_fixed_buffer) or dynamically allocated by the encoding stub (idl_es_encode_dyn_buffer). For dynamically allocated buffers, the client memory management routines in effect at the time of the call to the stubs are used to allocate the resulting buffer. This is normally malloc/free (rpc_ss_allocate/rpc_ss_free from within a manager call) unless otherwise specified via rpc_ss_set_client_alloc_free() or rpc_ss_swap_client_alloc_free().
For a simplified decode operation, the application provides the starting address of the buffer containing the byte stream which is then decoded into the operation parameters specified.
[uuid(689d24d8-45f0-11cb-825f-08002b107359)] interface scalars { void data_set1( [in] handle_t h, [in,out] long *a, [in,out] long *b, [in,out] double *c); }
#include "scalars.h main() { idl_ulong_int a = 8, b = 22; idl_long_float c = 72.3; idl_es_handle_t h; idl_byte my_encoding[1000]; error_status_t st; idl_unlong_int my_encoding_size; /* Decode file x, into values */ idl_es_encode_fixed_buffer(my_encoding, 1000, &my_encoding_size, &h, &st); if (st != error_status_ok) abort(); /* Create byte stream encoding of my data */ data_set1(h, &a, &b, &c); idl_es_handle_free(&h, &st); if (st != error_status_ok) abort(); /* Display then free the encoded byte stream */ fwrite(my_encoding, my_encoding_size, 1, stdout); }
Because an operation with the [decode] and/or [encode] attribute, is a client-only function, the manager entry point vector entry for that operation will be NULL. Servers requiring encoding services should incorporate the client stub for the desired interface.
The encoding format of the byte stream will be an extended version of that used by DCE security. This format contains a generic header portion which identifies the contents of the encoding (interface, version, operation number, and the transfer syntax used to encode the data portion), and the data itself. Any transfer-syntax dependent data (such as the drep for NDR) is encoded at the beginning of the data portion. The idl_pkl_header_t utilized by DCE SEC will be modified in the following ways:
The resulting internal header format used by an IDL encoding is provided below. This format is distinguished from a DCE SEC pickle via the version number field. By examination of the version number field DEC SEC can case between the existing pickling code, and the IDL encoding services encoding stubs.
/* * i d l _ e s _ p v t _ h e a d e r _ t * * An IDL encoding is an opaque byte stream from the application * perspective. The representation in a byte stream of a set of * values whose type is specified in IDL. An encoding is divided * into two parts: a "header" part followed by a "data" part. * * The header part contains four fields encoded according to the spec * given below: an 8-bit version number, an 8-bit integer drep value * used to encode the header portion, an rpc_syntax_id_t specifying * the encoding used in the encoding, an rpc_if_id_t specifying the * interface uuid and version, of the value encoded in the pickle. * The data part of a pickle is a byte stream that contains the * encoding of the value per the encoding syntax specified in the * header; this may include some info that is needed by the selected * syntax and properly part of the pickled value (e.g., the NDR * format label). * * Assuming that the info in a pickle header could be modeled by the * following type: * * struct idl_es_pvt_header_t { * unsigned8 version; /* pickle header version (now * version=1) * unsigned8 int_drep; /* drep of pickle header * unsigned8 fill2; * unsigned8 fill3; * rpc_syntax_id_t syntax; /* encoding syntax used * rpc_if_id_t if_id; /* uuid/version of interface * unsigned32 op_num; /* operation number of encoding * } idl_es_pvt_header_t; * * Here is the layout of the pickle header (all integer values in the * header are encoded in the endian format indicated by the * integer_drep field): * * name type offset(s) * ---- ---- --------- * version unsigned8 0 * integer_drep unsigned8 1 * fill unsigned8 2,3 * syntax.id.time_low unsigned32 4,5,6,7 * syntax.id.time_mid unsigned16 8,9 * syntax.id.time_hi_and_version unsigned16 10,11 * syntax.id.clock_seq_hi_and_reserved unsigned8 12 * syntax.id.clock_seq_low unsigned8 13 * syntax.id.node[0] byte 14 * syntax.id.node[1] byte 15 * syntax.id.node[2] byte 16 * syntax.id.node[3] byte 17 * syntax.id.node[4] byte 18 * syntax.id.node[5] byte 19 * syntax.version unsigned32 20,21,22,23 * if_id.uuid.id.time_low unsigned32 24,25,26,27 * if_id.uuid.id.time_mid unsigned16 28,29 * if_id.uuid.id.time_hi_and_version unsigned16 30,31 * if_id.uuid.id.clock_seq_hi_and_reserved unsigned8 32 * if_id.uuid.id.clock_seq_low unsigned8 33 * if_id.uuid.id.node[0] byte 34 * if_id.uuid.id.node[1] byte 35 * if_id.uuid.id.node[2] byte 36 * if_id.uuid.id.node[3] byte 37 * if_id.uuid.id.node[4] byte 38 * if_id.uuid.id.node[5] byte 39 * if_id.vers_major unsigned16 40,41 * if_id.vers_minor unsigned16 44,43 * op_num unsigned32 44,45,46,47 */
This feature is an enhancement to the API for customized and implicit binding to allow attempts on multiple servers. Currently, the use of implicit or customized binding handles provide users with a way to build RPC an interface that appear to the caller as a local interface. They currently do not, however, support attempts on multiple servers due to their limited API. This feature is enabled by applying the [rebind] ACF attribute to either the customized handle or an customized implicit handle (i.e., an implicit handle whose type is not handle_t).
Upon the failure of the RPC call utilizing either the customized or customized implicit binding method with the [rebind] attribute, a user-supplied rebind routine will be invoked to provide another handle on which to attempt the call. The rules under which a rebind are attempted due to an error are the same as those for the [auto_handle] binding method. The rebind routine for a handle with the [rebind] attribute has the following signature:
The application may indicate that rebinding is not possible by returning the value of NULL as the function result of the rebind routine. Note that the rebind routine has the responsibility for freeing the h argument, if necessary. The unbind routine is only called once per operation with the possibly NULL handle_t returned from the final call to the rebind routine.
Jerry Harrow | Internet email: harrow@r2me2.enet.dec.com | |
ZK02-1/Q18 | Telephone: +1-603-881-2193 | |
Digital Equipment Corporation | ||
110 Spitbrook Rd. | ||
Nashua, NH 03062 | ||
USA |