# ============================== # Fichier: Events-and-NewtWorld # Projet: (Newton Bowels - Events) # Ecrit par: Paul Guyot (pguyot@kallisys.net) # # Créé le: 28/10/2003 # Tabulation: 4 espaces # # Copyright: © 2003 by Paul Guyot. # Tous droits réservés pour tous pays. # Licence: Licence Réflexive Kallisys # http://www.kallisys.org/reflexive/ # =========== # $Id$ # =========== Introduction ------------ NewtonOS is a preemptive multitasking operating system based on a kernel called OS600. Communication between tasks is done with messages sent to ports like in most (any?) preemptive multitasking operating system. Most tasks are built around an event loop and they are blocked, waiting for messages to arrive, thus consuming the lowest amount of CPU and hence of battery. One of the tasks of NewtonOS is responsible for all the interface, actually for all the NewtonScript code. It is the NewtWorld task also known as the NewtApp or AppWorld task. When you create another task or if your code is executed within another task (for example if your code is called from the communication layer or from the card server), you may want to communicate with the NewtonScript world to provide feedback to the user or just to discuss with the NewtonScript bits of your package. Since NewtonScript is not thread safe, you cannot just call NewtonScript functions. The only proper way of doing so is to send a message to the NewtWorld task. This technical note explains how to install an handler for these messages and how to send them form another (or from the same) task. This technical note is mostly based on code that works in ATA Support. I would need to take more time to investigate features I don't use or document some subtleties. In the meanwhile, feel free to drop me a line if you need help with the technique described here. Messages and events ------------------- The kernel uses messages. NewtonOS has events which are just messages with some code identifying them. This is the kind of messages the NewtWorld task receives. Events are all based on the TAEvent class and they should be at most 256 bytes long (this is a constant in AEvents.h file, it's hard coded in the ROM). When a message is sent to a port, the kernel makes a copy of it in the memory zone of the other task. This would be extremely useful if NewtonOS made a more extensive use of the protected memory. Since usually memory is not protected between tasks and the size of events is limited to 256 bytes, if you need to send a larger event to the NewtWorld task, you can just send a pointer to the actual data. Events on the Newton are called AEvents, probably a short version for AppleEvents. Please note that they're much lighter than the AppleEvents on MacOS. Although it's a layer on top of kernel messages, they are much more efficient than their MacOS counterpart. They share with MacOS AppleEvents the notion of class and ID, although, unlike MacOS, it is not linked with AppleScript and it is just a way to sort events. The class and the ID are 32 bits unsigned integers, usually represented as 4 characters. Lowercase are reserved for Apple. The system mostly (only?) uses events of the class 'newt'. Sending an event ---------------- To send an event, you need to get the port of the NewtWorld task (or of whatever task you want to send an event to). The port of the NewtWorld task can be found using the NameServer. // Create a client for the nameserver TUNameServer myNameServerClient; // ID to the port TObjectId thePortID; // We don't really care about the specs. ULong theSpecs; // Retrieve the ID of the port NewtonErr theErr = myNameServerClient.Lookup( "newt", "TUPort", (ULong*) &thePortID, &theSpecs); What the name server provides is the ID of the port. We can pass this ID to the proper constructor of TUPort or assign it to TUPort. TUPort theNewtPort( thePortID ); or TUPort theNewtPort = thePortID; This is because an object of class TUPort is just an ID to the kernel object. Sending the event is done through the Send or the SendRPC methods. SendRPC is a way to get a reply from your event while Send is just to send a message with no reply. If you happen to send events quite frequently, you'd better save the port in a variable. Actually, I think that retrieving the name with the name server means sending a message to another port. Receiving events ---------------- To receive events, i.e. to have code executed in the NewtWorld task when you send an event from another (or the same) task, you need to create and install an event handler. We'll go into the depth of the magic later, but let's say that theoretically, you just sub-class TAEventHandler from AEventHandler.h system header and override the AEHandlerProc to get notification of an event that was sent. Practically, you can do this if your code is for NewtonOS 2.1 only. Otherwise, just subclass TAEventHandlerProxy from the header provided with this technical note. The TAEventHandler class (and the TAEventHandlerProxy class) has methods to initialize it with an ID and a class to tell the system which events will be sent to the handler. It also has four methods you can override at your convenience, they're callbacks to handle the events. The most important one is AEHandlerProc and it's called whenever an event is received by the NewtWorld task. You can also use the EventHandler as an idler by initializing the idler (InitIdler method), and overriding the IdlerProc method. The idler uses events as well, but usually, you're just interested with the result (the fact that your idler will be called back approximatively when you wanted it). The problem with events is that you need at some point to delete the event to avoid leaks. I think there is something, either the asynchronous message or the event itself, that you cannot delete just after having called Send. If you send an event to your own handler, the best is probably to delete the event in the handler call back routine (just like I do, you need to store a pointer because your callback routine is provided with a copy). If you send an event to another handler, you need to define a completion call back routine to know when the event was completed and hence when it's safe to delete it. Runtime architectures --------------------- This is where the reason of the TAEventHandlerProxy magic is explained. When a function is provided with a C++ object of some class A, the function doesn't necessarily know it is actually an object of a class B which is a subclass of A. Nevertheless, if this function calls a virtual function of this object, the function called should not be the implementation of A, it should be the implementation of A. To achieve this behavior, each object with virtual functions has a table to somehow find the virtual functions. The way this table is attached to the object is not standardized (well, was not standardized when the Newton was created, there are only recent efforts to standardize it). Usually, when there are various compilers on a platform they store this information in the same way so a C++ library compiled with one compiler would work with a program compiled with another compiler. Apple, for NewtonOS, used a compiler based on Norcroft, like ARM's. (Maybe it was ARM's, Norcroft provided the frontend of C++ compilers for a lot of companies designing CPUs). If you look at the same function compiled in NewtonOS 2.0 and in NewtonOS 2.1, you realize that this compiler was bumped to a newer, more efficient version. However, they also changed the same virtual methods were referred to in C++ objects (one should stress that it was particularly unefficient on NewtonOS 2.0). Thus breaking any software that would be compiled with the newer compiler and calling C++ functions using virtual methods previously compiled with the older compiler. And this is actually the case with event handlers. NewtonOS 2.0 expects your handler to have NewtonOS 2.0-style virtual methods while the development tools will create them in the NewtonOS 2.1 style. On NewtonOS 2.0, a TAEventHandler object will be layered like this: ---------------------------- | fNext | ---------------------------- | fEventClass | ---------------------------- | fEventID | ---------------------------- | fIdler | ---------------------------- ------------------------------- | Pointer to virtual table | -----> | Link (for subclasses?) | ---------------------------- ------------------------------- | pointer to destructor | ------------------------------- | pointer to AETestEvent | ------------------------------- | pointer to AECompletionProc | ------------------------------- | pointer to IdleProc | ------------------------------- And this is how the table is designed on NewtonOS 2.1: ---------------------------- ---------------------------- | Pointer to virtual table | -----> | Jump to destructor | ---------------------------- ---------------------------- | fNext | | Jump to AETestEvent | ---------------------------- ---------------------------- | fEventClass | | Jump to AEHandlerProc | ---------------------------- ---------------------------- | fEventID | | Jump to AECompletionProc | ---------------------------- ---------------------------- | fIdler | | Jump to IdleProc | ---------------------------- ---------------------------- Variables like fNext are not at the same offset. Plus the virtual tables are not in the same format (on NewtonOS 2.1, entries are jump instructions while on NewtonOS 2.0, they're pointers). The NewtonOS ROM doesn't make an intensive use of virtual methods. The classes with virtual methods (that you may want to override/call virtual methods of) are: CItemTester and subclasses (CItemComparer, TAEventComparer) TAEventHandler TPartHandler TTimerElement TUTaskWorld The proxy class TAEventHandlerProxy is here to make the virtual mechanism transparent. Your methods will be called back whether your code is running on NewtonOS 2.0 or NewtonOS 2.1. The only difference is that you cannot pass this where a TAEventHandler is required, you need to call the GetHandler accessor. Other than that, the interface is exactly the one of TAEventHandler. If your code is meant to run on NewtonOS 2.1 only, you don't need the proxy class. Just subclass the TAEventHandler class. Conclusion ---------- This technote explains how the accompanying sample code works and how to integrate this technique in your application. It might be interesting if you want some NewtonScript code to be executed when something happens in another task. Some details are lacking because I would need to make some experiments to confirm the behavior of some methods of the handler or some other mechanism. Still, the code in the sample code works in ATA Support RC6.1 (the TAEventHandlerProxy class is directly taken from ATA Support source code). To use the sample code, just install the package and then, from the NTK inspector, call the following functions: Events_InstallHandler() Events_RemoveHandler() Events_SendEvent() and watch what is written to the inspector. ## ===================================================================== ## ## The number of computer scientists in a room is inversely proportional ## ## to the number of bugs in their code. ## ## ===================================================================== ##