Chapter 4
The Component Object Model

The Component Object Model (COM) is an architecture and infrastructure for building fast, robust, and extensible component-based software. This chapter describes the basic subset of COM that is used by the OSKit; the complete COM specification is available from Microsoft’s web site1.

At its lowest level, COM is merely a language-independent binary-level standard defining how software components within a single address space can rendezvous and interact with each other efficiently, while retaining a sufficient degree of separation between these components so that they can be developed and evolved independently. To achieve this goal, COM specifies a standard format for dynamic dispatch tables associated with objects. These dispatch tables are similar in function to the virtual function tables (“vtables”) used in C++, but they are specified at the binary level rather than the language level, and they include additional functionality: in particular, a standardized run-time type determination (“narrowing”) facility, and reference counting methods. This minimal basis allows a software component to dynamically determine the types of interfaces supported by another unknown component and negotiate a common “language” or set of interfaces through which further interaction can take place. (“Parlez vous Fran cais? Sprechen Sie Deutsch?”) COM builds a whole range of services on top of this basic facility, such as cross-address-space RPC (MIDL), object linking and embedding (OLE), scripting (OLE Automation), etc. However, it is primarily this lowest-level facility that is used by and relevant to the OSKit.

4.1 Objects and Interfaces

The COM dynamic dispatch facility revolves around the fundamental concepts of objects and interfaces. An object in COM is a fairly abstract concept, not necessarily associated with a particular data structure in memory like C++ or Java objects. A COM object can be implemented in any language and can maintain its internal state in any way it chooses; as far as COM is concerned, the only thing relevant about the object is its interfaces. A client accessing a COM object generally does not have direct access to the actual data contained in the object; instead COM objects are only accessible through the set of interfaces the object exports. A reference to a COM object is really just a pointer to one of the object’s interfaces. An object may support any number of interfaces; each interface represents one particular “view” of the object, or one “protocol” through which the object can be accessed. Each interface has its own dynamic dispatch table, consisting of a few standard methods (function pointers) whose calling conventions and semantics are defined by COM, followed by an arbitrary number of custom methods whose calling conventions and semantics are specific to that particular interface.

Although the COM specification defines a few basic interfaces, anyone can independently define new COM interfaces, and in fact the OSKit defines quite a number of such interfaces. COM interfaces are identified by 128-bit globally unique identifiers (GUIDs), which are algorithmically generated through various standardized mechanisms; this avoids all the accidental collisions that can easily occur when human-readable names are used as identifiers. COM GUIDs are the equivalent to and compatible with the Universally Unique Identifiers (UUIDs) used in the Distributed Computing Environment (DCE) originally developed at Apollo and the Open Software Foundation (OSF). Although COM interfaces generally also have human-readable names such as IUnknown and IStream, these names are only for the programmer’s benefit at development time; they get compiled out in the final program and only the GUIDs are used at run-time.

4.1.1 Interface Inheritance and the IUnknown Interface

COM interfaces can directly extend other COM interfaces in single-inheritance relationships, simply by adding additional methods to the dispatch table defined by the base interface and/or further restricting the semantic requirements defined by the base interface. (Derived interfaces cannot relax or weaken the requirements of the base interface, since that would violate the whole principle of subtyping.) Multiple inheritance of COM interfaces cannot be implemented simply by extending dispatch tables this way, since in this case there would be multiple mutually conflicting dispatch tables to extend; however, the effect of multiple inheritance can be achieved by making the object support multiple independent interfaces using the querying mechanism described below. Note that the only form of inheritance of relevance to COM is subtyping, or inheritance of interfaces (types), as opposed to subclassing, or implementation inheritance: COM doesn’t care how an object is implemented, only what interfaces it exports.

Ultimately, every COM interface is derived from a single standard universal base interface, known as IUnknown in the COM standard and oskit_iunknown in the OSKit headers. This minimal COM interface contains only three standard methods, query, addref, and release, which provide the basic administrative facilities that all COM objects are expected to implement. These basic facilities, which essentially provide run-time type determination and reference counting, are described in the following sections.

4.1.2 Querying for Interfaces

The first function pointer slot in the dynamic dispatch table of any COM interface always points to the standard query method (called QueryInterface in the COM specification); this means that a reference to any COM interface can always be used to find any of the other interfaces the object supports. To determine if a COM object supports a particular interface, the client simply calls the standard query method on the object, passing the GUID of the desired interface as a parameter. If the object supports the requested interface, it returns a pointer to that interface; this returned pointer is generally, though not always, a different pointer from the one the client already had, since it points to a different interface, even though the interface refers to the same underlying logical object. The client can then interact with the object through the methods defined by this interface. On the other hand, if the object doesn’t support the requested interface, the query method simply returns an error.

Since interfaces are identified only by simple GUIDs and do not directly contain any detailed information about the methods defined by the interface, the client must already know before it requests the interface what methods the interface supports and what their semantics are. In other words, the basic COM query mechanism only allows the client and to determine the common set of interfaces both it and the object already understand; it does not directly enable the client to learn about other arbitrary unknown interfaces the object might support. Such a dynamic invocation interface can be built on top of the basic COM infrastructure, and in fact that is exactly what is done in ActiveX/OLE Automation scripting; however, the OSKit currently does not support or use this extended invocation facility.

Semantics of the Query Operation

The COM standard specifies that all COM objects must support the standard query operation, and furthermore, the query operation must have certain well-defined semantics. In particular, an object’s interfaces must be:

However, note that subsequent queries for a given interface identifier on a given object are not required always to return the same pointer. This allows objects to create interfaces dynamically upon request and free them later when they are no longer in use, independently of the lifetime of the object itself.

As a special exception to this rule, queries on an object for the IUnknown interface must always return the same pointer; this allows clients to perform a reliable object identity test between two arbitrary COM interface pointers by querying each for their IUnknown interfaces and comparing the pointers. However, as an approximate object identity test, in which occasional false negative answers can be tolerated (i.e., two objects appear different when they are in fact the same), it is sufficient simply to compare two pointers having the same interface type (i.e., the same interface identifier): although objects sometimes export multiple “copies” of an interface, as in certain multiple inheritance or interface caching scenarios, this is rare enough that simple pointer comparison can work well as a heuristic.

4.1.3 Reference Counting

In addition to the basic interface negotiation mechanism provided by standard query method, every COM interface must also export two additional standard methods, addref and release, which are used to control the object’s life cycle through reference counting. Whenever a client receives a pointer to a COM interface, e.g., as the result of a call to a method on a different interface, the client is considered to have one reference to the interface. When the client is done using this reference, it must call the release method on that interface so that the object will know when it is no longer in use and can delete itself. If the client needs to copy the reference, e.g., to give it to some third party while still retaining a reference itself, the client must call the addref method on the interface. Eventually, both the client and the third party will call release on their respective pointers to this interface (possibly at different times and in arbitrary order); the object can then be destroyed only when both outstanding references are released.

In COM, only interfaces are reference counted, not the objects themselves. After a client has obtained a reference to a particular interface, it must call the release method on exactly that interface, and not a different interface referring to the same object. This allows object implementations to maintain individual reference counts on each of their interfaces if they choose. Object implementations are also free to maintain only a single reference count for the entire object, in which case the addref and release methods on all the object’s interfaces will happen to do the same thing; however, clients must not depend on this behavior.

Cycles

As with all reference counted systems, there is a danger of cycles developing among COM objects and preventing proper garbage collection. For example, if object A holds a reference to object B, and object B holds a reference to object A, then assuming nothing is specifically done to change this situation, the reference counts of both objects will remain nonzero and the objects will effectively “keep each other alive” indefinitely even if nothing else in the system still refers to either of those objects. COM does not provide any automatic facility to solve this problem: instead, it must be solved manually by careful design.

One particular technique that can often be used to avoid cycles when two objects both need to maintain pointers to each other is to make one of the objects maintain a reference not to the other object itself, but rather to an inner object which is logically (and possibly physically) part of the main object but is independently reference counted. For example, often a client needs to register a callback object of some kind, such as the network-packet-receive callback object passed to the open method of the oskit_netdev interface. To avoid cycles, the client object should not pass a reference to itself as the callback, but instead should create a separate, independently reference counted callback object which is logically contained in the main client object, and pass a reference to the callback object rather than the main object. This way, the client can keep a reference to the server object, and the server can keep a reference to the callback object, but no cycle will develop and garbage collection works properly.

4.2 Reference and Memory Management Conventions

Since the set of methods and semantics attached to a COM interface represented by a particular GUID only needs to be informally defined by the designer of the interface and understood by the programmers writing code that uses that interface, the exact semantics of the interface can be arbitrary - COM makes no explicit restrictions on the interface’s methods and semantics as long as they are meaningful and well-defined. However, for convenience and consistency, and to make it more practical for COM interfaces to be defined in a form usable by automated tools such as IDL compilers, COM provides a set of method invocation conventions which interface designers are recommended to use when possible. These conventions mainly deal with the allocation and deallocation of memory across method calls, and similarly, the allocation and deallocation of COM object references.

As with typical interface definition languages (IDLs), COM defines three basic logical types of parameters, each with its own standard semantic rules for memory and object reference management. Although COM interfaces do not need to be defined in any IDL, this categorization makes COM’s conventions consistent with common IDLs and makes COM interfaces easier to define in IDLs when necessary:

By convention, the return value of a COM interface method is normally used to return a generic success/failure code to the caller (see Section 4.3, below). However, sometimes methods are instead defined to return something else as their return value; in this case, the return value can be thought of as an out parameter for purposes of memory and object reference management.

4.3 Error Handling

The COM specification defines a standard 32-bit namespace for error codes, which the OSKit adopts as the error code namespace for all of its exported interfaces, both COM and conventional. COM error return values are divided into three fields: a one-bit severity flag, indicating success (zero) or failure (one), a 15-bit facility, providing a broad categorization of error, and finally, a 16-bit code, which indicates the specific error and is only meaningful with respect to a particular facility.

Unfortunately, the management of error codes is one of the relatively few parts of COM in which Microsoft-centrism appears in force. Ideally, error codes should be GUIDs, just like interface identifiers; however, this would be too cumbersome and inefficient in practice. Therefore, COM divides the error code namespace into two categories: globally-unique, centrally allocated error codes, and interface-specific error codes. Most interfaces are expected to use the interface-specific range; these error codes are only meaningful when returned from methods on a particular COM interface, and their meanings are defined as part of that COM interface. However, as may be expected, most of Microsoft’s COM interfaces use centrally administrated error codes since they are much easier to deal with in large software systems and, conveniently, Microsoft happens to be the “central administrative authority.” Furthermore, again as may be expected, Microsoft does not readily allocate facility codes to third parties.

The OSKit defines a number of error codes that need to be valid across a large number of interfaces, both COM and non-COM; it would be difficult or impossible to make these error codes fit into the “interface-specific” paradigm. Further, since only one small 64K range has been assigned for interface-specific error codes, out of the two billion possible values, we felt that using values in the interface-specific range to represent errors that are treated as globally unique by OSKit components would be just asking for trouble in the long term, since in such a small namespace collisions are inevitable. Therefore, for global error codes used by the OSKit, we have informally allocated (i.e., stolen) facility code 0xf10 for our purposes. For the specific assignment of error codes in this range, see the description of oskit/error.h, in Section 4.6.2. However, the OSKit still uses the interface-specific error code range, when appropriate, for error codes only meaningful to a particular interface.

4.4 Binary Issues

Since COM is a binary-level standard, it defines the exact in-memory layout of COM interfaces and their function tables as seen by clients of those interfaces. Any object can implement any COM interface in whatever way it chooses, as long as it conforms to these basic rules.

4.4.1 Interface Structure


PIC

Figure 4.1: Diagram of the structure of a COM interface. The client holds a pointer to a pointer to a table of function pointers; the pointers in the function table point to the object’s implementations of the methods exported to the client through the interface.

Figure 4.1 shows a diagram of the structure of a COM interface. From the client’s viewpoint, a reference to a COM interface is a pointer to a pointer (the function table pointer) to a table of pointers to functions (the dynamic dispatch table). The function table pointer is generally just a part of a larger data structure representing the internal state of the object, illustrated in the figure by the dotted box; however, only the function table pointer itself is visible to the client. To call one of the interface’s methods, the client follows the interface pointer to the function table pointer, then dereferences the function table pointer to find the function table, looks up the appropriate function pointer in the table, and finally calls the function. In general, the first parameter to this function will be a copy of the original interface pointer the client started with, allowing the object implementation to locate its private object state quickly and easily.

4.4.2 Calling Conventions

The specific calling conventions used in a method call is also standardized by the COM specification but depends on the processor architecture. On the Intel x86 architecture, where different types of calling conventions abound, the standard calling conventions for COM interfaces are the stdcall conventions defined by Microsoft and used throughout the Win32 API. Note that these calling conventions generally do not match the default calling conventions of any particular C or C++ compiler, so implementors of COM interfaces must be careful to use the appropriate declarations. In the OSKit, the OSKIT_COMCALL macro can be used to declare functions and function pointers to use standard COM calling conventions, regardless of the compiler or processor architecture in use; see 4.6.1 for more details.

4.5 Source Issues

The OSKit header files defining COM interfaces do not use the traditional Win32 type names used in the corresponding Microsoft header files; instead, they follow the naming and style conventions used in the rest of the OSKit. For example, the type representing a 32-bit unsigned integer is called oskit_u32_t instead of DWORD as in Win32, and the type representing the standard COM stream interface is named oskit_stream_t instead of IStream. This is done for two reasons:

However, all of the COM interfaces in the OSKit use the standard COM function calling and interface layout conventions, so that binary-level compatibility with Win32 environments is possible (though it hasn’t been tried yet). Since COM is primarily a binary-level rather than source-level standard, this appeared to be the best approach to retaining compatibility with COM while maximizing the flexibility and ease-of-use of the OSKit.

4.6 COM Header Files

This section describes the general COM-related public header files provided by the OSKit.

4.6.1 com.h: basic COM types and constants

DESCRIPTION

This header file defines various types and other symbols for defining and using COM interfaces. Most of these symbols correspond directly to similar symbols in the Win32 API; however, all of the names are prefixed with oskit_ to avoid conflicts with actual Win32 headers or other symbols used in the client OS environment, and they are named according to the standard OSKit conventions for consistency with the rest of the OSKit.

The oskit_guid structure and corresponding type oskit_guid_t define the format of DCE/COM globally unique identifiers:

struct oskit_guid {

 
 
 
  oskit_u32_t data1; /* Data - often time stamp */
  oskit_u16_t data2; /* Data */
  oskit_u16_t data3; /* Data */
  oskit_u8_t data4[8]; /* Data - often MAC address */

};

Additionally, the related preprocessor macro OSKIT_GUID can be used to declare initializers for GUID structures.

The type oskit_iid_t is defined as an alias for oskit_guid_t, and is specifically used for COM interface identifiers (IIDs).

The following preprocessor symbols are defined for constructing and testing COM error codes:

OSKIT_SUCCEEDED:
Evaluates to true if the error code parameter indicates success (the high bit is zero).
OSKIT_FAILED:
Evaluates to true if the error code parameter indicates failure (the high bit is one).
OSKIT_ERROR_SEVERITY:
Extracts the the severity (success/failure) flag from the supplied error parameter.
OSKIT_ERROR_FACILITY:
Extracts the the facility code (bits 16-30) from the supplied error parameter.
OSKIT_ERROR_CODE:
Extracts the the code portion (low 16 bits) of the supplied error parameter.
OSKIT_S_OK:
Defined as zero, the standard return code for COM methods indicating “all’s well, nothing to report.”
OSKIT_S_TRUE:
Defined as zero, the same as OSKIT_S_OK; this is used when the method returns a true/false flag of some kind on success.
OSKIT_S_FALSE:
Defined as one (which, as a COM error value, still indicates success); used when the method returns a true/false flag of some kind on success. Note that this representation is exactly reversed from normal C conventions for boolean flags; it’s a unfortunate inherited Microsoftism.

Finally, the following macros, whose exact definitions are compiler-specific, are used in declarations of functions and function pointers for COM interfaces, to ensure that the standard COM calling conventions are used:

OSKIT_COMCALL:
Declares a function to use standard COM calling conventions, known as stdcall conventions in the COM specification. This tag must be placed in the function prototype between the return value and the symbol being declared, e.g., oskit_error_t OSKIT_COMCALL query(...). (Note that the GNU C compiler also allows the tag to be placed at the end of the prototype, but this placement is not compatible with other compilers and therefore not recommended.)
OSKIT_COMDECL:
This is simply a shorthand for oskit_error_t OSKIT_COMCALL; it is used in declarations of normal COM methods which return an error code as the result.
OSKIT_COMDECL_U:
This is simply a shorthand for oskit_u32_t OSKIT_COMCALL; it is generally used in declarations of the addref and release methods common to all COM interfaces, which return integer reference counts as their result.
OSKIT_COMDECL_V:
This is simply a shorthand for void OSKIT_COMCALL; it is used in declarations of COM methods having no return value.

4.6.2 error.h: error codes used in the OSKit COM interfaces

DESCRIPTION

This header file defines the type oskit_error_t, representing a COM error status; it is equivalent to the HRESULT type in Win32. It also defines a number of specific error codes that are widely applicable and used throughout the OSKit.

The following symbols correspond directly to standard COM errors, and use the standard values; they differ only in the OSKIT_ prefix added to the names to avoid conflicts with other header files the client may use.

OSKIT_E_UNEXPECTED:
Unexpected error
OSKIT_E_NOTIMPL:
Not implemented
OSKIT_E_NOINTERFACE:
Interface not supported
OSKIT_E_POINTER:
Bad pointer
OSKIT_E_ABORT:
Operation aborted
OSKIT_E_FAIL:
General failure
OSKIT_E_ACCESSDENIED:
Access denied
OSKIT_E_OUTOFMEMORY:
Out of memory
OSKIT_E_INVALIDARG:
Invalid argument

The following symbols correspond to the errno values defined by the 1990 ISO/ANSI C standard:

OSKIT_EDOM:
Argument out of domain
OSKIT_ERANGE:
Result too large

The following symbols correspond to the errno values defined by the 1990 POSIX.1 standard; although many of them are never actually generated by existing OSKit components, the full set is included for completeness:

OSKIT_E2BIG:
Argument list too long
OSKIT_EACCES:
Permission denied
OSKIT_EAGAIN:
Resource temporarily unavailable
OSKIT_EBADF:
Bad file descriptor
OSKIT_EBUSY:
Device busy
OSKIT_ECHILD:
No child processes
OSKIT_EDEADLK:
Resource deadlock avoided
OSKIT_EEXIST:
File exists
OSKIT_EFAULT:
Bad address. This is the same as OSKIT_E_POINTER.
OSKIT_EFBIG:
File too large
OSKIT_EINTR:
Interrupted system call
OSKIT_EINVAL:
Invalid argument. This is the same as OSKIT_E_INVALIDARG.
OSKIT_EIO:
Input/output error
OSKIT_EISDIR:
Is a directory
OSKIT_EMFILE:
Too many open files
OSKIT_EMLINK:
Too many links
OSKIT_ENAMETOOLONG:
File name too long
OSKIT_ENFILE:
Max files open in system
OSKIT_ENODEV:
Operation not supported by device
OSKIT_ENOENT:
No such file or directory
OSKIT_ENOEXEC:
Exec format error
OSKIT_ENOLCK:
No locks available
OSKIT_ENOMEM:
Cannot allocate memory. This is the same as OSKIT_E_OUTOFMEMORY.
OSKIT_ENOSPC:
No space left on device
OSKIT_ENOSYS:
Function not implemented. This is the same as OSKIT_E_NOTIMPL.
OSKIT_ENOTDIR:
Not a directory
OSKIT_ENOTEMPTY:
Directory not empty
OSKIT_ENOTTY:
Inappropriate ioctl
OSKIT_ENXIO:
Device not configured
OSKIT_EPERM:
Operation not permitted. This is the same as OSKIT_E_ACCESSDENIED.
OSKIT_EPIPE:
Broken pipe
OSKIT_EROFS:
Read-only file system
OSKIT_ESPIPE:
Illegal seek
OSKIT_ESRCH:
No such process
OSKIT_EXDEV:
Cross-device link

The following symbols correspond to the errno values added by the 1993 POSIX.1 standard (real-time extensions); although most of them are never actually generated by existing OSKit components, they are included for completeness:

OSKIT_EBADMSG:
Bad message
OSKIT_ECANCELED:
Operation canceled
OSKIT_EINPROGRESS:
Operation in progress
OSKIT_EMSGSIZE:
Bad message buffer length
OSKIT_ENOTSUP:
Not supported

The following symbol corresponds to the errno value added by the 1996 POSIX.1 standard:

OSKIT_ETIMEDOUT:
Operation timed out

The following symbols correspond to the errno values defined by the 1994 X/Open Unix CAE standard, and not defined by one of the above standards. Most of them are related to networking, and are therefore used by the OSKit networking components; a few are not used at all by the OSKit (such as the “reserved” and STREAMS-related codes), but are included for completeness.

OSKIT_EADDRINUSE:
Address in use
OSKIT_EADDRNOTAVAIL:
Address not available
OSKIT_EAFNOSUPPORT:
Address family unsupported
OSKIT_EALREADY:
Already connected
OSKIT_ECONNABORTED:
Connection aborted
OSKIT_ECONNREFUSED:
Connection refused
OSKIT_ECONNRESET:
Connection reset
OSKIT_EDESTADDRREQ:
Destination address required
OSKIT_EDQUOT:
Reserved
OSKIT_EHOSTUNREACH:
Host is unreachable
OSKIT_EIDRM:
Identifier removed
OSKIT_EILSEQ:
Illegal byte sequence
OSKIT_EISCONN:
Connection in progress
OSKIT_ELOOP:
Too many symbolic links
OSKIT_EMULTIHOP:
Reserved
OSKIT_ENETDOWN:
Network is down
OSKIT_ENETUNREACH:
Network unreachable
OSKIT_ENOBUFS:
No buffer space available
OSKIT_ENODATA:
No message is available
OSKIT_ENOLINK:
Reserved
OSKIT_ENOMSG:
No message of desired type
OSKIT_ENOPROTOOPT:
Protocol not available
OSKIT_ENOSR:
No STREAM resources
OSKIT_ENOSTR:
Not a STREAM
OSKIT_ENOTCONN:
Socket not connected
OSKIT_ENOTSOCK:
Not a socket
OSKIT_EOPNOTSUPP:
Operation not supported on socket
OSKIT_EOVERFLOW:
Value too large
OSKIT_EPROTO:
Protocol error
OSKIT_EPROTONOSUPPORT:
Protocol not supported
OSKIT_EPROTOTYPE:
Socket type not supported
OSKIT_ESTALE:
Reserved
OSKIT_ETIME:
Stream ioctl timeout
OSKIT_ETXTBSY:
Text file busy
OSKIT_EWOULDBLOCK:
Operation would block

4.7 oskit_iunknown: base interface for all COM objects

The oskit_iunknown interface, known as IUnknown in the Win32 API, serves as the basis for all other COM interfaces; it provides the following three methods which all COM interfaces are required to support:

query:
Query for a different interface to the same object.
addref:
Increment the reference count on the interface.
release:
Decrement the reference count on the interface.

4.7.1 query: Query for a different interface to the same object

SYNOPSIS

#include <oskit/com.h>

OSKIT_COMDECL query(oskit_iunknown_t *obj, const oskit_iid_t *iid, [out] void **ihandle);

DESCRIPTION

Given a reference to any of an object’s COM interfaces, this method allows the client to obtain a reference to any of the other interfaces the object exports by querying for a specific interface identifier (IID).

PARAMETERS
obj:
The object being queried.
iid:
The interface identifier of the requested interface.
ihandle:
On success, the requested interface pointer is returned in this parameter. The client must release the returned reference when it is no longer needed.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error. Usually the only error code this method returns is OSKIT_E_NOINTERFACE, indicating that the object does not support the requested interface.

4.7.2 addref: Increment an interface’s reference count

SYNOPSIS

#include <oskit/com.h>

OSKIT_COMDECL_U addref(oskit_iunknown_t *obj);

DESCRIPTION

This method adds one to the interface’s reference count (or to the object’s reference count, if the object implements only one counter for all its interfaces). A corresponding call must later be made to the release method.

PARAMETERS
obj:
The interface on which to increment the reference count.
RETURNS

Returns the new reference count. This return code should only be used for debugging purposes, as its value is generally unstable at run time and its behavior depends on the object’s implementation.

4.7.3 release: Release a reference to an interface

SYNOPSIS

#include <oskit/com.h>

OSKIT_COMDECL_U release(oskit_iunknown_t *obj);

DESCRIPTION

This method decrements the interface’s reference count (or the object’s reference count, if the object implements only one counter for all its interfaces). The object destroys itself if its reference count drops to zero. Note that the client must be careful never to release a reference too many times, or to release a reference to a different interface from the one on which addref was called, or chaos will surely ensue.

PARAMETERS
obj:
The interface on which to release a reference.
RETURNS

Returns the new reference count. This return code should only be used for debugging purposes, as its value is generally unstable at run time and its behavior depends on the object’s implementation.

4.8 oskit_stream: standard interface for byte stream objects

The oskit_stream COM interface supports reading and writing to stream objects, and corresponds to the Microsoft COM IStream interface2.

The oskit_stream COM interface inherits from oskit_iunknown, and has the following additional methods:

read:
Read from this object, starting at the current seek pointer.
write:
Write to this object, starting at the current seek pointer.
seek:
Change the seek pointer of this object.
setsize:
Change the size of this object.
copyto:
Copy from this object to another stream object.
commit:
Commit all changes to this object.
revert:
Revert to last committed version of this object.
lockregion:
Lock a region of this object.
unlockregion:
Unlock a region of this object.
stat:
Get attributes of this object.
clone:
Create a new stream object for the same underlying object.

4.8.1 read: Read from this stream, starting at the seek pointer

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_read(oskit_stream_t *f, void *buf, oskit_u32_t len, [out] oskit_u32_t *out_actual);

DESCRIPTION

This method reads no more than len bytes into buf from this stream, starting at the current seek pointer of this stream. out_actual is set to the actual number of bytes read.

PARAMETERS
f :
The object from which to read.
buf :
The buffer into which the data is to be copied.
len:
The maximum number of bytes to read.
out_actual:
The actual number of bytes read.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.2 write: Write to this stream, starting at the seek pointer

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_write(oskit_stream_t *f, const void *buf, oskit_u32_t len, [out] oskit_u32_t *out_actual);

DESCRIPTION

This method writes no more than len bytes from buf into this stream, starting at the current seek pointer of this stream. out_actual is set to the actual number of bytes written.

PARAMETERS
f :
The object to which to write.
buf :
The buffer from which the data is to be copied.
len:
The maximum number of bytes to write.
out_actual:
The actual number of bytes written.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.3 seek: Change the seek pointer of this stream

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_seek(oskit_stream_t *f, oskit_s64_t ofs, oskit_seek_t whence, [out] oskit_u64_t *out_newpos);

DESCRIPTION

This method changes the seek pointer of this stream. If whence is OSKIT_SEEK_SET, then ofs is used as the new seek pointer value. If whence is OSKIT_SEEK_CUR, then the new seek pointer value is set to the sum of ofs and the former seek pointer value. If whence is OSKIT_SEEK_END, then the new seek pointer value is set to the sum of ofs and the size of the stream object. The new seek pointer value is returned via out_newpos.

PARAMETERS
f :
The object whose seek pointer is to be changed.
ofs:
The relative offset used in computing the new seek pointer.
whence:
The location that ofs to which ofs is relative.
out_newpos:
The new seek pointer value.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.4 setsize: Set the size of this object

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_setsize(oskit_stream_t *f, oskit_u64_t new_size);

DESCRIPTION

This method sets the size of this stream to new_size bytes. If new_size is larger than the former size of this stream, then the contents of the stream between its former end and its new end are undefined.

The seek pointer is not affected by this method.

PARAMETERS
f :
The object whose size is to be changed.
new_size:
The new size in bytes for this object.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.5 copyto: Copy data from this object to another stream object

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_copyto(oskit_stream_t *f, oskit_stream_t *dst, oskit_u64_t size, [out] oskit_u64_t *out_read, [out] oskit_u64_t *out_written);

DESCRIPTION

This method copies size bytes from the current seek pointer in this stream to the current seek pointer in dst.

Both seek pointers are updated by this method. This method is functionally equivalent to performing an oskit_stream_read on the source stream followed by an oskit_stream_write on the destination stream.

PARAMETERS
f :
The source stream from which to copy.
dst:
The destination stream to which to copy.
size:
The number of bytes to copy.
out_read:
The actual number of bytes read from the source.
out_written:
The actual number of bytes written to the destination.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.6 commit: Commit all changes to this object

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_commit(oskit_stream_t *f, oskit_u32_t commit_flags);

DESCRIPTION

This method flushes all changes made to this stream object to the next level storage object.

PARAMETERS
f :
The object to commit.
commit_flags:
Conditions for performing the commit operation.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.7 revert: Revert to last committed version of this object

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_revert(oskit_stream_t *f);

DESCRIPTION

This method changes the state of this stream object to its last committed state if the stream is a transacted object. Otherwise, this method does nothing.

PARAMETERS
f :
The object to revert.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.8 lockregion: Lock a region of this object

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_lockregion(oskit_stream_t *f, oskit_u64_t offset, oskit_u64_t size, oskit_u32_t lock_type);

DESCRIPTION

This method locks a range of this stream object, where the range starts at the specified byte offset and extends for the specified size bytes.

PARAMETERS
f :
The object to lock.
offset:
The starting byte offset of the range to be locked.
size:
The length in bytes of the range.
lock_type:
The type of lock to apply.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.9 unlockregion: Unlock a region of this object

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_unlockregion(oskit_stream_t *f, oskit_u64_t offset, oskit_u64_t size, oskit_u32_t lock_type);

DESCRIPTION

This method unlocks a range of this stream object, where the range starts at the specified byte offset and extends for the specified size bytes.

The parameters must match the parameters used in a prior oskit_stream_lockregion call.

PARAMETERS
f :
The object to unlock.
offset:
The starting byte offset of the range to be unlocked.
size:
The length in bytes of the range.
lock_type:
The type of lock to release.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.10 stat: Get attributes of this object

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_stat(oskit_stream_t *f, [out] oskit_stream_stat_t *out_stat, oskit_u32_t stat_flags);

DESCRIPTION

This method returns the attributes of this stream object. out_stat is a pointer to an oskit_stream_stat_t structure, defined as follows:

struct oskit_stream_stat {

  oskit_char_t *name; /* string name (optional) */
  oskit_u32_t type; /* type of object */
  oskit_u64_t size; /* size in bytes */

};
PARAMETERS
f :
The object whose attributes are desired.
out_stat:
The attributes of the stream object.
stat_flags:
Which attributes to obtain.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.8.11 clone: Create a new stream object for the same underlying object

SYNOPSIS

#include <oskit/com/stream.h>

OSKIT_COMDECL oskit_stream_clone(oskit_stream_t *f, [out] oskit_stream_t **out_stream);

DESCRIPTION

This method creates a new stream object for the same underlying object, with a distinct seek pointer. The seek pointer of the new object is initially set to the current seek pointer of this object.

Subsequent modifications of data within one stream object are visible to readers of the other object; likewise, locking on either object affects the other object.

PARAMETERS
f :
The object to be cloned.
out_stream:
The new stream object
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.9 oskit_listener: callback interface for event notification

The oskit_listener interface encapsulates an event notification function. When associated with another object, the creator of the oskit_listener may be informed whenever an event occurs on that object. Associating an oskit_listener with some object is done via methods on the object. See the description of the Asynchronous I/O interface (Section 7.2) for an example use of this interface. The oskit_listener COM interface inherits from oskit_iunknown, and has the following additional method:

notify:
Inform a listener that an event of interest has occurred.

The oskit_create_listener function can be used to create instances of the listener interface.

4.9.1 create: Create a new listener object

SYNOPSIS

#include <oskit/com/listener.h>

oskit_listener_t *oskit_listener_create( oskit_listener_callback_t *handler, void *arg);

DESCRIPTION

Create a new oskit_listener object. Whenever the notify method is invoked on the object, the function handler will be called with the parameter passed to notify and arg.

PARAMETERS
handler:
Function to be called when the listener object is notified.
arg:
Parameter to pass to function.
RETURNS

Returns a pointer to the new listener object, or 0 if none could be allocated.

4.9.2 notify: Inform a listener that an event of interest has occurred

SYNOPSIS

#include <oskit/com/listener.h>

OSKIT_COMDECL oskit_listener_notify(oskit_listener_t *l, oskit_iunknown_t *obj);

DESCRIPTION

Invokes a listener’s event notification function. The obj parameter should indicate the COM object with which the listener was associated.

PARAMETERS
l:
The listener object.
obj:
The associated COM object.
RETURNS

Returns the result of the notification function which should be 0 on success, or an error code as specified in <oskit/error.h>.

4.10 oskit_listener_mgr: Interface for managing multiple listeners

The oskit_listener_mgr support routines can be used to create and manage lists of listener objects. An object supporting listeners can create an oskit_listener_mgr per event, add and remove oskit_listener objects, and notify listeners en masse using this interface. Note: this is not a COM interface but could (and probably should) be cast as such in the future. See listenter_fanout.[hc] as a possible alternative.

4.10.1 create: Create a listener manager instance

SYNOPSIS

#include <oskit/com/listener_mgr.h>

struct listener_mgr *oskit_create_listener_mgr( oskit_iunknown_t *obj);

DESCRIPTION

Create a manager object associated with obj. The obj parameter will be passed to all listener notify methods when oskit_listener_mgr_notify is called.

PARAMETERS
obj:
Object with which the listeners are associated.
RETURNS

Returns a pointer to the new listener manager object, or 0 if none could be allocated.

4.10.2 destroy: Destroy a listener manager instance

SYNOPSIS

#include <oskit/com/listener_mgr.h>

void oskit_destroy_listener_mgr(struct listener_mgr *mgr);

DESCRIPTION

Destroys the indicated manager object. All listeners associated with the manager are removed with oskit_listener_mgr_remove.

PARAMETERS
mgr:
Manager object.

4.10.3 add: Add a listener to a manager

SYNOPSIS

#include <oskit/com/listener_mgr.h>

oskit_error_t oskit_listener_mgr_add(struct listener_mgr *mgr, oskit_listener_t *l);

DESCRIPTION

Associate a listener object with a manager.

PARAMETERS
mgr:
Manager object.
l:
Listener object to add.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.10.4 remove: Remove a listener from a manager

SYNOPSIS

#include <oskit/com/listener_mgr.h>

oskit_error_t oskit_listener_mgr_remove(struct listener_mgr *mgr, oskit_listener_t *l);

DESCRIPTION

Disassociates a listener object from a manager.

PARAMETERS
mgr:
Manager object.
l:
Listener object to remove.
RETURNS

Returns 0 on success, or an error code specified in <oskit/error.h>, on error.

4.10.5 notify: Notify all listeners associated with a manager

SYNOPSIS

#include <oskit/com/listener_mgr.h>

void oskit_listener_mgr_notify(struct listener_mgr *mgr);

DESCRIPTION

Invokes the notify method on all listeners associated with the given manager.

PARAMETERS
mgr:
Manager object.

4.10.6 count: Return the number of listeners associated with a manager

SYNOPSIS

#include <oskit/com/listener_mgr.h>

int oskit_listener_mgr_count(struct listener_mgr *mgr);

DESCRIPTION

Returns the number of listeners associated with this manager.

PARAMETERS
mgr:
Manager object.
RETURNS

Returns the number of listeners.