DirXML™ Native Driver Frequently-Asked-Questions (FAQ)

Contents


Where do I start?

If you have not already, read the general DirXML FAQ. The DirXML FAQ covers items of general import to driver writers and introduces the interfaces that DirXML uses to communicate with a driver. The C++ interfaces that DirXML uses are the same as the Java interfaces except for language-specific items such as pointers and memory management. This document focuses on items specifically of interest to driver writers who wish to use C++ for their drivers.


What is a native driver?

A native driver is a DirXML driver that is written in C++ as opposed to Java. As such, it runs using native, compiled code instead of Java byte-code.


How does a native driver differ from a Java driver?

The primary differences between a native driver and a Java driver are the native interfaces' use of pointers and the need to dispose of XML documents that the native driver creates. In addition, a native driver can more easily cause DirXML and NDS to crash because the native driver runs in the NDS process space and DirXML does not have the benefit of the Java Virtual Machine to insulate DirXML and NDS from an errant driver.

Note, however, that DirXML does take some precautions when calling into a native driver: All calls into the native driver are protected by a try-catch block that catches all exceptions. If an exception is caught by this try-catch block (e.g., the driver didn't catch it), the driver is shut down. On Win32 this works well. On NetWare however, it does not work at all. At the time of this writing it is unknown how well this works on the Unix platforms. This mechanism cannot, however, protect against a native driver writing over process memory.

Future versions of DirXML will run drivers in a separate address space from NDS, but in the meantime, native driver writers must be very careful to verify that their code functions properly.


When should I write a native driver instead of a Java driver?

In general, if the target application for the driver supports a Java interface, or a remote interface that can be accessed from Java (e.g., an IP interface accessible with sockets) then the driver should be written in pure Java. Writing the driver in pure Java means that the driver should be able to run unchanged on any platform on which DirXML runs.

However, for many applications the only interface available is through native library calls on the application platform (e.g., an interface .dll is provided). For such cases it is typically far more convenient to write the driver in C++ than it is to write the driver in Java with a JNI layer to access the native library.


What are the native driver interfaces?

The native driver interfaces are declared in the following files (the actual header files are found in the DirXML SDK):

The native driver primary interfaces are very similar to the Java interfaces. For detailed discussion of each interface see the relevant section in the general FAQ. The native driver interfaces are defined using macros which expand into C++ class declarations.

Character strings and the interfaces

Virtually all the characters strings used by the DirXML interfaces are null-terminated UTF-16 encoded character strings. The underlying primitive type is an unsigned short, which is typedef'd to the identifier unicode. The only char strings used are in some auxiliary interfaces and functions that deal with filenames.

Interfaces in C++

If a C++ file is being compiled the interface and its methods appear as a virtual base class. For example, the following code references the XmlDocument::getDocument() interface method in C++:

     DOM::Document document = inputDoc->getDocument();

Namespaces

In several DirXML header files you will see a construct similar to:

     NAMESPACE(DOM)Element * element
The "NAMESPACE(DOM)" macro expands to "DOM::" when compiling C++ on most platforms. If you are writing a C++ driver it is typically easier to use a "using namespace" directive at the top of your source file than using the NAMESPACE macro.

Interfaces implemented by the driver

The CreateDriver function

In addition to the above interfaces your native driver implementation module (dll, nlm, or shared library) must export a single function called CreateDriver. This entry point is called by DirXML to get the DriverShim interface pointer. The CreateDriver entry point takes no parameters and returns a pointer to DriverShim. On Win32 platforms the function may be exported as "CreateDriver", "_CreateDriver", or "_CreateDriver@0" due to differences in compiler name generation. On NetWare the function name must have the uppercase name of the NLM appended to it (e.g., CreateDriverCOOLDRVR if the driver is implemented in COOLDRVR.NLM).

Interfaces implemented by DirXML


What is the life-cycle of a native driver?

The life-cycle of a native driver is very similar to the life-cycle of a Java driver. The differences are in how the driver is created and in the fact that DirXML notifies the driver when it is no longer needed so that the driver can perform resource cleanup (such notification is not needed in Java since the JVM handles deallocation, etc.).

Creating the driver

To create a native driver DirXML loads the driver's implementation module (dll, nlm, or shared library) and calls the exported CreateDriver function. The CreateDriver function creates an instance of the native driver object that implements DriverShim and returns a pointer to the object. DirXML then uses the DriverShim methods exactly as described in the general FAQ.

Destroying the driver

After DirXML has called DriverShim::shutdown() and after the Publisher thread has returned from PublicationShim::start() DirXML calls the DriverShim::destroy() method. At this point it is safe for the driver to cleanup any resources used and it is safe to delete the driver object. In fact, the most common way of implementing the destroy() method in C++ is:

					 void
					 DriverShimImpl::destroy()
					 {
						  //destructor handles resource cleanup
						  delete this;
					 }
				
Note that DriverShim::destroy() may be called on either the Publisher thread or on the Subscriber thread. DirXML will call destroy() on the Publisher thread after the Publisher returns from PublicationShim::start() if the Publisher thread does not return from PublicationShim::start() within 30 seconds of the call to DriverShim::shutdown().

In addition note that there is only a destroy() method on the DriverShim interface. This means that when destroy() is called that the DriverShim implementation is responsible for cleaning up any resources used by the Subscriber and Publisher implementations, if such resources have not previously been cleaned up (in a typical implementation the driver object destructor will destroy the Subscriber and Publisher objects).


How do I use the XmlDocument interface?

The XmlDocument interface is used by DirXML to encapsulate an XML document so as to hide the underlying document representation. For example, your driver might find it most useful to receive the XML document as a series of SAX events. The XmlDocument interface allows your driver to do this regardless of the underlying document representation.

The XmlDocument interface provides methods for getting and setting the document in the following forms:

Getting the XML document as a DOM tree

The following C++ code snippet illustrates getting the XML document from an XmlDocument interface as a DOM tree:

					 XmlDocument *
					 SubscriptionShimImpl::execute(XmlDocument * inputDoc
					 XmlQueryProcessor * query)
					 {
						  DOM::Document * document = inputDoc->getDocument();
						  ...
					 }
				

Getting the XML document as SAX events

The following C++ code snippet illustrates getting the XML document from an XmlDocument interface as a series of SAX events:

					 XmlDocument *
					 SubscriptionShimImpl::execute(XmlDocument * inputDoc, XmlQueryProcessor * query)
					 {
						  SAX::Parser * eventGenerator;
						  SAX::InputSource * inputSource;
						  eventGenerator = inputDoc->getDocumentSAX();
						  inputSource = inputDoc->getDocumentInputSource();
						  //my event handler is a class that implements the DocumentHandler interface
						  //declared in sax.h
						  eventGenerator->setDocumentHandler(myEventHandler);
						  //this call causes the SAX events to be sent to the document handler
						  eventGenerator->parse(inputSource);
						  ...
					 }
				

Getting the XML document as a byte array

The following C++ code snippet illustrates getting the XML document from an XmlDocument interface as a byte array:

					 XmlDocument *
					 SubscriptionShimImpl::execute(XmlDocument * inputDoc
					 XmlQueryProcessor * query)
					 {
						  static const unicode ENCODING[] = {'U','T','F','-','8',0};
						  const unsigned char * bytes;
						  int length;
						  bytes = inputDoc->getDocumentBytes(ENCODING, LITTLE_ENDIAN, &length);
						  ...
					 }
				


How do I create an XML document?

The XmlDocument interface provides three primary methods of creating an XML document. The XML document may be created from a Document Object Model (DOM) tree, Simple Api for Xml (SAX) events or from a byte array containing the serialized XML document. Examples of each follow:

Creating the XML document from a DOM tree

The following C++ snippet illustrates creating an XmlDocument using the DOM (the factory methods are defined in InterfaceFactory.h):

					 //create a document using DirXML factory method
					 DOM::Document * document = Document_new();
					 //create the document tree
					 DOM::Element * ndsElement = document->createElement(NDS_ELEMENT_NAME);
					 ...
					 //create the XmlDocument instance
					 XmlDocument * returnDoc = XmlDocument_newFromDOM(document);
					 ...
					 //sometime later, after returnDoc is no longer being used:
					 XmlDocument_destroy(returnDoc);
					 document->destroy();
				

Creating the XML document from SAX events

The following C++ snippet illustrates creating an XmlDocument using SAX events. For ease of illustration, the SAX event source is the DirXML supplied XML parser, using a file as input:

					 SAX::InputSource * inputSource = InputSource_new();
					 inputSource->setSystemId("tempfile.xml");
					 SAX::Parser * parser = Parser_new();
					 XmlDocument * xmlDoc = XmlDocument_newFromSAX(parser, inputSource);
					 ...
					 //sometime later, after xmlDoc is no longer being used:
					 XmlDocument_destroy(xmlDoc);
					 Parser_destroy(parser);
					 InputSource_destroy(inputSource);
				

Creating the XML document from a byte array

The following C++ snippet illustrates creating an XmlDocument from a byte array:

					 static const unicode ENCODING[] = {'U','T','F','-','8',0};
					 //make a driver-specific call to get the serialized XML from somewhere
					 int length;
					 unsigned char * bytes = get_bytes_from_somewhere(&length);
					 //create the XmlDocument from the serialized XML
					 XmlDocument * xmlDoc = XmlDocument_newFromBytes(bytes,length,ENCODING,LITTLE_ENDIAN);
					 ...
					 //sometime later, when xmlDoc is no longer in use...
					 XmlDocument_destroy(xmlDoc);
					 release_bytes_from_somewhere(bytes);
				


What about Memory Management?

As with any C++ program native drivers must carefully manage the objects they create. What this means primarily is that a native driver must handle destroying any XML document objects the driver creates, and must not destroy any XML document objects passed to the driver. This requires management for two primary areas:

Any other objects that the driver creates are also the driver's responsibility to destroy. For example, if the driver calls FileOutputStream_newFromName() then the driver must also call FileOutputStream_destroy() with the returned object when it is no longer used.


Are there any utility functions available?

There are a number of utility functions available. These fall into the following general categories:

Interface factories and destructors

DirXML supplies implementations of all interfaces required. The interface factories and corresponding destructors are defined in InterfaceFactory.h. If a factory method does not have a corresponding destructor method it is because the destructor method is defined in the interface itself (see, for example, Node::destroy()).

Note that a driver writer is free to implement the interfaces for passing data to DirXML from the driver if such implementations work better for a particular driver. However, all interfaces passed to the driver from DirXML will use DirXML's underlying implementation.

Encoding support

The encoding support functions are for converting to and from Base64 encoding and for converting between UTF-8 and UTF-16 encoding. Base64 encoding is how DirXML encodes binary data in the XML documents used for encoding commands and events. UTF-8 and UTF-16 are two encoding methods for Unicode characters using 8- and 16-bit encoding units, respectively. Base64 functions are declared in Base64Codec.h and the UTF functions are in UTFConverter.h.

XDS Support

XDS (XML Directory Services) is the name of the XML dialect used by DirXML. The XDS support functions allow access to the strings necessary to create a valid XDS document and provide simple methods to create empty XDS input and output documents. The XDS support functions are in NdsDtd.h.


With which libraries do I link my native driver?

The import libraries are found in the DirXML SDK.


How do I debug a native driver?

This varies by platform.

Win32

NetWare