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.
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.
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.
The native driver interfaces are declared in the following
files (the actual header files are found in the DirXML SDK):
NativeInterface.h
- Defines the primary interfaces implemented by the
native driver, as well as the mechanism used to pass the
XML command and event notification documents.
dom.h - Defines a C++
language binding for the Document Object Model (DOM) 1.0
interfaces.
sax.h - Defines a C++
language binding for the Simple Api for Xml (SAX) 1.0
interfaces
OutputStream.h
- Defines a java-like stream interface used with several
other interfaces.
XMLWriter.h -
Defines an interface returned from XmlDocument
used to control the serialization of XML.
DriverFilter.h
- Defines an interface provided so that drivers can
easily use the publisher and subscriber channel
Event Filters.
Trace.h - Defines an
interface used for drivers to write debugging or
informational messages to the DSTrace screen.
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.
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.
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++:
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.
DriverShim
- The top-level interface responsible for starting up and shutting down the driver.
SubscriptionShim
- The interface responsible for accepting and processing commands from DirXML.
PublicationShim
- The interface responsible for notifying DirXML of events that occur in the application.
XmlQueryProcessor
- The interace passed by the PublicationShim into
XmlCommandProcessor::execute()
when calling DirXML to report an application event.
DirXML uses the XmlQueryProcessor interface to query the
PublicationShim for any additional information DirXML may
need to process the application event.
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).
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.).
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.
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:
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).
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:
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);
...
}
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:
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();
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);
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);
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.
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.
Document_new()
- Creates a new DOM Document object using the DirXML native
implementation.
Trace_new()
- Create an implementation of
Trace. This is
useful for debugging. The Trace
interface causes messages to be written to the DSTrace screen and, optionally, a
file. See DSTrace
for more information.
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.
Base64Codec_encode()
- Encode a byte array into UTF-16 characters using Base64
encoding.
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.
NdsDtd_getStrings()
- Returns a pointer to a structure containing pointers to
const unicode strings. These strings are tag names,
attribute names, and attribute values defined in
nds.dtd. See the actual
include file for the structure declaration.
Attach a debugger to the dhost process, or start dhost.exe
under the debugger. To attach the Visual Studio debugger,
use Build | Start Debug
| Attach to Process...
Set a breakpoint in your driver code. This will require
preloading your driver dll if your driver is not already
running. For Visual Studio, preloaded modules are
configured under Project | Settings...,
Debug tab, Additional DLLs.