Magic Internet Kit

Magic Internet Kit
Programmer's Guide


Connecting to the World


The heart of the Magic Internet Kit is the easy-to-use object framework for connecting your application to the outside world. This act can take several forms, from serial communications to TCP/IP over a variety of data links. This chapter describes the interfaces that your packages can use to create, use, and destroy links to remote hosts.

The ConnectableMeans class

In Magic Cap, a Means subclass specifies details about a connection that your application wants to create. For example, a TelephoneMeans object holds a phone number for a basic serial modem connection. These objects don't actually do the connecting, rather they just specify parameters.

The Magic Internet Kit takes this concept of a Means object much farther by defining a ConnectableMeans class. This is a Means object mixin that knows how to connect itself to the outside world in some way. Due to the fact that this class is a mixin, it is never instantiated directly, but rather is "mixed in" with existing means objects. The resulting subclasses then implement the methods defined by ConnectableMeans.

All of the Magic Internet Kit's connection classes inherit from ConnectableMeans. For example, the DialupPPPMeans object implements a version of ConnectableMeans that knows how to create TCP/IP connections using PPP over a Magic Cap communicator's built-in modem.


Source Code Note: The ConnectableMeans class definition can be found in the Means:ConnectableMeans.Def file.

Methods of ConnectableMeans

There are three methods defined by ConnectableMeans: CanCreateConnection, CreateConnection, and DestroyConnection. The methods do what they sound like, and we'll cover what each one does in detail.

CanCreateConnection

operation CanCreateConnection(): Boolean, noMethod;

This method is called by client code to determine if a connection could potentially be established. For example, if this ConnectableMeans subclass communicates using the modem, CreateConnection could check to see if the phone line is plugged in, make sure the user has set up a dialing location, and also check that the modem is not already in use.

The application calling CanCreateConnection should be aware that the boolean return value is not a guarantee that a connection attempt will succeed. There are many factors that can effect the success of a connection attempt, and many of these cannot be judged until the attempt is actually made. For example, if the object communicates using the modem, and the remote server is not answering, the object won't know that until after dialing. CanCreateConnection therefore should only be counted on as a "sanity check" before doing any real work to try connecting.

CreateConnection

operation CreateConnection(): Object, noMethod;

CreateConnection is the method used to connect a ConnectableMeans subclass to a remote host. If the connection attempt succeeds, this method will return the Object ID of the new stream to be used. The stream returned from CreateConnection is always a subclass of CommunicationStream, for example the Modem class for serial modem connections, or the TCPStream class for TCP/IP connections.

If the connection attempt fails, CreateConnection will throw a ConnectException heavyweight exception. Heavyweight exceptions are somewhat different than typical lightweight exceptions used in other parts of Magic Cap, so these will be discussed shortly in the following CreateConnection error handling section.


Note: With TCP/IP connections, you can actually call CreateConnection repeatedly to get multiple streams. See the chapter on TCP/IP Communications in Depth for more information.

DestroyConnection

operation DestroyConnection(stream: Object), noMethod;

DestroyConnection is the inverse of CreateConnection; whenever a stream is no longer needed, this method is used to destroy it in whatever manner is appropriate for the specific ConnectableMeans subclass. This method should also destroy any objects or buffers that were allocated by CreateConnection. For example, if CreateConnection creates a TCPStream object, DestroyConnection will be sure to destroy that object.

CreateConnection error handling

As a general rule with communications, connecting to a remote host requires quite a few things to all work together with each other, and therefore there are many potential places for things to go wrong. Errors in Magic Cap are typically handled with lightweight exceptions that get thrown when the error occurs and are then caught by code designed to specifically handle that error. The lightweight exception itself is really just an unsigned value that translates into an error code listed in the Exceptions.h file in the Magic Cap interfaces. For more information on handling these types of exceptions, see Magic Cap Concepts.

With the multitude of possible errors that could happen when connecting, an application using the lightweight exception model would have to catch almost a dozen distinct types of exceptions. The code for just setting up the error handling could be pages long! The Magic Internet Kit takes a slightly different but much cleaner approach by using heavyweight exceptions.

A heavyweight exception is very much like a lightweight exception in how it is used, but in this case the exception is not an error code but rather a real, live object. In the case of CreateConnection, the object that is thrown is a ConnectException or one of its subclasses. Code that is calling CreateConnection, therefore, needs to catch objects of the ConnectException class. The magical method that does this is called, quite reasonably, CatchByClass. CatchByClass will catch all exception objects of a specified class and all of its subclasses. To catch all connect-time errors, catch the ConnectException class. For only errors that deal with hardware, you can catch the HardwareConnectException class, which is a subclass of ConnectException. TCP/IP errors are thrown with TCPConnectException and IPConnectException objects, respectively. Most applications, though, will just want to catch ConnectException since it specifies all of the necessary details about the error in an attribute.

The ConnectException object has an attribute named ConnectError. The value of this attribute is an error code that specifies what exactly went wrong during the connection attempt. The error codes are listed in the MIKErrors.h file in the Magic Internet Kit's "Includes" file. Applications can then check for specific errors, or even check for a category of error by masking off bits. The errors are grouped by hex digit, so to see if the error had something to do with DNS, for example, a simple bitwise AND like the following will do the trick:

if (error & errorDNSMask) /* it's a DNS error */ 

Now let's take all of this information and see what it looks like in real, live code.

Using ConnectableMeans: an example

A quick example of using a ConnectableMeans subclass is in order. In this example, we will connect the Magic Cap communicator's built-in modem to a remote host. This will be a simple serial connection, so the only information we need about the remote host is its phone number.

The first step in this example is creating the objects that we'll need. The ModemMeans object is perfect for what we need to do; it's designed exactly for doing direct serial connections using the modem. For more information on this class, see the Serial Communications in Depth chapter of this document.

The following code defines and creates the ModemMeans object and the Telenumber object that holds the text of the destination number.

ObjectID means      = NewTransient(ModemMeans_, nil);
ObjectID telenumber = NewTransient(Telenumber_, nil);
ObjectID stream;
ObjectID exception;

Now we can "fill in" the Telenumber and set the means object's Telenumber attribute appropriately.

ReplaceTextWithLiteral(Telephone(telenumber), "(800) 555-1212");
SetTelenumber(means, telenumber);

Now we get to the fun part. Let's call CanCreateConnection to see if we have a chance at connecting.

if (!CanCreateConnection(means))
{
    /* error case */
    return false;
}

Once that test is passed, we'll want to set up error handling code for CreateConnection. To do this, we'll use the CatchByClass method mentioned above.

if ((exception = CatchByClass(ConnectException_)) != nilObject)
{
    /* The error code is specified by the ConnectError attribute. */
    Unsigned error = ConnectError(exception);
    
    /* Figure out the error type */
    switch (error)
    {
        /* Do what's appropriate here for the errors listed in */
        /* MIKErrors.h */
    }

    /* The exception object should be destroyed now that we're done */
    /* with it. */
    Destroy(exception);
    
    return false;
}

If we've gotten this far, call CreateConnection to connect the modem to the remote host.

stream = CreateConnection(means);

At this point we have a real, live connection. In the error case, CreateConnection failed up to the above exception handler, so our code can go on assured that the connection attempt worked. Let's commit the exception handler and then write some bytes to the stream.

Commit();

Write(stream, "hello there!", 12);

Finally, let's destroy the connection. For this example, we won't need the means object anymore, so destroy that, too.

DestroyConnection(means, stream);
Destroy(means);

MIK's ConnectableMeans subclasses

As mentioned above, the Magic Internet Kit comes with several subclasses of ConnectableMeans for you to use. These can be broken down into two categories: serial based and TCP/IP based. Each category and its classes will be discussed individually in the following sections.

Serial communications

Serial communications, for MIK's purposes, will be defined as opening a raw data stream to a remote host without the aid of software protocols like TCP/IP. This includes slamming bits down to the serial port or connecting the modem to a remote host. There are three ConnectableMeans subclasses that are serial-based: ModemMeans, MagicBusModemMeans, and SerialPortMeans. Each of these classes will be discussed briefly, and for more information see the Reference section of this guide.

ModemMeans

Class ModemMeans, as mentioned earlier in the ConnectableMeans example code, communicates using a Magic Cap communicator's built-in modem. If a connection attempt is successful, the stream returned from CreateConnection will be the iModem indexical. Application code can read and write from iModem since it inherits from CommunicationStream, and then DestroyConnection knows how to properly disconnect it.

ModemMeans inherits from TelephoneMeans and mixes in ConnectableMeans. This class does not add any additional fields, so a typical instance of this class looks the same as a TelephoneMeans instance, for example:

Instance ModemMeans 402;
 reservationTime: 0;
 telephoneNumber: (Telenumber 403);
End Instance;

Instance Telenumber 403;
        country: 1;
      extension: nilObject;
      telephone: (Text 404);
End Instance;

Instance Text 404;
	  text: '(800) 555-1212';
End Instance;

An example use of ModemMeans would be to talk to a bulletin board system (BBS) that allows direct dial-in via modem. Then the client application could do whatever it needed to, e.g. process mail or files, and disconnect. One advantage of this type of connection is that it is simple to set up and implement. On the flip side of this, though, is that it is not as functional as a modem connection using Point-to-Point Protocol (PPP), especially since a raw modem stream does not have inherent error correction and recovery built in, whereas PPP does.


Source Code Note: The ModemMeans class is defined in Means:ModemMeans.Def and implemented in Means:ModemMeans.c

MagicBusModemMeans

This class is much like the ModemMeans class above, except that it uses the Magic Internet Kit's custom external modem driver in place of the communicator's built-in modem. The MagicBusModem driver allows users to plug an external modem into their communicator's MagicBus port and then the application can use it with this class.


Source Code Note: The MagicBusModemMeans class is defined and implemented in Means:MagicBusModemMeans.(Def, c)

SerialPortMeans

Class SerialPortMeans allows applications to communicate via a Magic Cap communicator's MagicBus port. The MagicBus port, when used for passive serial connections, can communicate at speeds up to 38.4 Kbps. This class inherits from Means and ConnectableMeans, and it defines its own attribute BaudRate for the speed of the connection. A typical instance of SerialPortMeans looks like the following:

Instance SerialPortMeans 452;
      baudRate: 38400;
End Instance;


Source Code Note: The SerialPortMeans class is defined and implemented in Means:SerialPortMeans.(Def, c)

TCP/IP communications

TCP/IP is the standard protocol of the Internet. Internet Protocol (IP) is a packet-based protocol that is implemented over various types of data links, for example Ethernet, and is the low-level heart of the Internet. Transmission Control Protocol (TCP) runs on top of IP and provides applications with a reliable stream interface for communications. Many applications use stream-based services, such as the World Wide Web, by means of TCP/IP. The Magic Internet Kit also supports User Datagram Protocol (UDP), and this will be discussed in the TCP/IP Communications in Depth chapter of this document.

The Magic Internet Kit provides three classes for communicating via TCP: DialupPPPMeans, MagicBusPPPMeans, and XircomMeans. Each of these classes will be discussed briefly, and for more information see the Reference section of this guide.

DialupPPPMeans

Class DialupPPPMeans is used to create TCP/IP connections using Point-to-Point Protocol (PPP) over the Magic Cap communicator's built-in modem. This is the most common way to connect a communicator to the Internet, and for good reason: it provides a reliable, error-correcting, and authenticating stream over built-in hardware. Additionally, most all Internet Service Providers support PPP, so any application using it will have excellent connectivity with existing dial-up servers.

DialupPPPMeans inherits from DialupIPMeans, ConnectableMeans, and UsesTCP. DialupIPMeans is a built-in Magic Cap class that contains fields for useful information, namely the remote phone number, host name, and TCP port. ConnectableMeans, as described above, adds on the single-API interface for creating a destroying connections. The UsesTCP class is provided in MIK to do the hardware independent work of managing data link servers and TCP streams. UsesTCP will be described later in the Behind the scenes with TCP/IP section.

Instances of TCP-based Means objects contain a bit more information than the serial-based Means objects that were shown above. You'll notice that some parts are the same as for ModemMeans, namely the Telenumber and Text object. There are a few additional classes, though: a FixedList and Monitor. These will be discussed in greater detail as part of the UsesTCP mixin class. Here's a sample instance of DialupPPPMeans and its supporting classes:

Instance DialupPPPMeans 502;
    reservationTime: 0;
    telephoneNumber: (Telenumber 503);
           hostName: (Text 505);
               port: 80;
         linkAccess: (Monitor 508);
          linkClass: PPPLinkServer_;
      meansSourceIP: 0;
         dNSServers: (FixedList 509);
              login: (Text 506);
           password: (Text 507);
End Instance;

Instance Telenumber 503; // 12 bytes
        country: 1;
      extension: nilObject;
      telephone: (Text 504);
End Instance;

Instance Text 504;
           text: '(800) 555-1212';
End Instance;

Instance Text 505;
            text: 'www.genmagic.com';
End Instance;

Instance Text 506;
            text: 'none';
End Instance;

Instance Text 507;
            text: 'none';
End Instance;

Instance Monitor 508;
           next: nilObject;
          count: 1;
           user: nilObject;
End Instance;

/* List of DNS servers */
Instance FixedList 509;
         length: 1;
          entry: 0x12345678;
End Instance;

An example use of DialupPPPMeans, besides the obvious one of connecting to the Web or other Internet services, would be to take advantage of its error correction and multiple stream facilities for a vertical market application. Say that a salesperson is in the field with a Magic Cap communicator, and they need to synchronize records with the main office. A TCP/IP solution with PPP would be perfect because of the reliable connection that these protocols offer. Additionally, since TCP/IP/PPP is a totally cross-platform standard, one could write client applications for any other platform and still talk to the same server.


Source Code Note: The DialupPPPMeans class is defined and implemented in Means:DialupPPPMeans.(Def, c)

MagicBusPPPMeans

Like the serial-based MagicBusModemMeans above, MagicBusPPPMeans is just an extension of a modem-based class that uses an external modem instead of a communicator's built-in modem. MagicBusPPPMeans inherits from DialupPPPMeans and mostly makes sure that modem-specific method calls are pointed at MIK's iMagicBusModem indexical instead of Magic Cap's iModem. This is accomplished by overriding the Stream attribute that is inherited from the Means superclass. The Stream attribute specifies the hardware driver to use for communications.


Source Code Note: The MagicBusPPPMeans class is defined and implemented in Means:MagicBusPPPMeans.(Def, c)

XircomMeans

This class is designed for use with MIK's special driver for Xircom Netwave wireless ethernet cards. This driver is not yet heavily tested, so developers using it are currently warned to take note that they are doing so at their own risk. This driver will hopefully be polished and production-quality in the near future.


WARNING! The XircomMeans class is currently unsupported. Also, do not use Xircom Netwave PC Cards in Sony Magic Link PIC-1000 devices; the card's power requirements greatly exceeds what the PIC-1000 will support.

XircomMeans, like DialupPPPMeans, inherits from DialupIPMeans, ConnectableMeans, and UsesTCP. The DialupIPMeans inheritance is not totally appropriate since this is not a dial-up connection, but the fields for the remote host name and port are required by the TCP/IP libraries. The Telenumber attribute is ignored and should be set to nilObject.


Source Code Note: The XircomMeans class is defined and implemented in Means:XircomMeans.(Def, c)

Behind the scenes with TCP/IP

In order to minimize code duplication, the above hardware-specific TCP/IP Means classes only do "real work" that is specific to the hardware they support. If that's the case, then who's doing all the rest of the work? This section will describe the key class that lies between the high-level Means classes and the low-level TCP/IP stack: UsesTCP.

UsesTCP is the mixin class that all of the above TCP/IP Means classes inherit from to do the gnarly work of managing TCP/IP streams and data link servers. UsesTCP knows how to automagically connect and disconnect the link servers when needed, resolve host names with DNS when they are specified symbolically, and other fun stuff. We'll take a look at the features of UsesTCP, and you can find complete documentation on its implementation in the TCP/IP Communications in Depth chapter.

First of all, TCP/IP has the neat capability of creating multiple communications streams over the same data link. This would usually present a bit of a problem, though, when it comes to connecting and disconnecting streams because the code would have to figure out when it needs to create or destroy the link server. UsesTCP does this automatically by keeping count of the number of streams that a given link server has running on it. Client code, therefore, does not have to worry about managing the data link.

Second, the names of IP destinations can be specified by either an IP address, e.g. 192.216.22.142, or symbolic host name, e.g. www.genmagic.com. UsesTCP will look at the HostName attribute of the Means object, and if the name is not an IP address, it will ask the DNS servers in the DNSServers attribute if they can turn the name into an IP address.

A third feature of UsesTCP is its flexibility. UsesTCP provides methods that subclasses can override to do custom initialization of the data link server and do any negotiation with the remote host before the link level protocol is activated. Additionally, client code can manually open and close the data link level without creating a TCP/IP stream at the same time. This is useful if the client code wants to create and destroy several streams, one after another, without opening and closing the data link each time.

For more information on UsesTCP, see the TCP/IP Communications in Depth chapter and the Reference section of this document.


Source Code Note: The UsesTCP class is defined and implemented in Means:UsesTCP.(Def, c)

Example: Finger Client

Enough talk, let's rock! In this section we will build a TCP/IP application from the ground up using the Magic Internet Kit. The application will be a finger client that asks a remote host about the status of a user on that host. For more information on the finger protocol, see RFC 742. We'll build this application in a step-by-step fashion, so boot your computer and follow along!

Step 1: Create the code base

The data that we will exchange with the server is very basic: we send a user name and the server sends back information on that user and closes the stream. For our client, there will be one text field for the user name, and another field for the server's reply. We'll also need to provide a basic user interface for setting up the information on our host. Given these requirements, the EmptyWithFrills template sounds like a great starting place, so let's open up New Comms App and create our new application base.

Select TCP/IP as your connection type, and then add whatever hardware support you desire. Also include DNS if you want name resolution capabilities. I'll name my new application "Finger." Now build the new application for the Macintosh simulator.

Step 2: Build the user interface

The EmptyWithFrills template provided us with a new door in Magic Cap's hallway. Behind that door is a scene with one button in it that takes the user to its connection setup scene. Now add the following components to this scene:

For my version of Finger, the user interface looks like this:

Figure 5 Finger user interface

When you have the interface the way that you like it, use Magic Cap's Inspector tool to select Finger's Scene. Then select Examine -> Dump Inspector Target Deep from the simulator's menu to dump the interface components in this scene. If you hold down the shift key while doing this last step, the objects will be dumped to the Macintosh clipboard, too, which will make the next step easier.


Note: We are not using Magic Cap's "Dump Package" feature since Dump Package would dump a lot more stuff than we need. For example, all of the setup components would get dumped, and these are most easily kept in separate files like the template starts with. Dump Package won't handle the multiple object instance files properly.

With these objects dumped out, go back to MPW and paste them into your Objects.Def file as you would with any other Magic Cap application.

Step 3: Define the FingerButton class

Let's make the "Do it" button that we added above do the real work. First, subclass Button and override its Action method. A few fields for the two TextField objects would be handy, too, so add those. Here's the class definition that I'll use for this new class, called FingerButton:

Define Class FingerButton;
        inherits from Button;

        field           queryText: Object, getter, setter;
        field           replyText: Object, getter, setter;

        attribute       QueryText: Object;
        attribute       ReplyText: Object;

        overrides       Action;
End Class;

With the new FingerButton class defined, modify your Objects.Def file to reflect the new class. Here's an excerpt from my Objects.Def file:

Instance FingerButton 'Do it!' 9;
           next: (TextField 'Server reply:' 10);
       previous: (SimpleActionButton 'Set up host info' 17);
      superview: (Scene 'Finger' 6);
        subview: nilObject;
 relativeOrigin: <66.5,-43.0>;
    contentSize: <50.0,24.0>;
      viewFlags: 0x70101200;
     labelStyle: iBook12Bold;
          color: 0xFF000000;
       altColor: 0xFF000000;
         shadow: nilObject;
          sound: iTouchSound;
          image: nilObject;
         border: iSquareButtonBorderUp;
      queryText: (TextField 'User to finger:' 2);
      replyText: (TextField 'Server reply:' 10);
End Instance;

If you like, you can also remove the "Port" setup TextFields from the object instance files related to the Means classes, e.g. DialupPPPObjects.Def. The files are, by default, in a folder called "MIKObjects" within your project's folder. We won't need for the user to set up the remote port number since we know that port 79 is the finger port on Unix machines.

Step 4: Write the code!

Now it's time to write the code for FingerButton's Action method. Here's the code as one block. We'll discuss it in a step-by-step fashion afterwards.

Method void
FingerButton_Action(ObjectID self)
{
    ObjectID    means = DirectID(iiMeans);
    ObjectID    replyText = ReplyText(self);
    ObjectID    queryText = QueryText(self);
    ObjectID    stream;
    ObjectID    exception;
    Unsigned    count;
    Boolean     done;
    Str255      replyString;
    Str255      queryString;

    /* Clear the reply TextField */
    DeleteText(replyText);

    /*
    ** Make sure the Means object is TCP-capable! iiMeans refers to
    ** the current Means object that the user set up in the "Finger
    ** Setup" scene included with the template that we used
    ** (EmptyWithFrills). See ConnectChoiceBox and HardwareChoiceBox's
    ** SetLevel methods for the implementation of this.
    */
    Assert(Implements(means, UsesTCP_));

    /* Set the port to 79 (the finger port) */
    SetPort(means, 79);

    /* If we fail the first sanity-check, return immediately. */
    if (!CanCreateConnection(means))
    {
        Honk();
        ReplaceTextWithLiteral(replyText, "Ack! Can't connect!");
        return;
    }

    /* Catch all connect-time exceptions. */
    if ((exception = CatchByClass(ConnectException_)) != nilObject)
    {
        /* An error occured while connecting! We'll just return. */
        Honk();
        ReplaceTextWithLiteral(replyText,
             "Ack! An error occured while trying to connect!");

        /* Destroy the exception object now that we're done with it. */
        Destroy(exception);
        
        return;
    }

    /* Connect to the remote host! */
    stream = CreateConnection(iiMeans);

    /* Commit the above exception handler */
    Commit();

    /* Send the query to the remote host */
    TextToString(queryText, queryString);

    if (Catch(serverAborted) != nilObject)
    {
        /* An error occured while writing! */
        Honk();
        ReplaceTextWithLiteral(replyText,
            "Ack! An error occured while trying to write!");
        return;
    }
    
    Write(stream, &queryString[1], queryString[0]);
    Write(stream, "\xD""\xA", 2);
    Commit();

    /* Read in the reply */
    done = false;
    while (!done)
    {
        char*   currentChar;

        /* Read up to 255 characters at a time */
        count = Read(stream, replyString, 255);
        replyString[count] = 0x0; /* Null-terminate the string */

        /* Turn those pesty carriage returns into spaces */
        currentChar = replyString;
        while (currentChar[0])
        {
            if (currentChar[0] == 0xd)
                currentChar[0] = 0x20;
            currentChar++;
        }

        /* Stick the data into the "server reply" TextField */
        AppendLiteral(replyText, replyString);

        if (count < 255)
            done = true;
    }

    /* Destroy the connection now that we're done. */
    DestroyConnection(means, stream);
}

Step 4.1: Getting ready to take-off

Assert(Implements(means, UsesTCP));
SetPort(means, 79);

if (!CanCreateConnection(means))
    return;

The first step is a bit of pre-flight checking, namely checking that the Means object that the user selected in the "Finger Setup" scene implements UsesTCP. Of course, when we created the application, we only specified TCP means objects like DialupPPPMeans as ones we wanted to use, so this should never happen. We'll also manually set the remote port to 79, which is the finger port as specified in RFC 742.

Once that's done, we'll call our sanity-check method CanCreateConnection to see if we can possibly connect. If not, we'll return immediately.

Step 4.2: Set up the error handling code

if ((exception = CatchByClass(ConnectException_)) != nilObject)
{
    Honk();
    Destroy(exception);
    return;
}

Before we try to do the real work of establishing a connection, we must set up the exception handler. We'll catch all ConnectException objects and just return from our method. A real application would want to check the ConnectError attribute of the exception object to see exactly what kind of exception was thrown.

Step 4.3: Take off!

stream = CreateConnection(means);
Commit();

The CreateConnection method should do just that: create a live connection to the remote host. If this method fails, it will throw a ConnectException and wind up in our above exception handler. If not, the code will just go on. At this point we'll want to commit the connect-time exception handler since the connection worked.

Step 4.4: Send the query

if (Catch(serverAborted) != nilObject)
{
    /* An error occured while writing! */
    Honk();
    ReplaceTextWithLiteral(replyText,
        "Ack! An error occured while trying to write!");
    return;
}

Write(stream, &queryString[1], queryString[0]);
Write(stream, "\xD""\xA", 2);

Commit();

Now it's time to send our query to the remote host. Before we actually call TCPStream's Write method, though, we should set up error handling code. In this case, we want to catch serverAborted exceptions. If the Write method fails, for example if the remote server closes the stream before all the bytes are written, then Write will throw a serverAborted exception. If we don't tell Magic Cap that we can handle this type of error, the uncaught exception will cause the communicator to reset. That would be bad.

With our error handling code in place, we can try to stuff some bytes down the TCP stream. The first Write call sends the user name to the server, and the second Write sends a carriage return/linefeed combo that terminates the query. After this is done, we can Commit the above exception handler since we are done with the code that could fail.

Step 4.5: Read the response

count = Read(stream, replyString, 255);
(...)
AppendLiteral(replyText, replyString);

At this point, the server should be spitting data back at us. We'll read this in 255 byte chunks and stick it in the "Server Reply" text field. The TCPStream_Read method will block until the request can be satisfied, unless the stream gets closed under it, so we can use that to our advantage here. In the case of finger, the server will close the stream immediately after sending its data, so at that point our Read call will return with however many bytes it could get.

Step 4.6: Close the stream

DestroyConnection(means, stream);  

Now that we're done with the stream, we can get rid of it. The underlying TCP code will have already disconnected the TCP stream for us since the remote host disconnected earlier after sending its data, but we still need to let the ConnectableMeans object know that we're done. In this case, the means object will disconnect the data link server that the TCP stream was using, and also destroy the TCPStream object.

Step 5: Rebuild and play with your new application

Now rebuild your copy of Finger and see if it works! If it doesn't, you can double-check it with the copy of Finger that accompanies the Magic Internet Kit in the documentation folder.

Step 6: Complain about Finger stopping user interaction

Finger still leaves a bit to be desired. For example, you'll notice that Magic Cap will stop the user from doing anything while the FingerButton_Action method is running. This is because the code is all executing on the User Actor, which is the thread that all user interaction runs on. A better solution would have this code executing on its own thread so that the user can go do other stuff while Finger runs. That's what we'll do next.

Multithreading with Actors

Magic Cap is a multi-threading platform, and threads are very handy for communications. As you've seen above with Finger, blocking all user interaction while dialing the phone or waiting for a remote server is, at best, very annoying for the user. Instead of executing time consuming code on the thread that handles user interaction, applications should create their own threads for these tasks.

In Magic Cap, a thread is called an Actor. Applications wishing to create their own actors must subclass the Actor class and override its Main method to make it do what they want. This section will briefly cover how to use actors, but Magic Cap Concepts provides a far more complete discussion of this topic. We recommend that you read that chapter as soon as you have time.

Actor concepts

Like everything in Magic Cap, an actor is an object. The base Actor class does not really do anything when you create it, but rather it is meant to be subclassed. The first method that you should override is Main. Main is the heart of the actor; when the actor is created, Main is executed. When Main returns, the actor is destroyed.

Magic Cap uses cooperative multitasking with its actors, so each actor should be sure to give up time to other actors. This is done by calling RunNext on the scheduler, referenced by the iScheduler indexical. Calling RunNext tells the scheduler to run the next waiting actor.

Some methods that you might call will automatically call RunNext if they are going to take a while to return. For example, if you call Read on a TCPStream object, and the Read cannot be immediately satisfied because all the bytes are not available, Read will call RunNext to let other actors do their stuff.

Creating an actor

Actors are created using the NewTransient method. Here's an example:

newActor = NewTransient(CommsActor_, nil);  

This code will create a new CommsActor class. The second nil parameter is for parameters that one might want to pass for the new actor, and passing nil tells Magic Cap to use the default parameters. If you want to pass in parameters, for example the size of the execution stack that the actor should have, you can do so just like for any other object. Here's an example of setting the stack size a bit larger than the default:

NewActorParameters newActorParams;

ZeroMem(&newActorParams, sizeof(NewActorParameters));
SetUpParameters(&newActorParams.header, newActorObjects);

newActorParams.stackSize = 0x2000; /* default is 0x1000 (4K) */

newActor = NewTransient(CommsActor_, &newActorParams.header);

Once the actor is created, it will be ready to run in the scheduler. Keep in mind that the code in your actor's Main method will not start executing until the scheduler switches to the actor. This will happen the next time that you, or the system, calls RunNext.

Using an actor

As mentioned above, all the real work of an actor is performed in the Main method. Here's a sample main method:

Method void
MyActor_Main(ObjectID self, Parameters* UNUSED(params))
{
    while (FeelingPeachy(self))
    {
        DoSomeStuff(self);

        if (SomeFatalErrorOccurred(self))
        {
            return;
        }
    }
}

There is one essential caveat to using actors that you should be aware of: code running on an actor that isn't the user actor cannot call methods that deal with drawing on the screen. This means that code in the above MyActor_Main method cannot move viewables on the screen, call RedrawNow, or otherwise change stuff on screen. If you need to mess with viewables, do so using RunSoon. RunSoon will be discussed shortly in the Moving between actors section.


Note: There is one exception to the rule of not messing with stuff on screen from outside the User Actor: announcements. You can always call the Announce method regardless of the current actor since it will automatically use RunSoon when needed

There is one more interesting caveat to using actors in Magic Cap: an actor is a transient object, so in Magic Cap 1.0 a power cycle would destroy the actor. This is not always that case in Magic Cap 1.5, so special care must be taken to ensure that the state of an actor does not get confused if the power is turned off and then back on. This is particularly important with communications, of course, because any data links will get shut down when the power is turned off.

The key to managing transient actors in Magic Cap 1.5 is overriding the Reset method of an object. Reset gets called when the device is powered on, so you can use this method to hunt down any actors that need to be managed and do whatever is appropriate. For communications this usually means destroying the actor.


Source Code Note: Two of MIK's template packages illustrate how to override reset and destroy remaining actors at power-up time. See the CommsManager class in the Terminal and EmptyWithFrills templates.

Destroying an actor

Code can destroy an actor using Magic Cap's Destroy method. If the code wanting to kill the actor is running on the actor in question, though, it should instead force the actor's Main method to return.

Moving between actors

Magic Cap provides mechanisms for code executing on one actor to talk to other actors, so we'll briefly cover those mechanisms here. There are a few different ways to manage multiple threads, including semaphores, cross-actor exception throwing, and the RunSoon method mentioned above. For more information on these topics, see the Magic Cap Concepts chapter on actors.

Semaphores

A Semaphore object is very handy for controlling access to a resource. Magic Cap's semaphores are quite a bit more intelligent than the typical semaphore in operating system theory, in fact they behave almost like monitors. Semaphores provide automatic queueing and dequeueing as needed to control access to a single resource from multiple threads, so there is no need to poll a semaphore.

There are two key methods in the Semaphore class that you should be aware of: Access and Release. Access is used to "hold down" the semaphore. If the semaphore being accessed is not already held down by code on another actor, Access will return immediately. If the semaphore is already accessed, though, Access will block until someone else releases the semaphore using Release. Release will tell the semaphore that you're done messing with it, and it will then wake up the next actor in the queue, if any, that wanted to access the semaphore.

Cross-actor exceptions

If you're not already familiar with exception handling in Magic Cap, you should read the "Handling Exceptions" chapter of Magic Cap Concepts for a good introduction. This section briefly describes how to use exceptions with actors.

Every actor has its own exception stack, so an exception thrown on one actor using Fail cannot be caught from another actor. This is very useful for localizing exception handling code; for example a serverAborted exception thrown on a communicating application's comms actor won't be caught accidentally by the Post Office actor in the system.

If code executing on one actor wants to throw an exception on a different actor, it should use the FailSoon method. FailSoon will cause a specified exception to get thrown on the other actor the next time that the actor comes up in the scheduler. If the target actor is not ready to run, e.g. it is blocked, the actor will be awakened and the exception thrown.


Source Code Note: See CommsManager_DestroyCommsActor in the CujoTerm template for an example of using FailSoon.

RunSoon

The RunSoon method lets code in one actor specify a completion function that is to be run on the User Actor. When Magic Cap's scheduler switches to the User Actor, this function will be executed. For example, if you are creating Card objects from data that is being pulled in by a communications stream, you can only perform some of the Card class's methods from the User Actor, e.g. Borrow/ReturnForm. The best way to do this, then, would be to read the data in from one actor and then call RunSoon to perform the final card creation from the User Actor.

Usage of this method is most easily explained with an example:

typedef struct
{
    Parameters  header;
    ObjectID    interestingThing;
} DoSomethingUsefulParams;
#define numObjectsDoSomethingUsefulParams 1

Private void
DoSomethingUseful(DoSomethingUsefulParams *params)
{
    ObjectID thing = params->interestingThing;
    DestroyTransientBuffer(params);

    DoSomethingElse(thing);
}

Method void
Thing_DoSomethingElse(ObjectID self)
{
    // stuff...
}

#undef  CURRENTCLASS
#define CURRENTCLASS MyActor

Method void
MyActor_Main(ObjectID self, Parameters* UNUSED(params))
{
    DoSomethingUsefulParams*    params;
    // other stuff...

    params = NewTransientBuffer(sizeof(*params));
    SetUpParameters(¶ms->header, numObjectsDoSomethingUsefulParams);
    params->interestingThing = ThingObject(self);

    RunSoon(true, (CompletionFunction) DoSomethingUseful, ¶ms->header);
    // other stuff...
}

Step 1: Define the parameters for the RunSoon completion function

typedef struct
{
    Parameters  header;
    ObjectID    interestingThing;
} DoSomethingUsefulParams;
#define numObjectsDoSomethingUsefulParams 1

RunSoon completion functions can take a parameter block structure like the one used here. The first item in this structure is a Parameters header. This is used for type checking, so you don't have to fill anything in for it. After the header, you can stick anything you want in the structure. Keep in mind the number of ObjectIDs that you use, since you'll need this later. A handy trick is to define a symbol for the number of ObjectIDs as shown above.

Step 2: Write the completion function

Private void
DoSomethingUseful(DoSomethingUsefulParams *params)
{
    ObjectID thing = params->interestingThing;
    DestroyTransientBuffer(params);
    DoSomethingElse(thing);
}

Completion functions for RunSoon calls are typically very short and merely call another method of the class in question for doing the "real work." When a completion function is executed, the current context may not be the on that you expect, e.g. your own package. This is usually not what you want, but calling a method of one of your objects will ensure that Magic Cap switches into your package's context.

Step 3: Write the methods that do the real work

Method void
Thing_DoSomethingElse(ObjectID self)
{
    // stuff...
}

As mentioned in the previous step, do the "real work" of a RunSoon in a method of one of your package classes and not the completion function.

Step 4: Call RunSoon from another actor!

Method void
MyActor_Main(ObjectID self, Parameters* UNUSED(params))
{
    DoSomethingUsefulParams*    params;

    params = NewTransientBuffer(sizeof(*params));
    SetUpParameters(¶ms->header,
        numObjectsDoSomethingUsefulParams);
    params->interestingThing = ThingObject(self);

    RunSoon(true, (CompletionFunction) DoSomethingUseful,
        ¶ms->header);
}

Now that your completion function is all set up, you can now call RunSoon from any actor that you want, and the completion function will execute on the User Actor.

Actors in the Magic Internet Kit

If all of this multithreading stuff looks intimidating, don't fret; the Magic Internet Kit comes to the rescue again. MIK includes actors in two of its templates, Terminal and EmptyWithFrills. In Terminal, all connecting and reading is performed on the CommsActor object. Additionally, there is a CommsManager class that is used to create and destroy CommsActor objects. The EmptyWithFrills template provides these same classes, except the terminal-specific code is removed and a "your code goes here" comment is in its place.


Source Code Note: See the CommsActor.(Def,c) and CommsManager.(Def,c) files in the CujoTerm and EmptyWithFrills templates for their uses of Actors.


Magic Internet Kit home page Programmer's Guide index