|
|
|
Displaying Newsletter
|
Issue 4, 03 Nov 2001
|
|
|
|
|
I have been recently exposed to .Net by a couple of colleagues of mine. People who are very bright and are Unix folks from way back. Who have often had little good to say about the Microsoft Hegemony.
For those of you who may not be so "enlightened" as to the purpose and concept of .Net , let me attempt to explain from a high-level perspective. The concept, at least as far as I have seen, is that web pages can be authored using an object hierarchy. This object hierarchy uses JavaScript as its basis for client-side interactions. You can derive from a standard object and build a new object which other .Net programmers can use. These objects can have two different types of interactions - server-side and client-side. Client-side interactions are typical JavaScript callbacks (mouse over, etc). Server-side interactions are what we used to call "Submits" - so named after the concept that the end user would fill in some data on a web form and press the submit button. The page's information, including object states is sent back to the web server where it is processed.
The clever part of this, on the business side of things is that, of course, you need a web server. And guess who makes the only web server that handles .Net objects? Yes, surprise, it is IIS. Microsoft sells updates to Visual Studio to developers, and more web server licenses.
The clever side of this on the technical end of things is that developers are being pressed to make everything a "web application". Much of this perspective relates to the poor way that Windows (even the vaunted XP) deals with the installation of software. You have to be an administrator to install a good deal of software. Since IT staff can't/shouldn't/won't give that right to everyone, IT staff has to install all of the software, which makes every upgrade that a company goes through costly (overtime, etc). Compare this to installing the software once, on the web server, and letting everyone access it - no licensing issues, no dll misery - just apps. Reminds me of my mainframe days.
Of course, if the applications and the operating system did the right thing, this would not be an issue. Applications should not need to install DLL's, and especially not into the OS's directories. The whole registry concept should be expunged as quickly as possible. .INI files, much like XML files, were human readable, portable and made sense, so out the door they went.
Where does all of this leave us, the OBOS designers? First of all, this is (yet another) "innovation" that makes web browsers a tougher game to play. Unless you are running Windows, you may not be able to view .Net pages. Wait - I thought you said JavaScript? Yes, but how long will it be before JS is embraced and extended? Bill would tell you that this just goes along with Microsoft being a natural innovator. Sure. Right.
Let me just toss an idea off of you all. BeOS is, inherently, client/server. More than 95% of the method calls in any of the kits are translated into BMessages that are sent to the server to execute. Imagine if someday we had BMessages that could travel over a LAN/internet. Muscle already has something similar. Now lets assume that there was a BASIC-like language (BScript, maybe, or Squirrel) that the web browser could parse, much like JavaScript. It would not be hard to imagine that the whole GUI for an application could be written using that BScript language. The main application (i.e. the back end) runs on the web server. It *thinks* that it is talking to app_server. But the BMessages are, instead, funneled over the internet to the browser where they are processed. To the app, it "feels" like it is running on a normal BeOS machine, but the user is using it remotely. Figure something like VNC, but faster (not transmitting GFX) and more responsive.
And better than .Net .
|
|
|
The BeOS API is a rich source of functionality for any programmer to use. But all that functionality is tied up in C++ code. So programmers
using only vanilla C can't play, right? Not so fast. It might not be obvious, but it's actually quite easy to do mixed mode programming
with C and C++. In this article, I'll give a simple example to show how to do this.
Of course, you may be wondering... why? Does anyone program in C anymore? You better believe it! It's still one of the most widely used
languages and I don't think that it's going to go away anytime soon. Some people actually like C. I know this is true because I'm one
of them (but hey, I'm normal in every other respect... sorta.)
But isn't C++ just a superset of C? Couldn't you just rename C files to have ".cpp" extensions and compile them as C++ programs?
Well... C++ is intended to be a superset of C as much as possible. But there are several subtle differences between the two. Merely treating
a C file as a C++ file may not work. The simplest example would be C programs using C++ reserved words such as "this" or "operator".
But there are also lots of other subtle differences in such things as function prototypes, extern declarations, character literals, enum assignment,
assigning void pointers, various scoping rules etc. such that a legal C program would not compile as C++.
Resolving these problems could be tricky for larger C programs.
Still, even if your C program will successfully compile as a C++ program, does that mean you should change the file extension? C programs
traditionally end in ".c" and nothing else. C++ programs usually end in ".cpp", although there are a few other variations. Just because a
file now has a ".cpp" extension doesn't make it a C++ program, at least not in the usual sense -- i.e. a program that makes use of classes,
inheritence, function overloading, etc. If a program consists of dozens of standard ANSI C files (comprising tens of thousands of lines of code),
does it make sense to rename all these as ".cpp" files because there is one instance of a C++ function being called?
That's a bit foolish and quite misleading.
Besides, you may prefer to stick with a C compiler for other reasons. For one thing, C compilers tend to be much faster.
This is no surprise because C is a much simpler language than C++. C compilers also tend to produce smaller executables.
If you don't believe me, try compiling a C program either way and compare the results. And frankly, I trust C compilers more.
This may be paranoia, but C++ is such a complex language that I feel it is much more likely for C++ compilers to have non-trivial bugs in them.
For this reason alone, I tend to steer away from the more arcane features of C++ when I'm writing in that language.
So ultimately, it comes down to this. C programs abound. Many people, including myself, enjoy using the language.
Treating C programs as if they were actually C++ programs, however, is misleading, possibly leads to errors, and in many cases doesn't make sense.
However, there are often many very useful functions wrapped up in C++ libraries that a C programmer would like to access. The BeOS API is one
obvious example.
Fortunately, calling C++ libraries from C functions is quite easy. The key is to stop thinking of C as just a subset of C++ and, instead, as
a different language. Then you can do mixed-mode programming just as you would with any mix of languages. In fact, even easier, because
C and C++ do share a lot of common behavior, such as how they handle pushing function arguments on the stack.
In fact, this is how easy it is: suppose you have a C file called fred.c and you have a C++ file called joe.cpp .
What do you need to do in the BeOS to compile these two together? How about:
gcc fred.c joe.cpp
That's not too tough is it?
You might wonder how this works. Well, keep in mind that gcc itself (the executable) is only a small program that parses the command line
and then calls other programs with the appropriate arguments. The gcc program (i.e. the entire suite) contains both a C compiler (cc1 )
and a C++ compiler (cc1plus ).
Furthermore, it has a little bit of smarts about which one to use.
If you write "gcc foo.c " on the command line, then gcc will notice the ".c" extension and automatically call the C compiler.
However, if you write "gcc foo.cpp ", then gcc will instead call the C++ compiler. There are command line options that allow
you to specify which compiler to call regardless of the file extension, but this default behavior is generally the most useful (and simple)
way to invoke the proper compiler.
Thus, in the sample call above, gcc will call the C compiler for fred.c to generate the object file fred.o .
It will call the C++ compiler for joe.cpp to generate the object file joe.o . They are now both machine code.
The linker can then easily stitch them together, providing all symbolic references can be resolved (including library calls).
For this to happen, I'll have to bring up "C" linkage. Rather than doing this in the abstract, I'll use a simple example.
A MIME type string example
One useful BeOS feature that a programmer might well want to add to his C program is MIME type strings.
Every file in the BeOS carries around its own MIME string ("text/plain", "image/gif", etc.).
There is a simple way to access this information from the BeOS API. Without access to it, however, a C programmer would likely find himself
implementing some kludge such as making a long list or table that maps filename extensions into MIME strings -- for example:
".txt", "text/plain"
".html", "text/html"
".jpg", "image/jpeg"
. . .
Eek! This is mightly tedious. And it doesn't even work, at least not in general, because BeOS doesn't require files to have these
extensions. Calling the BeOS API is much simpler, more elegant, and will always give the right answer.
So how do you get the MIME string using the BeOS API? Just call the function GetType that is part of the NodeInfo class.
You pass a character buffer to the function and it will fill it with the appropriate MIME string, if one is found. But how do you get the NodeInfo
object? Generally you would start with a filename string, so how do you end up with the right NodeInfo object for a given filename?
It takes a few steps, but it's pretty simple. Here's a complete C++ file that contains the needed MIME retrieving function:
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
//
// mime.cpp
//
// wrapper file for getMimeType (callable from C programs)
//
// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
#include <NodeInfo.h>
extern "C"
{
void
getMimeType (char *path, char *mimebuf)
{
// retrieves the MIME type string for a given file
//
// in: the full pathname of a file
// out: the file's MIME string (copied into the buffer)
*mimebuf = 0; // initialize the buffer to an empty string
BEntry entry (path);
BNode node (&entry);
BNodeInfo info (&node);
info.GetType (mimebuf);
}
}
First, you use the filename to create a BEntry object. This object can be used to instantiate the corresponding Node object.
Now that you have the right Node , you can use it to get the appropriate NodeInfo object.
Finally, you can call GetType which will fill the buffer with the MIME string.
Just save this code as mime.cpp and you're ready to use it with C programs.
Notice the extern "C" wrapper placed around the function definition.
This is crucial -- you can't leave it out. You see, C++ supports a feature called function overloading that allows several functions with the
same name but different return types and/or parameters to co-exist. This is possible because the C++ compiler actually changes each function name
into something unique. As such, the object code for compiled C++ files would ordinarily include symbolic references to functions with weird, encoded
names. It's called C++ name mangling and is something that any programmer who interfaces with C++ libraries has to deal with.
Fortunately, it's
easily taken care of with the extern "C" directive. This directive tells the compiler to leave the friggin name alone! Thus, in the sample
above, the symbol "getMimeType " will actually be present in the mime.o object file. This will allow any C program that
is compiled with a reference to "getMimeType " to be properly linked with mime.o .
Now, to call the function....
Let's assume we've got a C program called foo that processes
files and would like to know whether it is dealing with a text file or not. Determining this is simple
using the getMimeType function above: just fetch the MIME string and then see whether it starts
with "text/". Here's an implementation:
bool
isTextFile (char *fname)
{
// gets the file's MIME type string and returns whether or not
// the super type is "text" (e.g. "text/plain", "text/html", etc.)
char mbuf[256];
char *p;
getMimeType (fname, mbuf);
p = strchr (mbuf, '/');
if (p)
*p = 0;
return (strcmp (mbuf, "text") == 0);
}
Now, to compile this...
Assume (for simplicity) that foo.c is the only C source file.
Then we can compile the program like this:
gcc foo.c mime.cpp -lbe
The command line option -lbe tells the linker to search the standard BeOS library file
'libbe.so' for needed functions. BEntry , BNode , etc. are located in this library file. If you
needed network library functions from 'libnet.so', as an example, you would use the option -lnet .
And so on.
To summarize, all that is needed to make C++ calls from C is this:
- Segregrate all C++ functions into separate ".cpp" files
- Wrap the function definitions with
extern "C" to preserve the function's original symbolic name (so that the linker can bind it with C programs)
- Add the additional ".cpp" files to the command line
- Include the necessary C++ libs on the command line (use the
-lxxx option for the BeOS 'libxxx.so' libraries)
Gcc will take care of the rest.
With this knowledge in hand, the C programmer can now view the BeOS API in a whole new light. Rather than seeing it as
a playground for others, he can now view it as one big, wondrous candy store packed with all kinds of goodies just waiting to be sampled.
|
|
|
With this article I hope to explain the basics of scripting using BeOS and how it can enhance the OpenBeOS project. To try out several examples mentioned in this article, you'll have to download a program called hey , which enables you to script BeOS applications from a Terminal.
The program hey was developed by Atilla Mezei, an early BeOS developer, who unfortunately seems to have left the BeOS community. He created this tool to have a good testing tool when implementing scripting in BeOS applications.
As an example, I'm going to use my pet project, the print kit for OpenBeOS. Or to be more specific, the print_server. The print_server presents most of the functionality of the printing architecture of BeOS, and is used by both the BPrintJob class and the Printers preference application.
The requests to the print_server range from selecting a printer to putting up a page setup dialog for a specific printer. Also removing or adding a printer is actually done by the print_server. Here's an example of using hey with a scriptable application:
$ hey print_server DO PageSetup OF Printer "MyPSPrinter"
This will make the print_server put up the page setup dialog for the printer named "MyPSPrinter", and return the settings made in the reply BMessage object. Another option is:
$ hey print_server CREATE Printer WITH name = "DummyPrinter" AND driver="Postscript" AND Transport="Serial Port" AND TransportAddress="ser1"
Which will create a printer with the name "DummyPrinter" using the Postscript printer driver and using the 1st serial port as output port.
To see what other commands the print_server will respond to, tell it to show its vocabulary:
$ hey print_server GETSUITES
Also, the classes from the Application and Interface kit both contain lots of scripting support. This is all described in the BeBook, as all these classes have a Scripting Support table explaining what you can script with them. Just for a cool example, try this:
$ hey Tracker GET Frame OF Window 1
You'll see returned a BRect() description that looks like the resolution of your current workspace. If you do this:
$ hey Tracker SET Frame OF Window 1 TO BRect[0,0,200,200]
The window of the Tracker will be resized to a really small one. If you now drag your Terminal around, you'll see that the largest part of your screen will not get updated anymore, as the window is smaller, and outside that window there is nothing :)
Another useful one is this:
$ hey Terminal SET Title OF Window 0 TO "Ithamar's teaching me Scripting :)"
This is mostly useful if you're opening a lot of Terminals and want to quickly see which is which in the window list from the Deskbar. Hope this shows a few simple neat tricks with BeOS Scripting :)
Implementing scripting in the print_server
How does this work internally? Well, I'll explain to you how I did it within the print_server. Most of this code is in the current CVS tree for OpenBeOS, so you should be able to view it from the web or download it using CVS tools on your favorite platform.
Please note that I'm assuming, for the sake of space saving here, that you've read the Application Kit's introduction to messaging, and have had some experience with messaging on the BeOS (that means, you've written a GUI app for BeOS, that had a clickable something, like menus, or buttons, or such).
First thing to do is to make the application object scriptable. As described in the BeBook (see links on the bottom of this article), this involves basicly a few simple overrides. First of all, we'll add a method called GetSupportedSuites() to the application class, which looks like this:
status_t
PrintServer::GetSupportedSuites(BMessage *message)
{
message->AddString("suites", B_PSRV_PRINTSERVER_SUITE);
BPropertyInfo prop_info(prop_list);
message->AddFlat("messages", &prop_info);
return Inherited::GetSupportedSuites(message);
}
This method is called by the BHandler class as soon as any app sends the B_GET_SUPPORTED_SUITES message to it.
As you can see, it adds a string "suites" to the message passed, that should look like "suites/vnd.VENDOR-NAME" where VENDOR and NAME are chosen by you. Next, it adds a flattened object of type BPropertyInfo under the name "messages", which are the actual scripting commands which are defined under the above mentioned scripting suite. The format of this table we will see later on in this article.
Next thing is that the application object will have to know how to resolve the scripting specifiers, like "OF Printer 1", which is handled by a method called ResolveSpecifier. See an example of such a method below:
BHandler*
PrintServer::ResolveSpecifier(BMessage *message, int32 index,
BMessage *specifier, int32 what,
const char *property)
{
BPropertyInfo prop_info(prop_list);
Printer* printer;
switch(prop_info.FindMatch(message, 0, specifier, what, property)) {
case B_ERROR: // Not found!
break;
case 0: // Return Printer handler
<snip>
break;
case 1: // Create new Printer and return it
<snip>
break;
default:
return this;
}
return Inherited::ResolveSpecifier(message, index, specifier, what, property);
}
I've snipped a few pieces of code to keep this listing short -- look at the CVS tree for details on the snipped code. Basically, it creates an object of type BPropertyInfo again, using the scripting suite description table, and asks the object to find a match with what it gets in the arguments. It then returns an index in the scripting suite description table of the match found, or B_ERROR if no match was found. The arguments are as follows:
message: Complete scripting message (message->what will be one of B_GET_PROPERTY, B_SET_PROPERTY, etc)
specifier: The current specifier (Printer 0, PageSetup, etc.)
what: short for message->what
property: The name of the property referenced in the current specifier (Printer, PageSetup, etc.)
What this method should do, is simply return a BHandler to the object that the specifier resolves to. For the print_server, I create a BHandler derived object for every printer definition on the system. When I get a "OF Printer x" in here, I lookup the printer definition object, pop the specifier (BMessage::PopSpecifier() ), and return the printer definition object (a BHandler derived object). This will let the specific printer def object handle the next specifier.
If you've found a specifier which is an end-point, so to speak, like the PageSetup specifier in one of my examples above, you can just return this and the specifier will be sent to the MessageReceived() function, so you can handle it there. If you cannot recognize the message, call the inherited version of ResolveSpecifier() , and the classes will see if they can "get" it, and if not, return a "not understood" message to the scripting originator.
This is basically all :) You repeat this code for every object that needs to understand scripting (this example was done with the PrintServer application, but also my Printer object can understand printing of course!) and you're set.
Hope this clarifies it a bit -- for more indepth explanations, only trust the original authors :) :
The Be Book: Application Kit: Scripting
Be Developer Library: Scripting
|
|
|
|
|
|
|