shf@well.UUCP (Stuart H. Ferguson) (03/17/88)
InterProcess Communication - Object-Oriented Approach So far the response to my "object-oriented" IPC proposal has been generally favorable. In this message I hope to show that this approach is actually simple and easy to implement, as well as being straightforward to expand and customize. Basics: craig@unicus.UUCP (Craig D. Hubley) lucidly comments on and discusses the proposed IPC model, including this brief and clear summary of the approach: >... Stuart is proposing an object-oriented model >for the IPC, that is, `Please [method] my [object]' rather than >`[program] controls [program]'. I think this is the right way to go, >since it is no more difficult, lends itself to reliable control structures, >and is a general case of the more specific master-slave paradigm. ... >The Amiga environment as it stands is preserved. Extensions to it consist >of placing a conceptual envelope around each data structure to turn it into >an object: > A Class > Set of methods performable on this class ... > Any class exists because a program claims it exists. > Any method exists because a program claims it exists. He also describes the basics of a possible system to implement this idea, but his proposal is a little too complex for my liking. I think the whole thing can be done with almost embarassing simplicity. Processes offer to service messages. The messages consist of commands to objects (i.e. something of the form "please [method] my [object]"). Object classes and methods are specified by unique longword identifiers, like `PIPE' or `EDIT'. Services are specified as Class.Method, as in "ILBM.SHOW", or "FILE.COPY". There is one message port associated with each unique Service. The association between Services and message-ports is managed by a runtime library. The list is kept in the Base structure of the library and is locked so that only one process can alter the data in Base at one time. Processes offer Services by calling a runtime routine. For example: port = OpenServicePort (); RegisterService (ILBM, EDIT, 0); ... WaitPort (port); ... That's all there is to it. Since the library is runing within the calling processes context, it can take care of the port. If a client program wants to use a service, it just does: result = Dispatch (object,command); where result and object are pointers to Objects, and command is a pointer to a command block. The runtime library checks the class of the object and the requested method and looks up this Class.Method entry in its list of available Services. It then dispatches the command message to the appropriate port and waits for a reply. There would also be calls for asyncronous message passing. This is the basic interface. I glossed over the details of the object and command (method request) specification, but library routines could be provided to manipulate the object structures so that user programs wouldn't have to know any specifics. One might imagine requesting a decending sort on an ascii file by doing: struct Object *obj,*result; struct Command *com; ... /* create an instance of a FILE object, * with a given file lock and * with TEXT attribute flag */ obj = NewObject (FILE); AddObjAttribute (obj, LOCK, filelockptr); AddObjAttribute (obj, TEXT, 0); /* specify a decending sort */ com = NewCommand (SORT); AddCmdOption (com, DESC, 0); /* dispatch the request */ result = Dispatch (obj, com); /* clean up */ DisposeObject (obj); DisposeCommand (com); ... The "result" pointer would be the output object, like a file or a pipe for example. The program could then send that object the SAVE command and it would save itself to disk if it turned out to be a pipe, or rename itself if it was already on disk. Note that the programmer need know nothing about how object structures are organized. He does, however, have to know about what attributes are valid for the class of object he wants to use. Object class hierarchies would be easy to implement and more or less transparent to the programmer. If the library can't find a given Class.Method, it will look for the Super-Class.Method and relay the command to that method server if it finds it. It will look up the tree until it reaches a root class and then it will give up and return an error. Class hierarchies are useful to organize objects in a logical fassion, and help to prevent code duplication yet again. Specifics: The basic approach I've outlined addresses many specific concerns as special cases. For example, Craig suggested that the system should have the following feature: > A permanent editable list of where methods that are currently > unavailable (not in memory) might be found. (A text file) > A preference flag somewhere decides whether the user is to > be consulted as a last resort, or whether the request fails. This could be implemented by a "service broker" on top of the basic system. The broker would be a small program that would register the services of other programs not currently in memory. When it received a request for one of its services, it would LoadSeg() the actual server and relay the request. >Streams I haven't thought broadly about yet. ... I have. Streams can be implemeted as objects. A process can register itself as a method on stream-class objects. `Grep' and most other Unix(tm) utilities are just methods on streams. For example, to `grep' a stream, one might do: /* obj is a pointer to a stream class object such as a pipe or a file */ com = NullCommand (GREP); AddCmdOption (com, REX, "pattern"); result = Diat they will do that can be specified before programmers go off and design their own objects and methods, the more uniform and consistent the results will be. A set of root classes will provide a platform for developers to use, as well as examples of how to "do it right" as far as creating new classes and methods. -- Stuart Ferguson (shf@well.UUCP) Action by HAVOC (shf@Solar.Stanford.EDU)