Connection Desktop Integration Library (CDIL) Detailed


CDIL Overview
Initializing
Connecting
Reading and Writing
Disconnecting
Disposing
Options


This article details using the Connection Desktop Integration Library (CDIL). It is a "user's guide" when compared to the reference document Newton Desktop Integration Libraries available at the DIL web page. As such it is designed to cover the use of the CDIL in a programmatic "cookbook" fashion. Included in it are various pitfalls and issues which may occur when using the CDIL. For a complete code example using the CDIL you might want to look at the source code examples available from Apple at the DIL web page or the CDIL Archive code included in this course. If you want to get "the big picture" without going into the nitty-gritty details covered in this article, you may wish to try the DIL overview article which describes both the CDIL and the FDIL libraries at a functional level with very little discussion of coding issues.

While this article was written for the 1.0 version of the CDIL, most of what it says should apply both to earlier, beta versions and later releases which will be backwardly compatible.

The CDIL was designed with C++ in mind but is implemented in C. As such the API often uses a "magic cookie", that is a pointed to a pipe "object" that the calls are applied to. Currently the CDIL is implemented for the Macintosh OS and for Windows. For the Macintosh OS it is implemented as a statically linked library suitable for use with the Metrowerks or the MPW environments. The Windows OS version is implemented as a DLL (Dynamically Linked Library) and may be used with any development environment which supports DLLs. These libraries may all be found at the DIL web page.

The CDIL library must be opened and initialized and the appropriate low-level communications tool must be initialized before the routines to connect and transfer data may be called. In the section on initializing the CDIL we discuss what must be done before you can begin to connect and transfer data. On the Macintosh it uses the Communications Toolbox as its low-level API so you may want to get that documentation from Apple either in printed form as part of the Inside Macintosh series of technical publications or from the online website [find website].

The section on connecting in this article describes how to connect a Newton to a desktop machine.

Once a connection has been established, the CDIL pipe can be used to send and receive streams of bytes. The section on reading and writing describes this process.

When done transferring data, the CDIL pipe used must be destroyed. The first step in this process is to disconnect the pipe to break the connection between the Newton and the desktop machine. The section on disconnecting covers this step.

Continuing with the tear-down process, the next step is to dispose of the CDIL pipe and to close the CDIL. This is covered in the section on disposing.

The CDIL is designed to work in a variety of environments and to support many uses. There are a number of features not detailed in the sections listed above which are described in the section on options. Examples of this sort of option include encryption, pipe state information, and other details not covered elsewhere in this article.
Initializing

Before the CDIL may be used it must be opened and initialized. To open the library you must call the routine:

fErr = CDInitCDIL() ; // fErr is a local variable

No DIL calls of any type may precede this call. Any error returned will reflect a problem with the low-level communications code or devices.

Next, a CDIL pipe object must be created using the following call:

ourPipe = CDCreateCDILObject();
// ourPipe is often a global for convenience


This pipe object will be used as an argument in almost all other CDIL routines. If CDCreateCDILObject() fails, it returns a nil pointer.

On Windows machines an additional call must then be made as follows:

// Windows only
CDSetApplication ( ourPipe, applicationInstance );

This call connects an application with the pipe in order to associate driver timers with the application.

Next the pipe must be initialized for the kind of hardware connection which will be used. This is done with a call to the CDIL routine CDPipeInit() which requires hardware specific parameters for initialization. For example, on the Macintosh the following call may be used to initialize the pipe for serial I/O at 38400 Baud with 8 data bits, no parity, connecting through the Modem port.

fErr = CDPipeInit(ourPipe, "Serial","","Baud 38400
dataBits 8 Parity None Port ", "Modem",
kDefaultBufferSize, kDefaultBufferSize);

After this call the pipe is ready to connect to the Newton and begin transferring data. Depending on the hardware and the desktop platform the arguments for this call will differ as necessary. The third argument (the null string "" in this case) is used to assign a logical name for protocols that require a name (such as network protocols). Since in the example shown, serial I/O, no such name is required, the null string is passed in.



Putting this all together we get code like this:

CDILPipe *ourPipe; // global

main ()
{
CommErr fErr; // CommErr is a CDIL defined type

// do platform specific intializations

fErr = CDInitCDIL() ; // we should check fErr
ourPipe = CDCreateCDILObject();

#ifdef _WINDOWS
CDSetApplication ( ourPipe, applicationInstance );
#endif

fErr = CDPipeInit(ourPipe, "Serial","","Baud 38400
dataBits 8 Parity None Port ", "Modem",
kDefaultBufferSize, kDefaultBufferSize);

// once we check fErr we're ready to connect - see
// section on connecting
// and transfer data - see
// section on reading and writing
// tear down CDIL pipe and library instance
// see section on disposing
}
Connecting

On the desktop machine there are two parts to establishing a connection with a Newton. First a connection must be detected and then the connection must be accepted. The following call is used to look for an attempted connection:

fErr = CDPipeListen(ourPipe, kDefaultTimeout, 0, 0);

This call checks on the specified pipe for an attempted connection from a Newton. kDefaultTimeout is a constant defined in the DILCPIPE.H which will be different depending on the desktop platform. For the Macintosh it has a value appropriate for a 30 second default, while the Windows version has a 1 second default.



On Windows machines you should use the following "cookbook code":

startTime = GetTimeInSecs();
timeout = 0;
fErr = CDPipeListen(ourPipe, kDefaultTimeout, 0, 0);
while ( timeout<kTimeout )
{
timeout = GetTimeInSecs()-startTime;
if ( kCDIL_ConnectPending==CDGetPipeState(ourPipe) )
CDPipeAccept(ourPipe);
}

This code essentially watches for completion of the state transition between starting a listen and getting confirmation that contact was made with the target device. This is not necessary on the Macintosh side as this code was included in the CDIL itself so that the state has changed before CDPipeListen returns. Unfortunately this change did not make it into the 1.0 release of the Windows version and so it must be done by hand.

CDPipeListen is the first routine which may be called asynchronously. To make an asynchronous call the third argument should be a non-nil function pointer to a routine which the CDIL will call upon noticing a connection. This callback routine will have one argument which will be the value of the fourth argument. This last argument can be anything that the caller wants it to be as long as it fits into a long integer. This lets the caller do things like communicating state information to the callback routine. For example, when waiting for a Newton to connect the desktop may want to continue to process user events. By passing a state flag of kImListening (or whatever), the application can have a single asynchronous callback routine which takes appropriate action depending on the state passed to it. This would look something like this:

void MyCallback( state )
{
UpdateStateDisplay ( state );
switch ( state )
{
case kImListening:
DoAccept(); // our routine
break;

case kImReading:
HandleIncoming(); // our routine
break;

case kImWriting:
NextOutgoing(); // our routine
break;

// etc.
}
}

// now async calls use the same callback, for example:

CommErr OurConnectRoutine ()
{
fErr = CDPipeListen(ourPipe,-1, MyCallback,
kImListening);
// -1 says no timeout
}

Using this technique the desktop application can be built as a state machine which chains asynchronous calls on the completion of previous calls. While waiting for each completion, the application can continue processing user actions and can update state displays on completion of each async call.

In order to support such asynchronous calls it is necessary to periodically call the CDIL routine CDIdle to check for asynchronous completion. This is typically done in the main event loop and looks like this:

CDIdle ( ourPipe );



After CDPipeListen completes, the desktop application has an option of accepting or denying the connection. A connection is accepted by calling CDPipeAccept. Normally this is called unless there is an error returned by CDPipeListen. The code for CDPipeAccept looks like this:

fErr = CDPipeAccept( ourPipe );

Putting this together with a synchronous call to CDPipeListen, we get the following code to connect to a Newton:

fErr = CDPipeListen(ourPipe, kDefaultTimeout, 0, 0);
if ( !fErr )
fErr = CDPipeAccept( ourPipe );
else
DoOurHandleError(fErr);

Once connected we are ready to begin reading and writing from the desktop machine to the connected Newton.
Reading and Writing

If the aim is to transfer information to and from Newton frames and arrays then the FDIL should be used once the CDIL connection has been established. If data will be transferred as a stream of bytes, the CDIL routines CDPipeRead and CDPipeWrite can be used.

As with CDPipeListen these calls may either be made synchronously or asynchronously. And as with CDPipeListen all that is required to make a call asynchronous is to provide a pointer to a callback routine. These calls look like this:

fErr = CDPipeRead ( ourPipe, (void *)response, &read_len, NULL,kSwapping,kMacRomanEncoding,-1,MyCallback,
kReading );

fErr = CDPipeWrite(ourPipe, (void *)content_str, &length, true,kSwapping,kMacRomanEncoding,-1,MyCallback,
kWriting);

The first argument is the pipe which was opened earlier, the next argument is the buffer the data is being read from or written into, the third argument is the number of bytes requested to be read or written. After the call this length argument will be overwritten by the number of bytes actually read or written. The fourth argument is currently ignored for CDPipeRead but may be used in the future to indicate completion of a "chunk" of data - it corresponds roughly to the sendFlags slot used on the Newton when sending explicitly packetized data. For CDPipeWrite the equivalent argument will force the buffer to be flushed after this call if the argument is set to 'true' (as it is here). The next three arguments control byte swapping, encoding and timeout lengths. In the case of the fifth and sixth arguments we have defined constants kSwapping and kEncoding described below. For the timeout argument (the seventh argument) we use a -1 which indicates no timeout. Finally the last two arguments are used for asynchronous calls (as these are), the first being a pointer to a callback routine, the second a value which is passed to the callback, in this case an application defined state constants.

The argument which controls swapping (kSwapping here) is used because of processor specific byte ordering. This is known as the "big endian vs. little endian" question. Basically it revolves around which byte in a group of bytes (say a long integer) is the most significant (biggest value) byte. For example, in the Macintosh OS, which uses big endian processors, the value 0x04050607 is stored in memory with the hex byte 04 first followed in succession by 05, 06 and 07. In the Windows OS, which uses little endian processors, the value 0x04050607 is stored in memory as 07 followed by 06, 05 and 04. The Newton's processor can handle either byte-ordering but normally runs in big endian mode. This means that when an application is written using DILs on a Windows machine, the swap value should be used to reorder the bytes. The swap value used depends on the size of the "groups" being received. For example, if the incoming data are long integers, the swap value should be 4 - the number of bytes in a long. If the incoming data is short integers, the swap value should be 2, and so on. Of course if single byte values are being transferred there is no need to do any swapping.

The encoding argument controls the conversion of ASCII characters to the Unicode characters used on the Newton. In particular, the conversion of characters whose character value is greater than 127 is different on Windows OS machines than it is on Macintosh OS machines. Typically you simply have to use one of two values: kMacRomanEncoding - which maps values according to Macintosh usage - and kPCRomanEncoding - which maps them for PCs. Finally, you can leave unicode values unchanged (and hence 16-bit values) by using the kUnicode constant.

Note that when CDPipeRead or CDPipeWrite returns an error (even a timeout error), no bytes will have been read or written.

Even when using the FDIL to transfer data it is not uncommon to use the CDIL read/write calls as a "control channel" for protocol messages. That is, strings are often sent using CDILs to implement a high-level protocol which coordinates the nature and state of data exchange between the desktop machine and the Newton.

For example, in the lab CDIL Archive, the following protocol is implemented to send data from the Newton to the desktop machine using CDILs. The FDIL Archive uses the same protocol and only the data itself is sent using the FDIL rather than a length followed by string data. In either case the CDILs are used to implement the protocol control messages show below:

<connect>
Newton->desktop "Sending"
Newton->desktop <data format>
desktop->Newton "READY"
Newton->desktop <length> (of string)
Newton->desktop <data>
desktop->Newton "CONFIRMED" or "ABORT"
Newton->desktop "End " or "More" or "Abrt"
<disconnect>

The Archive Protocol

Part of the code to receive data from the Newton is shown below:

while ( (*length-length_read) &&
(num_tries++<=kNumRetries) && (readErr==kTimeout) )
{
read_len = *length-length_read;
// necessary because if we timed out we read 0 bytes
// so we must reset it
readErr = CDPipeRead ( ourPipe, (void *) response,
&read_len, &eom, Swapping, Encoding,
kTimeout, 0, 0 );
if ( read_len != 0 ) {
response += read_len;
length_read += read_len;
}
EventLoop(); // if you don't service events, make
// this call a no-op

This code shows a synchronous call to CDPipeRead which, if the kTimeout constant is small enough still allows the application to respond to user events.

The code to send out the "READY" message with an eom (0x04) byte added follows:

fErr = CDPipeWrite(ourPipe, (void *)"READY\4", &length,
true,kSwapping, kEncoding,kTimeout,0,0);



When the data transfer portion of a protocol is completed the connection is broken down by first disconnecting and then disposing of the pipe. These are covered in the following sections.

Disconnecting

To disconnect a pipe the call CDPipeDisconnect is used. It looks like this:

fErr = CDPipeDisconnect ( ourPipe );

While this is straightforward to use, there are some subtle problems which may occur. The first is that on some older 16-bit Windows systems if the desktop machine is disconnected first, it is possible to hang the desktop machine. It is therefore better to always disconnect the Newton first.

In addition to this platform specific problem, if an error in an earlier CDIL call causes you to abandon execution you must be sure to call CDPipeDisconnect or the selected port can remain open. If this happens a later call to CDPipeListen will fail as the pipe will not yet be closed. This can be particularly pernicious when attempting to debug a program so you may want to have a utility around to force a close on a port.

Note that you must call CDPipeInit again after disconnecting a pipe if you want to reconnect the desktop machine to the Newton.



After disconnecting the pipe, if you are completely done with the pipe it may be disposed of and the library closed.
Disposing

Disposing of the pipe and closing the library is simply a matter of making the following two calls:

fErr = CDDisposeCDILObject( ourPipe ) ;
fErr = CDDisposeCDIL() ;

The first call closes the pipe, the second cleans up the CDIL environment and closes the library.
Options

There are a number of additional features available in the CDIL which provide programmers with more information as well as some additional functionality. While these will not be covered in detail here the more important ones will be discussed. For a more complete description see the DILs documentation on the DIL web page.

State Information
CDIL pipes may go through some or all of the following states during its existence:
kCDIL_Uninitialized
kCDIL_InvalidConnection
kCDIL_Startup
kCDIL_Listening
kCDIL_ConnectPending
kCDIL_Connected
kCDIL_Busy
kCDIL_Aborting
kCDIL_Disconnected
kCDIL_Userstate

CDIL Pipe States
Perhaps the most important use of these states is the transitions that occur during connection. Since the pipe doesn't really exist before CDCreateCDILObject is called, the state is undefined. After CDCreateCDILObject has been called but before CDPipeInit is called the state of the pipe is kCDIL_Unitialized . If a pipe cannot be initialized, say because of a problem with the port, the state of the pipe is kCDIL_InvalidConnection. After a successful call to CDPipeInit the state becomes kCDIL_Startup. Next, after CDPipeListen is called but before it has returned, it will transition to kCDIL_Listening. After completion (assuming success) it will become go to kCDIL_ConnectPending while waiting for the call to CDPipeAccept. Once accepted it becomes kCDIL_Connected.

During reading and writing, the state alternates between kCDIL_Busy during active transfer of data to kCDIL_Connected when not transferring data.

This is summarized in the following table:



You can get the current state of the pipe by calling CDGetPipeState as follows:

CDIL_State state;

state = CDGetPipeState ( ourPipe );


To cancel an ongoing requested action (such as an asynchronous read) the call CDPipeAbort will cancel the action and disconnect the pipe. The kCDIL_Aborting state occurs after the abort call has been made but before it has completed. After it has completed it transitions to kCDIL_Disconnected as it does after a call to CDPipeDisconnect.

Finally the user may add additional states to this list based on the state kCDIL_Userstate by using CDPipeSetState as follows:

fErr = CDPipeSetState(ourPipe,kCDIL_Userstate+kMyState );
// kMyState is application defined >=1

As is the case on the Newton, you may want to keep your own state information based on where you are in your protocol. For example, in the protocol for the CDILArchive described in the section reading and writing we create our own state values to denote connecting, connected, sending/receiving, confirming and disconnecting. Since the low-level communications software may be executing asynchronously from the CDIL application we keep track of our state separately from the pipe's state as the pipe may not have transitioned to the state that our software thinks we are in or may be in a state which is meaningless to us. For example, after calling CDPipeWrite as far as we are concerned we are sending and will be ready to transition to confirming state. However, the state of the pipe will be kCDIL_Busy until the write call completes after which it will return to kCDIL_Connected neither of these states has particular meaning to our protocol.

The CDILArchive example uses an entirely separate state mechanism but it could be redesigned to use the kCDIL_Userstate mechanism described above. Note that if you use the user state pipe option your user states will override the pipe state settings described at the start of this section. To re-enable the pipe state settings call CDPipeSetState with a state of zero.

Encryption
The CDIL supports encryption and decryption of data traveling down along the pipe by use of an encryption callback as shown in the following figure:



This encryption callback is installed with the following call:

CDEncryptFunction ( ourPipe, MyEncryptFunction,
arbitraryValue);


This will cause MyEncryptFunction to be called whenever data is sent out over the pipe. The encryption function has the following format:

fErr = MyEncryptionFunction ( theData, numberofBytes,
arbitraryValue );


where 'theData' is a pointer to a buffer of the outgoing bytes and 'numberofBytes' is how many bytes are in the buffer. 'arbitraryValue' is a long integer which is whatever was passed in as the third argument to CDEncryptFunction.

Note that since the FDIL uses the CDIL pipe to transfer data, FDIL data may be encrypted using this method however currently there is no easy way to decrypt frame data on the Newton as it is decompressed "behind the scenes" when it is received on the Newton. To decrypt FDIL data it can be sent to the Newton as a series of bytes, decrypted and unflattened using the Translate utility available in the Newton 2.0 OS. Of course data sent using CDPipeWrite does not have this problem since it is always a byte stream.

To turn off encryption all that is necessary is that another call to CDEncryptFunction be made with a NULL pointer for the encryption function pointer argument.

To decrypt incoming data the routine CDDecryptFunction allows the installation of a separate decryption function. This call and the encryption function are almost identical to the previous encryption calls:

CDDecryptFunction ( ourPipe, MyDecryptionFunction,
arbitraryValue);

fErr = MyDecryptionFunction ( theData, numberofBytes,
arbitraryValue );


Installing a decryption function will cause it to be called everytime there is incoming data, for example when CDPipeRead is called.

Miscellaneous
The following is a list of other routines not covered with a brief description of their use. For further details see the CDIL documentation on the DIL web page. Here is a brief summary of these routines:

CommErr CDBytesInPipe ( CDILPipe *pipe,
CDILPipeDirection direction, long *count ) ;
CDBytesInPipe returns the number of bytes waiting to be moved in the specified pipe in the specified direction. It returns the number of bytes in the count variable.

char *CDGetConfigStr( CDILPipe *pipe ) ;
CDGetConfigStr returns the configuration string (e.g., baud setting, etc.) for the given pipe - note that in some cases this may be different from the string passed into CDPipeInit as a particular baud rate may not be possible on the port so the requested values may be altered.

char *CDGetPortStr( CDILPipe *pipe ) ;
CDGetPortStr returns a string describing port the pipe is associated with. This should be the same value that was passed into CDPipeInit.

long CDGetTimeout ( CDILPipe *pipe ) ;
CDGetTimeout returns the current setting of the timeout, usually set from a call to CDPipeRead or CDPipeWrite.

CommErr CDFlush ( CDILPipe *pipe,
CDILPipeDirection direction ) ;
CDFlush flushes all bytes from the buffer in the direction specified. For example to flush the outgoing buffer it is called with kWritePipe for the direction argument.

void CDPad ( CDILPipe *pipe, long length ) ;
CDPad pads the data in the write buffer with zeros until it the length of the buffer is an even multiple of the length requested. For example, to make sure the buffer is long integer aligned a length of 4 ( sizeof (long) ) would be passed as the second argument.

void CDSetPadState( CDILPipe *pipe, Boolean paddingOn ) ;
CDSetPadState turns automatic padding so that every buffer is padded according to the value passed into CDPad. If CDPad has not been called it automatically pads to long integer (4-byte) boundaries.

Navigation graphic, see text links

Developer Services | Technical Information | Tools | Sample Code

Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help


Copyright Apple Computer, Inc.
Maintained online by commscourse@newton.apple.com
Updated 26-Sep-96 by dcd