I would like to share with you a neat technique of using function objects
with the BMessage
class to simplify some common patterns in your
application code.
Consider a menu containing an item for every window open in an application. When an item from the menu is selected, we would like to call some code to make the corresponding window active. Part of the menu building code might look something like this:
for (int32index
= 0;index
<windowCount
;index
++) {BWindow
*window
=windowList
->ItemAt
(index
);BMessage
*message
= newBMessage
(kSelectWindowMessage
);message
->AddInt32
("index",index
);menu
->AddItem
(newBMenuItem
(window
->Title
(),message
)); }
We would then have a corresponding MessageReceived()
function, that would
contain the following code:
switch (message
->what
) { casekSelectWindowMessage
: int32index
;message
->FindInt32
("index", &index
);windowList
->ActivateIndexedWindow
(index
); ...
where windowList
is of an unspecified WindowList
class.
When we were building up the window menu, we knew ahead of time that we
would end up calling ActivateIndexedWindow()
when an item was selected, and
passing it the index as a parameter, we knew we would call it on the
windowList
object. It seems like it would be convenient to be able to
hide the corresponding message dispatching and parameter packaging code
into some more convenient shorthand. Wouldn't it be nice if instead we
could write:
for (int32index
= 0;index
<windowCount
;index++
) {BWindow
*window
=windowList
->ItemAt
(index
);BMessage
*message
= newNewFunctionObjectMessage
( &WindowList
::ActivateIndexedWindow
,windowList
,index
);BMenuItem
*item
= newBMenuItem
(window
->Title
(),message
);menu
->AddItem
(item
); }
and get rid of the code in MessageReceived()
altogether. We will try to do
just that using a function object packaged inside a BMessage
.
As a result of selecting our menu item we need to be able to call a function on an object with some parameters, in our case:
windowList
->ActivateIndexedWindow
(index
);
A function object is an object that packages all this information—the function code address itself, the object we call it on and possibly some parameters. This information is all it takes to call the function.
Since we want the user interface and the execution parts of the application to be coupled loosely to support the multithreaded environment well, we need to be able to collect all that information at one point and use it to invoke a function call at a different point. We can use the following class to do that:
classFunctionObject
{ public:FunctionObject
(void (WindowList
::*callThis
)(int32),WindowList
*onThis
, int32withThis
) :function
(callThis
),target
(onThis
),parameter
(withThis
) { } voidoperator
()() const { (target
->*function
)(parameter
); } private: void (WindowList
::*function
)(int32);WindowList
*target
; int32parameter
; };
Using this class we can construct a function object:
FunctionObject
*functor
= newFunctionObject
( &WindowList
::ActivateIndexedWindow
,windowList
,index
);
Save it away into the functor variable and when the right time comes, call it just like this:
(*functor
)();
All the parameters (one ulong in this case) and the target object get passed correctly, as if we called
windowList
->ActivateIndexedWindow
(index
);
The FunctionObject
class we have here would not be very practical,
because it is pretty much hard coded for this particular example. We will
need to make the class a little bit more general. We will use templates,
that way we can adapt an appropriate function object to any target class,
member function and parameter types. Templates won't handle the fact that
we may have zero, one, two or more parameters in a call or that some
member functions need to be const
so that they can get called on a const
version of the target object. To work around that we will just use
inheritance and have a function object template for one of each of these
calling patterns, that way we will have classes representing function
objects for each of these:
function
();function
(param1
);function
(param1
,param2
);function
(param1
,param2
,param3
); ...
We might add a flavor of each of these that has a non void return type,
but we don't really need that for the kind of code we are trying to
write. In a real world version of a function object class we would also
want const
flavors that represent invoking a const
version of a member
function, taking a const
target pointer. We will leave both of these out
for simplicity.
classFunctionObject
{ // the abstract superclass that defines the common interface public: virtual~FunctionObject
() {} virtual voidoperator()
() const = 0; virtual ulongSize
() const = 0; }; template<classFT
, classT
> classPlainMemberFunctionObject
: publicFunctionObject
{ // function object without any parameters public:PlainMemberFunctionObject
(FT
callThis
,T
*onThis
) :function
(callThis
),target
(onThis
) { } virtual~PlainMemberFunctionObject
() {} virtual voidoperator()
() const { (target
->*function
)(); } virtual ulongSize
() const { returnsizeof
(*this
); } private:FT
function
;T
*target
; }; template<classFT
, classT
, classP
> classSingleParamMemberFunctionObject
: publicFunctionObject
{ // function object with a single parameter public:SingleParamMemberFunctionObject
(FT
callThis
,T
*onThis
,P
withThis
) :function
(callThis
),target
(onThis
),parameter
(withThis
) { } virtual~SingleParamMemberFunctionObject
() {} virtual voidoperator()
() const { (target
->*function
)(parameter
); } virtual ulongSize
() const { returnsizeof
(*this
); } private:FT
function
;T
*target
;P
parameter
; }; template<classFT
, classT
, classP
> classDoubleParamMemberFunctionObject
: publicFunctionObject
{ // function object with two parameters public:DoubleParamMemberFunctionObject
(FT
callThis
,T
*onThis
,P1
withThis1
,P2
withThis2
) :function
(callThis
),target
(onThis
),parameter1
(withThis1
),parameter2
(withThis2
) { } virtual~DoubleParamMemberFunctionObject
() {} virtual voidoperator()
() const { (target
->*function
)(parameter1
,parameter2
); } virtual ulongSize
() const { returnsizeof
(*this
); } private:FT
function
;T
*target
;P1
parameter1
;P2
parameter2
; };
...and so on.
It gets a little repetitive, but you only do it once for your whole app.
We have code to construct and call a function object, we will now add
code that packages a function object into a BMessage
on one side, unpacks
and executes it on the other side. We will use yet another class for that:
classFunctionObjectMessage
{ public: static boolDispatchIfFunctionObject
(BMessage
*); protected: staticBMessage
*NewMessage
(constFunctionObject
*); };BMessage
*FunctionObjectMessage
::NewMessage
(constFunctionObject
*functor
) {BMessage
*message
= newBMessage
('fCmG'); // any message with this pattern // will be a function objectmessage
->AddData
("functor",B_RAW_TYPE
,functor
,functor
->Size
()); // invoke the virtual Size call here to get the actual size of // the concrete function object subclass // need to check for errors in a real app returnmessage
; } boolFunctionObjectMessage
::DispatchIfFunctionObject
(BMessage
*message
) { if (message
->what
!= 'fCmG') return false; // find the function object longsize
; constFunctionObject
*functor
; status_terror
=message
->FindData
("functor",B_RAW_TYPE
, &functor
, &size
); if (error
!=B_NO_ERROR
) returnfalse
; // function object found, call it (*functor
)(); returntrue
; }
We are now ready to start using function objects in our code. First of all in every looper that receives messages we add the function object dispatch stub that we just wrote. Note that you will typically only need to send function object messages against a looper, not against the individual handlers it might have. The dispatching mechanism picks the right function and object for you.
voidMyApp
::MessageReceived
(BMessage
*message
) { if (FunctionObjectMessage
::DispatchIfFunctionObject
(message
)) return; // not a function object, handle other messages here ...
Then we instantiate a function object for every signature of a function
we call. You can conveniently do that in a subclass of the
FunctionObjectMessage
we already defined. In our example we were trying
to call a function of some WindowList
class:
classWindowListFunctionObjectMessage
: publicFunctionObjectMessage
{ public: staticBMessage
*NewFunctionObjectMessage
(void (WindowList
::*func
)(),WindowList
*target
) { returnNewMessage
( &PlainMemberFunctionObject
<void (WindowList
::*)(),WindowList
>(func
,target
)); } staticBMessage
*NewFunctionObjectMessage
(void (WindowList
::*func
)(ulong),WindowList
*target
, ulongparam
) { returnNewMessage
( &SingleParamMemberFunctionObject
< void (WindowList
::*)(ulong),WindowList
, ulong>(func
,target
,param
)); } };
Here we are providing a NewFunctionObjectMessage
call for a signature of
each function we want to call on a given class, in this case void
(
and WindowList
::*)()void (
WindowList
::*)(ulong)
All we have to do now is use it.
for (int32index
= 0;index
<windowCount
;index
++) {BWindow
*window
=windowList
->ItemAt
(index
);BMessage
*message
= newWindowListFunctionObjectMessage
::NewFunctionObjectMessage
( &WindowList
::ActivateIndexedWindow
,windowList
,index
);BMenuItem
*item
= newBMenuItem
(window
->Title
(),message
);menu
->AddItem
(item
); }
This is pretty much what we tried to do in the first place.
What is the big advantage of using this approach?
Using this technique we might save some code in a medium-to-large sized
app. It is really easy to add new user interface elements and supporting
code: You write the function that gets called when your control/menu
item/keyboard shortcut gets invoked, on the user interface side you add
code to create a corresponding function object message, but you don't
have to go and add anything on the receiving side, in the MessageReceived()
function of a corresponding handler.
Also this technique takes full advantage of C++ static type checking
support. In our example we used simple ulong parameters, consider if you
passed around some more elaborate types. Without this technique you would
be embedding the parameters into a BMessage
using AddData
/FindData
and
using a lot of casts. If you embedded one type on the sending side but by
mistake retrieved a different type on the receiving side, you would not
get any compiler warnings. Using function objects you would get a
compiler error whenever you try to pass a destination function of one
type and parameters of a different type.
You still have to worry about multithreading issues when using this
technique. If you dispatch a function object in one looper that ends up
calling a function/target object in another looper, you have to make sure
the other looper is locked correctly, typically in the target function.
The best way around this is to just target the right looper by targeting
the original invoker. That way the target looper gets locked for you as a
part of calling MessageReceived()
.
Passing a function object to a looper in a different address space will not work at all; the function address and the object itself will be inaccessible from the different address space.
Having class member functions as template parameters is a relatively new C++ feature and the Metrowerks compiler only supports it in the DR3 version (released with BeOS Advanced Access Preview Release) or later. The code we used here would not work with DR2 compilers.
Hello! Let me take this opportunity to introduce myself—I'm Brian, one of the Be support staff. I am often the one who replies to messages sent to custsupport, devsupport and devservices@be.com, so some of you have already had some interaction with me.
When I'm not running around the company searching for the answers to your questions, I accept (and occasionally regretfully deny) your applications to become official registered Be developers. I also test developer's BeWare submissions on the ftp site, and then move them into their appropriate pub/contrib directories.
And from time to time, I get pulled into helping out on whatever tasks need extra assistance at that particular moment (as does every Be employee). You may have seen me at Apple's WWDC, or you may read the few sections of the Be User's Guide authored by yours truly (10 points if you can guess which ones!).
Now that I have formally introduced myself, let me be a wee bit honest. I recently returned from vacation to find a friendly e-mail message from Valerie informing me of my assignment to write a Be Newsletter article.
Bewildered for a moment, it hit me—take this opportunity to try to clarify four issues which in the past we might have made confusing, in order to help you become informed about Be and the BeOS. Let me start with the required Be newsletter metaphor and go from there...
I recently went back to Massachusetts to attend my high school reunion. While there, I spent two nights in a row stargazing and reminiscing with an old friend. Friday night was rather overcast, with only the occasional star poking out here and there, but Saturday night was something else entirely. There was not a cloud in the sky and with the air in rural western Massachusetts, you can see EVERYTHING, from your favorite constellation to the beautiful stream of the Milky Way.
How does this pertain to online support at Be? I like to look at it in the following way: Trying to find information on some web sites is much like trying to stargaze on an overcast night. You might find a nugget or two of information, but for the most part, there are still unanswered questions.
Realizing how annoying this is for customers, at Be we try to make it as easy as stargazing on a clear night in rural Massachusetts. We believe we have very little to gain by keeping information from you. So before you ask your neighbor if Orion can be seen, try looking up. You'll be surprised at what you'll find in the sky—or on the Be web site!
Still, we do occasionally cloud our own skies; I can see that in some of the e-mail messages I respond to. I'd like to make an attempt to blow the clouds away from those issues, and clear the skies so you can see Neptune...
If you are applying to become a registered Be developer:
Please, be talkative! It can only help you in getting accepted into our registered developer program. (And should you be rejected, don't take it personally! It doesn't mean that you can't develop for us.)
I'm referring to the "Previous Experience" and "Potential Apps" fields here. This is the only way that we can tell whether or not you are qualified to be a registered developer (as much as I wish it was otherwise, I am not psychic).
If you are a registered developer who submits your software to our ftp site:
Please follow the ftp submission guidelines found at the following URL:
http://www.be.com/developers/ftp/uploading.html
If you do not, your software will not be moved from the incoming directory. We'll try to contact you if there are problems, but the quantity of submissions make it necessary for our guidelines to be strict.
"Where can I find useful information about Be and the BeOS?"
Check our web site! I was recently talking with one of the gentlemen from the company who will be handling custsupport@be.com, and he was not shy in describing his awe at the amount of information we provide on our web site.
Both Ron and Michael do a terrific job in providing as much up-to-date information on the web site as possible—make their jobs worthwhile!
"Where should I write, if I do not find my answers on the web site?"
Here is the definitive list of Be e-mail addresses. Even though Be is pretty small now and a single person (such as me) might oversee more than one of the incoming support e-mail addresses, this won't always be the case. It's best to have everyone understand our addresses now, so there isn't any confusion later on.
As Be grows, more people will be handling the incoming support e-mail and it's in your best interest to send it to the right place, as we will then be able to respond to you more quickly. This will actually happen sooner than you might think, as we have just hired a company to handle all custsupport@be.com e-mail (and eventually phone calls too!).
bedemotour@be.com
Interested in seeing the Be Travelling Circus visit your locale? Forward any questions about hosting a Be demo day at your school (user group meeting, etc.) to this address. Sorry, we don't do bachelor / bachelorette parties.
custservice@be.com
Write to this address for assistance with non-technical customer issues. Your BeOS CD didn't arrive? Want to know the status on your product order? BeBox service and warranty issues? This is the place for you!
custsupport@be.com
This is the address to which you write for help with BeBox and BeOS technical questions which are not answered in the Be FAQs. So for questions concerning installing, configuring, and using the BeOS (not to mention software or hardware compatibility), write to this address
devservices@be.com
This address is set up to help our registered developers with developer-specific, non-technical issues. Should you need to change your name or company information in the developer database, check the status of your developer application, or have issues with your submissions to our ftp site, write here!
info@be.com
Write to this address for general information on Be, the company.
jobs@be.com
Think you're the perfect person to fill that really awesome position at the greatest company on earth? Write to this address!
E-mail an ASCII version of your cover letter and resume, your e-mail and postal addresses, and both day and evening phone numbers in the *body* of an e-mail message (attached documents will not be considered). Don't forget to put the title of the position you're applying for in the subject line of your message.
newsletter@be.com
Do you really like this article, or the newsletter in general? Please feel free to express your enthusiasm to the editor! Questions and comments are welcome as well.
orders@be.com
Pssst! Wanna buy a BeOS CD? We got some "choice" Be T-shirt to accompany that CD, as long as you're in the buying mood...
webmaster@be.com
This is the address to which you write if you have any difficulties with Be's Internet-based services and information. So if you have found a broken link, received an error message, have a suggestion, or are having problems with a Be electronic mailing list, webmaster@be.com is the address for you!
So that's it. I hope this article is of help to you and has cleared up any confusion we may have caused concerning any of the four issues I covered. Let me know if I need to brush up on my metaphors...
Can you spot the change from the last version?
status_tHomeDirectory
(char *dir
, constintbuffLen
) { app_infoinfo
;be_app
->GetAppInfo
(&info
);BEntry
appentry
;appentry
.SetTo
(&info
.ref
);BPath
path
;appentry
.GetPath
(&path
); strncpy(dir
,path
.Path
(),buffLen
);dir
[buffLen
-1] = '\0'; returnB_NO_ERROR
; }
Anyway... Blue box, yellow box, little blue box, Windows NT, pizza, Jolt, Mountain Dew, 90 degrees, 99.999% humidity... What does this sound like?
Mac Hack of course! You know, it's kind of funny. The conference starts at Midnight and goes on for a few days. You do a lot of, "How late did you stay up?", "5am". But then you don't wake up until 12pm. You might as well just go to bed on time and get up early, but that would not adhere to the hacker ethic.
It was fun. Yasmin and Anita came along too. While I was staying up bleary eyed, they were visiting the Henry Ford Museum, and other choice places around Detroit. This conference really is for the Mac faithful. The crowd was split between really old timers, and really new timers. The Woz gave his life history, which I actually found to be interesting, maybe because it didn't end until 3am. He even managed to slip in a controversial analysis of Steve Job's opinion of Apple's NeXT purchase. And in his mind, Apple should be concentrating on K-12.
Well, Benoit did a hack and gave a show. It was a real-time Mandelbrot zoomer that had the crowd cheering even before he showed the "fast" version. It didn't win one of the many awards, but things like multi-colored menus did. Things just aren't what they used to be. The take home point for me was, we are merely guests at this party.
ftp://ftp.be.com/pub/dr9/samples/mandelhack.tgz
I spent my time doing a hack as well, but I didn't feel like submitting it because it actually might be useful.
ftp://ftp.be.com/pub/dr9/samples/pciviewer.tgz
This thing is like PCIProbe and the poke command combined. It will list your PCI devices in the left half of the window using BOutlineListView, and show the actual register values and other stuff on the right when you click on the individual entries. For those of you who have been wanting to use the BOutlineListView, here's you're chance. It uses this class, as well as a custom ListItem. What great fun. I thought about making it into a full-fledged PCI debugger like poke is, but I ran out of time. Maybe that's an exercise for the reader.
Well, the Preview Release is now headed for the doors and the final anticipation can begin. You wouldn't believe how tense things can get when a lock down is in progress. We have a thing here called Dr. No. That's an engineer who is assigned the unthankful task of saying "NO!" to last minute changes so that the final release is as stable as possible, and actually gets out the door on time. The couple of Dr. No's did an excellent job and I think we have a really good release headed your way. As you read the release notes you will find that although we did a tremendous amount of good work, you can expect there to be some anomalous behavior. This release is a good solid foundation upon which our future can be built.
When you do finally get it within a few weeks, code like mad, give us feedback, and move on to commercial Nirvana.
Geoff is back from Europe, and his first e-mail said he is anxious to write an article about the experience. So, not to steal any steam, I'm sure you can look forward to an enlightening expose from our American in Paris.
I saw the question being asked in several on-line messages; this is a good opportunity to clarify our position on the matter of UI guidelines. First, as I write this in the Geneva airport, and if I believe e-mail and voice-mail messages, we're in great danger of cutting the final, really, Preview Release CD and sending it to duplication.
The developer conference AA:PR CD gave us a great deal of feedback, energetic and, for the most part, encouraging. Positive or humbling, the feedback is most appreciated and we're doing our best to reciprocate, that is, making this developer release a credible foundation upon which to start building useful and money-making applications. This, of course, is for developers to decide. I rest secure, sort of, in the knowledge we're not dealing with a shy and retiring community.
Still with the UI guidelines in mind, the Preview Release was a very ambitious project. Some would say we deluded ourselves when we started, others might add this is the very kind of denial required for starting a family, remodeling a house—or writing OS software. Among the features and functions we wanted to improve, the user interface. The graphic look, up to DR 8, was well received, but function was found wanting. In particular, the "dock" concept didn't get many votes. What we got, instead, were many requests for a (now) more conventional desktop, keeping the workspaces.
Make it so, you said.
This reopened many UI issues and launched us into a great deal of experimenting with everything from color schemes, to text, to icons, to taskbars, to preferences... In many respects, for the Preview Release, UI decisions were made last. The next result is our documentation is behind the software, itself behind schedule—with our apologies.
Now, when it comes to UI guidelines, I understand the need for them and, at the same time I fear them a little. I don't want them to stifle creativity. Call this trying to put a good face on our lateness, but after our sometimes painful experimentation with UI function and look, I hope we'll see some daring exploration from Be developers. You're not going to see UI ayatollahs from Be demanding observance to UI guidelines, published or not. I think it's a) for us at Be to set one (by no means representing it to be the only one) example of decent UI design and, b) for users to vote with their wallet for what they like most, no reasons asked.
Of course, there is danger with that approach. Anarchy, ugliness, confusion. As for anarchy, I firmly believe developers will do what they want, anyway. The meek will not inherit our region of the Earth. For ugliness, it disappears quickly, unless the product is incredibly useful, or inexpensive. All of which is fair. Lastly, confusion. In the early days of bit-mapped screens and pointing devices, perhaps, when customers, even sophisticated ones, were unfamiliar with the emerging paradigms, sorry, ways.
No such problem anymore.
Still, no excuses, we need to, and will publish UI guidelines, if only to provoke creativity and observance in the breach.