documentation update

This commit is contained in:
2018-08-06 17:40:49 +02:00
parent 03d6d9672e
commit bb66a49ec1
4 changed files with 195 additions and 98 deletions

View File

@ -19,7 +19,7 @@
href="http://www.aps.anl.gov/epics/modules/soft/asyn/">
<em>asynDriver</em></a>.
You should first try to implement your bus driver compatible to
<em>asynDriver</em>.
<em>asynOctet</em>.
Then it can be used by <em>StreamDevice</em> automatically.
Only if that does not work, write your own bus interface.
</p>
@ -68,17 +68,13 @@ class MyInterface : StreamBusInterface
// StreamBusInterface virtual methods
bool <a href="#lock">lockRequest</a>(unsigned long lockTimeout_ms);
bool <a href="#lock">unlock</a>();
bool <a href="#write">writeRequest</a>(const void* output, size_t size,
unsigned long writeTimeout_ms);
bool <a href="#read">readRequest</a>(unsigned long replyTimeout_ms,
unsigned long readTimeout_ms,
long expectedLength, bool async);
bool <a href="#write">writeRequest</a>(const void* output, size_t size, unsigned long writeTimeout_ms);
bool <a href="#read">readRequest</a>(unsigned long replyTimeout_ms, unsigned long readTimeout_ms, long expectedLength, bool async);
bool <a href="#read">supportsAsyncRead</a>();
bool <a href="#event">supportsEvent</a>();
bool <a href="#event">acceptEvent</a>(unsigned long mask,
unsigned long replytimeout_ms);
bool <a href="#connect">connectRequest</a>(unsigned long connecttimeout_ms);
bool <a href="#connect">disconnect</a>();
bool <a href="#event">acceptEvent</a>(unsigned long mask, unsigned long timeout_ms);
bool <a href="#connect">connectRequest</a>(unsigned long timeout_ms);
bool <a href="#connect">disconnectRequest</a>();
void <a href="#lock">finish</a>();
public:
@ -132,13 +128,13 @@ bool <a href="#event">supportsEvent</a>();
</code></div>
<div class="indent"><code>
bool <a href="#event">acceptEvent</a>(unsigned&nbsp;long&nbsp;mask,
unsigned&nbsp;long&nbsp;replytimeout_ms);
unsigned&nbsp;long&nbsp;timeout_ms);
</code></div>
<div class="indent"><code>
bool <a href="#connect">connectRequest</a>(unsigned&nbsp;long&nbsp;connecttimeout_ms);
bool <a href="#connect">connectRequest</a>(unsigned&nbsp;long&nbsp;timeout_ms);
</code></div>
<div class="indent"><code>
bool <a href="#connect">disconnect</a>();
bool <a href="#connect">disconnectRequest</a>();
</code></div>
<div class="indent"><code>
void <a href="#lock">finish</a>();
@ -157,24 +153,24 @@ callback methods which must be called in response to the above request
methods (most probably from another thread):
</p>
<div class="indent"><code>
void <a href="#lock">lockCallback</a>(StreamIoStatus&nbsp;status);
void <a href="#lock">lockCallback</a>(StreamIoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<div class="indent"><code>
void <a href="#write">writeCallback</a>(StreamIoStatus&nbsp;status);
void <a href="#write">writeCallback</a>(StreamIoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<div class="indent"><code>
long <a href="#read">readCallback</a>(StreamIoStatus&nbsp;status,
const&nbsp;void*&nbsp;input&nbsp;=&nbsp;NULL,
long&nbsp;size&nbsp;=&nbsp;0);
ssize_t <a href="#read">readCallback</a>(StreamIoStatus&nbsp;status,
const&nbsp;void*&nbsp;buffer&nbsp;=&nbsp;NULL,
size_t&nbsp;size&nbsp;=&nbsp;0);
</code></div>
<div class="indent"><code>
void <a href="#event">eventCallback</a>(StreamIoStatus&nbsp;status);
void <a href="#event">eventCallback</a>(StreamIoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<div class="indent"><code>
void <a href="#connect">connectCallback</a>(StreamIoStatus&nbsp;status);
void <a href="#connect">connectCallback</a>(StreamIoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<div class="indent"><code>
void <a href="#connect">disconnectCallback</a>(StreamIoStatus&nbsp;status);
void <a href="#connect">disconnectCallback</a>(StreamIoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<h3>Other provided methods, attibutes, and types</h3>
@ -197,6 +193,9 @@ const&nbsp;char*&nbsp;<a href="#read">getInTerminator</a>(size_t&&nbsp;length);
<div class="indent"><code>
enum&nbsp;StreamIoStatus {StreamIoSuccess, StreamIoTimeout, StreamIoNoReply, StreamIoEnd, StreamIoFault};
</code></div>
<div class="indent"><code>
const char* ::toStr(StreamIoStatus);
</code></div>
<a name="theory"></a>
<h2>Theory of Operation</h2>
@ -276,34 +275,47 @@ correct.
<a name="connect"></a>
<h3>Connecting and disconnecting</h3>
<div class="indent"><code>
bool connectRequest(unsigned&nbsp;long&nbsp;connecttimeout_ms);
bool connectRequest(unsigned&nbsp;long&nbsp;timeout_ms);
</code></div>
<div class="indent"><code>
bool disconnect();
bool disconnectRequest();
</code></div>
<div class="indent"><code>
void connectCallback(IoStatus&nbsp;status);
void connectCallback(IoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<div class="indent"><code>
void disconnectCallback(IoStatus&nbsp;status);
void disconnectCallback(IoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<p>
Connection should be handled automatically.
If the device is disconnected, each attempt to access the
device should try to (re-)connect.
Whenever possible connection should be handled automatically.
The interface should call <code>connectCallback()</code> when
the device has connected and <code>disconnectCallback()</code> when
the device has disconnected.
These callbacks can be called asynchronously at any time.
</p>
<p>
If the device is disconnected, an attempt to access the
device should try to reconnect.
Normally, the interface should not try to disconnect unless
the device does so.
the device does so automatically.
</p>
<p>
However, sometimes the client wants to connect or
disconnect explicitely.
disconnect explicitly.
To connect, the client calls <code>connectRequest()</code>.
This function should return <code>true</code> immediately
or <code>false</code> if the request cannot be accepted or connection
This function should set up things to reconnect but should not block
waiting.
Instead it should immediately return <code>true</code> if
it expects that connection can be established soon, or
<code>false</code> if the request cannot be accepted or connection
handling is not supported.
The interface should call <code>connectCallback(StreamIoSuccess)</code>
The interface should call <code>connectCallback()</code>
once the bus could be connected.
If the bus cannot be connected within <code>connecttimeout_ms</code>
If the device can connect immediately without waiting, it may also call
<code>connectCallback()</code> directly from <code>connectRequest()</code>.
</p>
<p>
If the bus cannot be connected within <code>timeout_ms</code>
milliseconds, the bus interface should call
<code>connectCallback(StreamIoTimeout)</code>.
</p>
@ -313,11 +325,11 @@ something wrong with the I/O hardware,
<code>connectCallback(StreamIoFault)</code> may be called.
</p>
<p>
To disconnect, the client calls <code>disconnectRequest()</code>;
To disconnect explicitly, the client calls <code>disconnectRequest()</code>;
This function should return <code>true</code> immediately or
<code>false</code> if the request cannot be accepted or connection
handling is not supported.
The interface should call <code>connectCallback(StreamIoSuccess)</code>
The interface should call <code>connectCallback()</code>
once the bus is disconnected. There is no timeout for this operation.
If disconnecting is impossible, the interface should call
<code>connectCallback(StreamIoFault)</code>.
@ -326,10 +338,10 @@ If disconnecting is impossible, the interface should call
<a name="lock"></a>
<h3>Bus locking</h3>
<div class="indent"><code>
bool lockRequest(unsigned&nbsp;long&nbsp;lockTimeout_ms);
bool lockRequest(unsigned&nbsp;long&nbsp;timeout_ms);
</code></div>
<div class="indent"><code>
void lockCallback(IoStatus&nbsp;status);
void lockCallback(IoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<div class="indent"><code>
bool unlock();
@ -348,7 +360,7 @@ or <code>false</code> if the request cannot be accepted.
If the device is already locked, the bus interface should add itself to
a queue, sorted by <code>priority()</code>.
As soon as the device is available, the bus interface should call
<code>lockCallback(StreamIoSuccess)</code>.
<code>lockCallback()</code>.
If the bus cannot be locked within <code>lockTimeout_ms</code>
milliseconds, the bus interface should call
<code>lockCallback(StreamIoTimeout)</code>.
@ -369,7 +381,7 @@ locked the device.
When the protocol ends and the device is locked, the client calls
<code>unlock()</code>.
If other bus interfaces are in the lock queue, the next one should
call <code>lockCallback(StreamIoSuccess)</code> now.
call <code>lockCallback()</code> now.
</p>
<p>
The client calls <code>finish()</code> when the protocol ends.
@ -385,7 +397,7 @@ bool writeRequest(const&nbsp;void*&nbsp;output,
size_t&nbsp;size, unsigned&nbsp;long&nbsp;writeTimeout_ms);
</code></div>
<div class="indent"><code>
void writeCallback(IoStatus&nbsp;status);
void writeCallback(IoStatus&nbsp;status = StreamIoSuccess);
</code></div>
<div class="indent"><code>
const&nbsp;char*&nbsp;getOutTerminator(size_t&&nbsp;length);
@ -404,7 +416,7 @@ The function should arrange transmission of <code>size</code> bytes of
or <code>false</code> if the request cannot be accepted.
It must not block until output has completed.
After all output has been successfully transmitted, but not earlier, the
interface should call <code>writeCallback(StreamIoSuccess)</code>.
interface should call <code>writeCallback()</code>.
</p>
<p>
If output blocks for <code>writeTimeout_ms</code> milliseconds,
@ -417,14 +429,15 @@ something wrong with the I/O hardware,
<code>writeCallback(StreamIoFault)</code> may be called.
</p>
<p>
The interface must transmit excactly the <code>size</code> bytes
from <code>output</code>.
It must not change anything and it should not assume that
any bytes have a special meaning.
The interface must send excactly the <code>size</code> bytes
from <code>output</code>, not less.
It should not change anything unless the bus needs some special
formatting (e.g. added header, escaped bytes) and it should not
assume that any bytes have a special meaning.
In particular, a null byte does not terminate <code>output</code>.
</p>
<p>
A call to <code>getOutTerminator</code> tells the interface which
A call to <code>getOutTerminator()</code> tells the interface which
terminator has already been added to the output.
If <code>NULL</code> was returned, the client is not aware of a
terminator (no outTerminator was defined in the protocol).
@ -450,9 +463,9 @@ bool readRequest(unsigned&nbsp;long&nbsp;replyTimeout_ms,
long&nbsp;expectedLength, bool&nbsp;async);
</code></div>
<div class="indent"><code>
long readCallback(IoStatus&nbsp;status,
const&nbsp;void*&nbsp;input&nbsp;=&nbsp;NULL,
long&nbsp;size&nbsp;=&nbsp;0);
ssize_t readCallback(IoStatus&nbsp;status,
const&nbsp;void*&nbsp;buffer&nbsp;=&nbsp;NULL,
size_t&nbsp;size&nbsp;=&nbsp;0);
</code></div>
<div class="indent"><code>
const&nbsp;char*&nbsp;getInTerminator(size_t&&nbsp;length);
@ -487,11 +500,11 @@ The client copies its contents. It does not modify or free it.
It is not necessary to wait until all data has been received.
The bus interface can call <code>n=readCallback()</code> after
any amount of input has been received.
If the client needs more input, <code>readCallback()</code>
If the client expects more input, <code>readCallback()</code>
returns a non-zero value.
A positive <code>n</code> means, the client needs another
A positive <code>n</code> means, the client expects another
<code>n</code> bytes of input.
A negative <code>n</code> means, the client needs an unspecified
A negative <code>n</code> means, the client expects an unspecified
amount of additional input.
</p>
<p>
@ -559,46 +572,43 @@ something wrong with the I/O hardware,
<code>readCallback(StreamIoFault)</code> may be called.
</p>
<p>
If the <code>async</code> flag is <code>true</code>, the client
wants to read input asyncronously without any timeout.
That means, the bus interface should call <code>readCallback()</code>
even if the input was requested by another client.
</p>
<p>
If a client wishes to receive asynchonous input, it first calls
Sometimes a client wishes to get any input received at any time, even
when requested by another client.
If a client wishes to receive such asynchronous input, it first calls
<code>supportsAsyncRead()</code>.
The default implementation of this method always returns
<code>false</code>.
A bus interface may overwrite this method to return <code>true</code>
and eventually prepare for asynchonous input.
If a bus interface supports asynchronous input, it should overwrite
this method to set up everything needed to receive asynchronous input
and then return <code>true</code>.
The client is then allowed to call <code>readRequest()</code> with
the <code>async==true</code>.
This means that the client is interested in any input.
This means that the client is now interested in asynchronous input.
It should receive a <code>readCallback()</code> of all input which came
in response to a synchonous (<code>async==false</code>) request from
another client (which of course should receive the input, too).
The interface should also receive asynchonous input when no
in response to any synchonous (<code>async==false</code>) request from
another client (which should receive the input, too).
The interface should also receive asynchronous input when no
synchonous client is active at the moment.
Many asynchonous <code>readRequest()</code> calls from different clients
Many asynchronous <code>readRequest()</code> calls from different clients
may be active at the same time.
All of them should receive the same input.
</p>
<p>
For asynchonous requests, <code>replyTimeout_ms</code> has a different
For asynchronous requests, <code>replyTimeout_ms</code> has a different
meaning: If the bus interface has to poll the bus for input, it may take
<code>replyTimeout_ms</code> as a hint for the poll period.
If many asynchonous requests are active at the same time, it should poll
If many asynchronous requests are active at the same time, it should poll
with the shortest period of all clients.
An asynchonous request does not time out.
An asynchronous request does not time out.
It stays active until the next input arrives.
The client may reissue the asynchronous <code>readRequest()</code>
from within the <code>readCallback()</code> if it wants to continue
receiving asynchonous input.
receiving asynchronous input.
</p>
<p>
If the client calls <code>finish()</code> at any time, the bus
interface should cancel all outstanding requests, including
asynchonous read requests.
asynchronous read requests.
</p>
<a name="event"></a>
<h3>Handling events</h3>
@ -606,10 +616,10 @@ asynchonous read requests.
bool supportsEvent();
</code></div>
<div class="indent"><code>
bool acceptEvent(unsigned long mask, unsigned long replytimeout_ms);
bool acceptEvent(unsigned long mask, unsigned long timeout_ms);
</code></div>
<div class="indent"><code>
void eventCallback(StreamIoStatus status);
void eventCallback(StreamIoStatus status = StreamIoSuccess);
</code></div>
<p>
An event is a sort of input from a device which is not part of
@ -627,9 +637,9 @@ If <code>true</code> is returned, the client is allowed to call
If <code>mask</code> is illegal, <code>acceptEvent()</code> should
return <code>false</code>.
The call to <code>acceptEvent()</code> must not block.
It should arrange to call <code>eventCallback(StreamIoSuccess)</code>
It should arrange to call <code>eventCallback()</code>
when the event matching <code>mask</code> arrives within
<code>replytimeout_ms</code> milliseconds.
<code>timeout_ms</code> milliseconds.
If no such event arrives within this time, the bus interface
should call <code>eventCallback(StreamIoTimeout)</code>.
</p>

View File

@ -44,7 +44,7 @@ class MyConverter : public StreamFormatConverter
{
int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
bool printLong(const StreamFormat&, StreamBuffer&, long);
int scanLong(const StreamFormat&, const char*, long&);
ssize_t scanLong(const StreamFormat&, const char*, long&);
};
RegisterConverter(MyConverter,"Q");
@ -73,17 +73,17 @@ Provide multiple classes, that's more efficient.
<a name="parsing"></a>
<h3>Parsing</h3>
<div class="indent"><code>
int parse (const&nbsp;StreamFormat&&nbsp;fmt, StreamBuffer&&nbsp;info,
int parse(const&nbsp;StreamFormat&&nbsp;fmt, StreamBuffer&&nbsp;info,
const&nbsp;char*&&nbsp;source, bool&nbsp;scanFormat);
</code></div>
<div class="indent"><code>
struct&nbsp;StreamFormat {
char&nbsp;conv;
StreamFormatType&nbsp;type;
unsigned&nbsp;char&nbsp;flags;
short&nbsp;prec;
unsigned&nbsp;short&nbsp;width;
unsigned&nbsp;short&nbsp;infolen;
unsigned&nbsp;short&nbsp;flags;
long&nbsp;prec;
unsigned&nbsp;long&nbsp;width;
unsigned&nbsp;long&nbsp;infolen;
const&nbsp;char*&nbsp;info;
};
</code></div>
@ -124,12 +124,12 @@ flags set:
</ul>
<p>
It is not necessary that these flags have exactly the same meaning in your
formats, but a similar and intuitive meaning helpful for the user.
formats, but a similar and intuitive meaning is helpful for the user.
</p>
<p>
There are two additional flags, <code>default_flag</code> indicating a
<code>?</code> and <code>compare_flag</code> indicating a <code>=</code>
int the format, that are handled internally by <em>StreamDevice</em> and
in the format, that are handled internally by <em>StreamDevice</em> and
are not of interest to the converter class.
</p>
<p>
@ -163,13 +163,13 @@ This will probably be necessary if you have parsed additional characters
from the format string as in the above example<br>
</p>
<p>
Return <code>long_format</code>, <em>double_format</em>,
<em>string_format</em>, or <code>enum_format</code> depending on the
Return <code>unsigned_format</code>, <code>signed_format</code>,
<code>double_format</code>, <code>string_format</code>, or
<code>enum_format</code> depending on the
datatype associated with the conversion character.
It is not necessary to return the same value for print and for scan
formats.
You can even return different values depending on the format string,
but I can't imagine why anyone should do that.
You can even return different values depending on the format string.
</p>
<p>
If the format is not a real data conversion but does other things with
@ -178,7 +178,8 @@ return <code>pseudo_format</code>.
</p>
<p>
Return <code>false</code> if there is any parse error or if print or scan
is requested but not supported by this conversion.
is requested but not supported by this conversion or flags are used that
are not supported by this conversion.
</p>
<a name="printing_scanning"></a>
@ -191,7 +192,45 @@ That method is called whenever the conversion appears in an output or input,
respectively.
You only need to implement the flavour of print and/or scan suitable for
the datatype returned by <code>parse()</code>.
Both <code>unsigned_format</code> and <code>signed_format</code> will use
the <code>Long</code> flavour.
</p>
</p>
The possible interface methods are:
</p>
<div class="indent"><code>
bool printLong(const&nbsp;StreamFormat&&nbsp;fmt,
StreamBuffer& output, long value);
</code></div>
<div class="indent"><code>
bool printDouble(const&nbsp;StreamFormat&&nbsp;fmt,
StreamBuffer& output, double value);
</code></div>
<div class="indent"><code>
bool printString(const&nbsp;StreamFormat&&nbsp;fmt,
StreamBuffer& output, const&nbsp;char* value);
</code></div>
<div class="indent"><code>
bool printPseudo(const&nbsp;StreamFormat&&nbsp;fmt,
StreamBuffer& output);
</code></div>
<div class="indent"><code>
ssize_t scanLong(const&nbsp;StreamFormat&&nbsp;fmt,
const&nbsp;char* input, long& value);
</code></div>
<div class="indent"><code>
ssize_t scanDouble(const&nbsp;StreamFormat&&nbsp;fmt,
const&nbsp;char* input, double& value);
</code></div>
<div class="indent"><code>
ssize_t scanString(const&nbsp;StreamFormat&&nbsp;fmt,
const&nbsp;char* input, char* value, size_t& size);
</code></div>
<div class="indent"><code>
ssize_t scanPseudo(const&nbsp;StreamFormat&&nbsp;fmt,
StreamBuffer& inputLine, size_t& cursor);
</code></div>
<p>
Now, <code>fmt.type</code> contains the value returned by <code>parse()</code>.
With <code>fmt.info()</code> get access to the string you have written to
@ -203,16 +242,21 @@ The length of the info string can be found in <code>fmt.infolen</code>.
<p>
In <code>print*()</code>, append the converted value to <code>output</code>.
Do not modify what is already in output (unless you really know what you're
doing).
doing, e.g. some <code>printPseudo</code> methods).
Return <code>true</code> on success, <code>false</code> on failure.
</p>
<p>
In <code>scan*()</code>, read the value from input and return the number of
consumed bytes.
In the string version, don't write more bytes than <code>maxlen</code>!
consumed bytes or -1 on failure.
If the <code>skip_flag</code> is set, you don't need to write to
<code>value</code>, since the value will be discarded anyway.
Return <code>-1</code> on failure.
In <code>scanString()</code>, don't write more bytes than
<code>maxlen</code> to <code>value</code> and set <code>size</code> to the
actual string length, which may be different to the number of bytes consumed
(e.g. if leading spaces are skipped).
In <code>scanPseudo()</code>, <code>cursor</code> is the index of the first
byte in <code>inputLine</code> to consider, which may be larger than
<code>0</code>.
</p>
<footer>

View File

@ -15,7 +15,7 @@
<h2>What is <em>StreamDevice</em>?</h2>
<p>
<em>StreamDevice</em> is a generic
<a href="http://www.aps.anl.gov/epics" target="ex">EPICS</a>
<a href="https://www.aps.anl.gov/epics" target="ex">EPICS</a>
device support for devices with a "byte stream" based
communication interface.
That means devices that can be controlled by sending and
@ -52,7 +52,7 @@ It does not provide loops or branches.
</p>
<p>
<em>StreamDevice</em> comes with an interface to<a target="ex"
href="http://www.aps.anl.gov/epics/modules/soft/asyn/">
href="https://www.aps.anl.gov/epics/modules/soft/asyn/">
<em>asynDriver</em></a>
but can be extended to
<a href="businterface.html">support other bus drivers</a>.
@ -82,14 +82,26 @@ primitive commands.
</p>
<p>
It is not a block oriented device support.
It is not possible to send or receive huge blocks of data that contain
It is not intended for huge binary blocks of data that contain
many process variables distributed over many records.
Consider <a href="https://github.com/paulscherrerinstitute/regdev"
target="ex"><em>regDev</em></a>
for that.
</p>
<p>
It is not a very flexible html, xml, json, etc. parser. Data needs to
come in a predictible order to be parsable by <em>StreamDevice</em>.
</p>
<h2>Recommended Readings</h2>
<p>
<a href="http://www.aps.anl.gov/epics/base/R3-14/12-docs/AppDevGuide"
target="ex">IOC Application Developer's Guide</a>
IOC Application Developer's Guide:
<a href="https://www.aps.anl.gov/epics/base/R3-14/12-docs/AppDevGuide"
target="ex">R3.14.12</a>,
<a href="https://www.aps.anl.gov/epics/base/R3-16/1-docs/AppDevGuide"
target="ex">R3.15.5</a>,
<a href="https://www.aps.anl.gov/epics/base/R3-15/5-docs/AppDevGuide"
target="ex">R3.16.1</a>
</p>
<p>
<a href="https://wiki-ext.aps.anl.gov/epics/index.php/RRM_3-14"
@ -105,6 +117,28 @@ This marks text you typically type in configuration files etc.
Longer code segments are often set in a box.
</pre>
<h2>Changes in Version 2.8</h2>
<ul>
<li>Support standard EPICS module build system.
<li>Compatible with EPICS base releases up to 7.0.1.
<ul>
<li>Support for new record types: int64in, int64out, lsi, lso.
<li>Support for INT64 and UINT64 in aai, aao, waveform.
</ul>
<li>Run @init more often (e.g. when device re-connects or paused IOC is resumed).
<li>Use "COMM" error code in .STAT when device is disconnected.
<li>Allow spaces in protocol parameter list.
<li>Errors are new silent by default (var streamError 0) except during init.
<li>Support output redirect of all shell functions.
<li>Fix building shared libraries on Windows.
<li>Fix some C++11 warnings.
<li>Fix several signed/unsigned problems.
<li>Dropped support for cygnus-2.7.2 gcc (used by some old cygwin).
<li>Several bug fixes.
<li>Several documentation updates.
</ul>
<footer>
<a href="setup.html">Next: Setup</a>
Dirk Zimoch, 2018

View File

@ -210,6 +210,15 @@ div div div a {list-style-type:circle;}
</div>
<div>
<a target="_parent" href="formatconverter.html">Format Converter API</a>
<div>
<a target="_parent" href="formatconverter.html#class">Converter Class</a>
<a target="_parent" href="formatconverter.html#theory">Theory of Operation</a>
<div>
<a target="_parent" href="formatconverter.html#registration">Registration</a>
<a target="_parent" href="formatconverter.html#parsing">Parsing</a>
<a target="_parent" href="formatconverter.html#printing_scanning">Printing and Scanning</a>
</div>
</div>
</div>
<div>
<a target="_parent" href="osinterface.html">Operating System API</a>