ctim@dolphin.aaet.ti.com (Craig Timmerman) (11/17/90)
Since this question popped up again, here is some information on the Univ of Lowell C++ interface to Motif that I have been collecting. I figured others might be interested in this as well. The first part of this is a description/review of the bindings. The second part is a series of messages explaining where you can get the bindings. Maybe if enough people are interested, OSF will stop sitting on this stuff and release it for real. I have been using these bindings for about four months now for some prototype work. Overall I am pleased with how well they work. You can get a better picture of what the binding do by reading the Univ of Lowell paper (see part two for how to get it). The bindings are a set of C++ class wrappers around all of the existing Motif widgets. You refer to the widgets as true C++ classes and all of the Motif and Xt functions are setup as member functions of the classes. Attribute/resource setting is done with set/get member functions. The only time you have to refer to the actual widgets or windows is when you need to go down to the Xlib level. The bindings are very thin. There are only a couple of data members kept by the objects, one is the widget id, the other is a arg list and arg count used by the set/get functions. All set/get functions "queue-up" the attributes to be set/get much just they way you do with Xt. A member function equivalent to XtSet/GetValues is called to flush everything at once. The arg list is currently a fixed size, 100 element array. If I were to go into full production with my code this arg list would need to be changed to an array (preferably a C++ list object) which could grow as needed. 100 elements is a bit excessive for most applications. The delayed set/get action can be avoided by setting a static data member. All get functions take pointers to variables as parameters as a place to store the retrieved value (just like Xt). In many cases it would be preferable to just have the value be the return value of the call. This would be another change I would make to the bindings for production code. The bindings keep a global hash table of widget id to class instance mappings. When you call the object's constructor the mapping entry is put into the hash table. When an object is destroyed the hash table entry is removed. The bindings use the hash table for mapping Xt calls which return widgets to the objects that you want to really use in your C++ program. Callbacks, actions, and event handlers are defined with the aid of macros. The main thing these macros do is provide the two lines of code that access the hash table to convert the widget id normally passed to these functions into a pointer to the proper C++ class instance. This way you can keep the code for the callbacks in C++ and never have to worry about referencing widgets. This is a big difference between the Univ Lowell bindings and the Widget Wrapper Library (WWL) that you may have seen messages posted about. The Widget Wrapper Library doesn't keep a hash table mapping of widgets to objects. Therefore, all of your callbacks, actions and event handlers must be coded in straight C using Xt calls rather than in C++. Being able to access the C++ object in callbacks is even a bigger plus if you have subclassed the widget classes as I have done. The subclasses I have created have allowed me to keep track of additional data I needed for the display and operation on the widgets. This allows me to keep all the data I need right with the widget it pertains to without keeping a whole other data structure like I would in C. Subclassing also allows me to keep the functions which operate on specific objects all packaged together. The subclassing I have done only adds data and function members to the existing objects. It didn't change the basic way the widget works. IF you want to create an whole new widget you must first do the normal Xt subclassing in C, then write the proper C++ binding. The writing of the binding for a new class is straight forward. Several macros help you do it. I created a new binding for the Table widget in very short order. The callbacks, actions and event handlers are written as standalone C++ functions (not member functions). I found this a bit limiting since you either have to make the functions friends of your class or make any extra data members public (this applies only if you subclassed the objects as I did). What I did was write another set of callback, actions and event handler macros to make them call member functions on my subclasses instead of being standalone C++ functions. There are some problems with the bindings. Some of these are nits, others I think are major shortcomings in the software. Most of these could be fixed without a lot of effort. The first thing has to do with the destructors. The only thing the class destructors do is remove the widget<->object mapping from the hash table. The object instance will be destroyed but not the widget itself. What I would expect is if I destroy the object its associated widget gets destroyed too. Now destroying the widget could cause problems if objects were copied (you could have two objects pointing to the same widget). So what the objects need to keep is a widget and a reference count. The reference count is incremented when the object is copied. This way when the object is destroyed the widget is destroyed only if the reference count is 0. There needs to be more convenence functions/classes added. Creating menus is one places where this is nice. I have created a menu item object patterned after some of Doug Young's menu examples. I then added constructors on the menu classes to take a menu item list as a parameter. This way with on simple constructor call for a menu I get the entire menu created. And with one call to the menubar constructor I get the menubar, its menu items and all the pulldowns created. The bindings don't work with UIL (this may be a plus for some people :-) nor do they work with any of the interface builders available today. Getting the bindings to work with UIL would require changes to the MrmFetchHeirarchy call (and its relatives) to have the C++ objects created as well as the widgets. Most of the member functions are real functions. They should be reworked to make them inlines instead. Most of the member functions are 1-2 lines so inlines would just make everything more efficient. Objects are only created for "known" windows. If a widget automatically creates a "hidden" widget there is no C++ object to go with it. An example would be a scrolled window. With the bindings you will have a scrolled window object, but there will be no object that represents the automatically created clip window inside the scrolled window. Normally this isn't a problem until you try to traverse the parent-child heirarchy. Remember with the bindings you work entirely in C++. If I ask for a child's parent I get a pointer to the C++ object representing the parent. A child in a scrolled window is a child of the clip window, not the a child of the scrolled window. Asking for its parent gets a hash table error since there is no C++ object for the clip window. There is a XMString class provided with the bindings. However, this only implements the Motif XmString functionality. It really needs to have more of the functionality that you find with String classes found in class libraries like NIH. One function in particular would be a overloaded (char*) operator so XMStrings could be easily converted to normal char pointers. In contrast to the WWL, the XMString class has member functions to draw themselves, however these functions take the same parameters as the Motif XmStringDraw functions. They should take just the object pointer and figure out the window, display, etc out for themselves. There is no class for fontlists. You must use the stardard Motif mechanisms for dealing with fontlists. There are member functions for gaining access to the widget, window, display, and screen ids/pointers for an object. Most of the time you need these when calling Xlib functions. These member functions are fine, but overloading casting operators like (Widget*), (Screen), and (Display) might be a better way. The naming(capitalization) conventions used for the member functions is inconsistent. If the function represents an Xt function it is called and capitalized just like the Xt functions (sans the beginning Xt prefix). Other functions specific to the bindings start with initial lower case letters. This is a nit, but it's confusing to remember that Parent() is uppercase while display() and screen() are lower. The behind the scenes adding of resources done by some of the Motif composites are not handled properly. The form object is the biggest problem. All of the Attach<foo> like resources that are added to form children are incorrectly represented in the bindings by set/get member functions on the form widget. You really want the Attach<foo> member function to be on the child, but this member function only makes sense for children of forms. I don't know how to fix this one except by keeping these set/get functions on the form, but make them take the child widget as a parameter. That about wraps up my general comments. I can provide more info if someone has specific questions. I haven't yet sent my nits/shortcomings/bug fixes to OSF, but I will do so shortly. Overall, I have found the bindings to be easy to use. The general programming paradigm still follows Xt/Motif so there is no learning curve there. The bindings allowed me to do what I wanted in my prototype which is to code entirely in C++. However, I truly feel that these bindings are just an interim solution. What is needed is a true C++ class library/API that is not built on any existing C toolkit. This way true subclassing of the widgets to make new widgets would be possible. Hopefully the X consortium working off some kind of an Interviews base will get us in the right direction. My next message summarizes how you might obtain these bindings. ---------------------------------------------------------------------- Craig Timmerman ctim@aaet.csc.ti.com Texas Instruments Austin, TX ----------------------------------------------------------------------
nazgul@alphalpha.com (Kee Hinckley) (11/17/90)
> element array. If I were to go into full production with my code this > arg list would need to be changed to an array (preferably a C++ list > object) which could grow as needed. 100 elements is a bit excessive That's exactly what I did for my Motif wrappers. > The bindings keep a global hash table of widget id to class instance > mappings. When you call the object's constructor the mapping entry is > put into the hash table. When an object is destroyed the hash table > entry is removed. The bindings use the hash table for mapping Xt > calls which return widgets to the objects that you want to really use > in your C++ program. That sound expensive. Are all of the X[tm] functions really defined as member functions? E.g. What about XmProcessTraversal (well, I suppose that's only 1.1) or XtMapWidget. If every one of those is there that sounds like a fairly heavy object. > instance. This way you can keep the code for the callbacks in C++ and > never have to worry about referencing widgets. This is a big But it must produce a dummy callback routine no? Or does it just assume it knows the C++ calling conventions? Or does it have one callback for each signature, which then converts to object and calls the C++ callback? > The callbacks, actions and event handlers are written as standalone > C++ functions (not member functions). I found this a bit limiting > since you either have to make the functions friends of your class or > make any extra data members public (this applies only if you > subclassed the objects as I did). What I did was write another set of > callback, actions and event handler macros to make them call member > functions on my subclasses instead of being standalone C++ functions. I made dummy callbacks in C that called the C++ member functions. > The first thing has to do with the destructors. The only thing the > class destructors do is remove the widget<->object mapping from the > hash table. The object instance will be destroyed but not the widget > itself. This is not an easy problem. I fouled it up several times. My solution was to not actually use the destructor for the widget (except at the very base class). Particularly because in some instances I want to do window cacheing and don't *really* want to destroy the widget. What I did was have the object constructor put a delete callback on the widget. When I want to destroy an object I call a special deleteWidget member function. That function then calls XtDestroyWidget on the widget, which then calls back to destroy the object when the time comes. This allows caching and also ensures that the object doesn't get referenced after it's been deleted. > There needs to be more convenence functions/classes added. Creating > menus is one places where this is nice. I have created a menu item They don't do that? That's the main reason for using wrappers in my mind. I don't create them for lower class objects, just for the complicated ones (like list, text, dialog, main window) and for complex ones like menubar, pulldown, optionmenu. > window. Asking for its parent gets a hash table error since there is > no C++ object for the clip window. But you need to know the parent for lots of things! Or does it not do the convenience widgets like ScrolledText and ScrolledList? >> in class libraries like NIH. One function in particular would be a > overloaded (char*) operator so XMStrings could be easily converted to > normal char pointers. In contrast to the WWL, the XMString class has That can't be done without modifying the header files (since XmString was typedef'd to char *). At 1.1.1 it should be fixed to be unsigned char *, which will allow overloading like that. Thanks for the summary! -kee P.S. I should mention that the reason I haven't released my widget classes is that they are a) pretty tightly bound with very non-widget related classes and b) only cover the subset of widgets that I cared about. However if anyone wants specific examples (menu stuff, or base classes or whatever) I'd be happy to ship out pieces, but they'll be more useful as a reference than a toolkit.
ctim@dolphin.aaet.ti.com (Craig Timmerman) (11/20/90)
> > The bindings keep a global hash table of widget id to class instance > > mappings. When you call the object's constructor the mapping entry is > > put into the hash table. When an object is destroyed the hash table > > entry is removed. The bindings use the hash table for mapping Xt > > calls which return widgets to the objects that you want to really use > > in your C++ program. > That sound expensive. Are all of the X[tm] functions really defined as > member functions? E.g. What about XmProcessTraversal (well, I > suppose that's only 1.1) or XtMapWidget. If every one of those is > there that sounds like a fairly heavy object. I haven't done an exhaustive search, but most of the Xt/Xm calls are member functions (X11R3 and Motif 1.0 only). I don't really know what you mean by heavy. There is a short stub function for each Xt/Xm call supported as a member function. The added code for each function will add code size, but only once, not per created object. The objects are call overhead heavy since the member functions are true functions. This is one area that should be fixed. It would be much more efficient if they were just inlines. The only true overhead each object carries are data members for the Widget, arglist, and a argcount. > > instance. This way you can keep the code for the callbacks in C++ and > > never have to worry about referencing widgets. This is a big > But it must produce a dummy callback routine no? Or does it > just assume it knows the C++ calling conventions? Or does it have > one callback for each signature, which then converts to object and > calls the C++ callback? I should have been more explicit here. The macros you use to define the callbacks do create normal Xt callbacks. The macros actually just define the first three/four lines of the callback (the callback definition, opening brace, do the widget->object hash table look up). You provide the rest of the callback code including the terminating brace. The thing this scheme buys you is the widget to object mapping. All of the code you write/see only deals with the object and not the widget. > > The first thing has to do with the destructors. The only thing the > > class destructors do is remove the widget<->object mapping from the > > hash table. The object instance will be destroyed but not the widget > > itself. > This is not an easy problem. I fouled it up several times. My solution was > to not actually use the destructor for the widget (except at the very > base class). Particularly because in some instances I want to do > window cacheing and don't *really* want to destroy the widget. What > I did was have the object constructor put a delete callback on the > widget. When I want to destroy an object I call a special deleteWidget > member function. That function then calls XtDestroyWidget on the > widget, which then calls back to destroy the object when the time > comes. This allows caching and also ensures that the object doesn't > get referenced after it's been deleted. I don't know if I follow all your reasoning here. Since I create 99% of these wdiget objects using the new operator they are going to be around (cached) until I explicitly delete them. Therefore the destructor seems to be the right place to put the widget destruction. > > There needs to be more convenence functions/classes added. Creating > > menus is one places where this is nice. I have created a menu item > They don't do that? That's the main reason for using wrappers in > my mind. I don't create them for lower class objects, just for the > complicated ones (like list, text, dialog, main window) and for complex > ones like menubar, pulldown, optionmenu. I agree fully. They only provide equvilents for what already exists. They didn't try to add functionality. This is probably just because of time constraints and lack of trying this out on a large scale product. All of the demo/test programs that came with it are just variations on "Hello World". You don't get a very good feel for what additions need to be made with tests like that. This is a major area for expansion IMHO. Any expansion should also be done in pure C++. I.E. Use C++ classes where ever possible. > > window. Asking for its parent gets a hash table error since there is > > no C++ object for the clip window. > But you need to know the parent for lots of things! Or does it not > do the convenience widgets like ScrolledText and ScrolledList? It supports all of the widgets. This is why this is such a big problem. I have only run into it on scrolled windows, but any Motif widget that creates widgets for you will be a problem. I think it's a fixable problem (some of the constructors would need to be smarter and create extra objects), but it's a problem none the less. ---------------------------------------------------------------------- Craig Timmerman ctim@aaet.csc.ti.com Texas Instruments Austin, TX ----------------------------------------------------------------------
nazgul@alphalpha.com (Kee Hinckley) (11/20/90)
> > This is not an easy problem. I fouled it up several times. My solution was > > to not actually use the destructor for the widget (except at the very > > base class). Particularly because in some instances I want to do > > window cacheing and don't *really* want to destroy the widget. What > > I did was have the object constructor put a delete callback on the > > widget. When I want to destroy an object I call a special deleteWidget > > member function. That function then calls XtDestroyWidget on the > > widget, which then calls back to destroy the object when the time > > comes. This allows caching and also ensures that the object doesn't > > get referenced after it's been deleted. > > I don't know if I follow all your reasoning here. Since I create 99% > of these wdiget objects using the new operator they are going to be > around (cached) until I explicitly delete them. Therefore the > destructor seems to be the right place to put the widget destruction. That assumes that you always explicitly delete each widget. In fact this is hardly ever the case (I mean you *could*, but it would be a pain). In fact you are more likely to delete the top-level object and expect all the others to go away automatically (just like they do in Xt). Assuming that this is the case you now have two choices. You can mimic the Xt hierarchy and chase down the objects and explicitly delete them. That would be wrong. Or you can tell the widgets to delete the objects when they get destroyed (via an XmNdestroyCallback). However if you do the latter case, then you have the problem of not really knowing whether this particular object was deleted via 'delete' from your code, or from the Xt callback. And you start running into situations where you delete it, it deletes the widget, the widget callbacks and deletes the object *again*. Definitely not good. You can probably work these out, but I found it easier to never explicitly delete a wrapper object, but instead to call a method which deletes the widget, which then calls back and deletes the object. That ended up being much easier to keep track of.
basti@orthogo.UUCP (Sebastian Wangnick) (11/23/90)
ctim@dolphin.aaet.ti.com (Craig Timmerman) writes: >> > instance. This way you can keep the code for the callbacks in C++ and >> > never have to worry about referencing widgets. This is a big >> But it must produce a dummy callback routine no? Or does it >> just assume it knows the C++ calling conventions? Or does it have >> one callback for each signature, which then converts to object and >> calls the C++ callback? >I should have been more explicit here. The macros you use to define >the callbacks do create normal Xt callbacks. The macros actually just >define the first three/four lines of the callback (the callback >definition, opening brace, do the widget->object hash table look up). >You provide the rest of the callback code including the terminating >brace. As all of you seen to write those wrappers, here's my DM 0,02 worth of approach: Use static member functions as callbacks. They are C-callable plus you don't need extra friends. Sebastian Wangnick (basti@orthogo.uucp) PS: inews hack .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. .................................................. ..................................................