People seldom realize that learning the lay of the land can be a craggy thing to do (especially if chuckholes abound). However, we have learnt it the hard (um...quite) way and the last thing we intend doing is make you pass through the same brain-wrenching times. If you feel the velocity with which we go about explaining is on level with your average banana slug (that's ssslllooooowwww...), we apologize, but we hardly have a choice.
Now if you load Netscape and try to view the file, it will give you an error called "Illegal Operation performed". That is because we don't have any file in our directory called "john.zzz". So we go right out and create the file john.zzz. We put the following text in it...a1.html <embed src = "john.zzz" width = 200 height = 300>
Since you are in the middle of a tutorial, we'd rather you also have the same text. It will help you understand the things better later on. When we load the HTML file a1.html now, Netscape still refuses to do anything more about it than displaying a half-rendered image (or icon, as we spent half an hour arguing). That's for the simple reason that we haven't yet introduced Netscape to a file with an extension of .zzz .ABCDEFGHIJKLMN
The process of introductions is almost invariably complicated and the one in question is no exception. Towards this end, select the menu item "options" from the Netscape menu and click on "General Preferences". A tabbed dialog will suggest itself; select a tab called "Helpers" from here. Then click on the button "Create New Type". In the set of edit box twins that appears, type in the following text ...(and drop the whys and whats for now)
A word of caution : when you are keying the above text in, take care of the case, as it is sadly case-sensitive. In the edit box that says "File Extensions", key in our file extension, i.e., zzz. Even after all this pain in the er... back, our browser refuses to understand john.zzz. And it is about time to say, "Welcome to the world of plug-ins".Mime Type : jack Mime Sub Type : jill
Contrary to the highfalutin way it sounds, it is nothing more than a program that tells your Netscape Navigator about an alien extension. It is an auxiliary feature that allows the browser to identify all kinds of files (it does not matter if your cousin got one on his last trip to the Andromeda galaxy :) All we need to do to achieve this is to create a dll , that resides once and for all with Netscape, unless you don't want it that way. Whenever Netscape comes across a file with that particular extension again, it deals with it in the way specified by our dll file. This dll thing might get a trifle heavy, so let us simplify it a bit.
Assuming I am Mr. Netscape and you are Mr. DLL. Every time I wake up, that is, every time I am being loaded, I will ask you for the various types of file extensions you have in your pocket. I will take all of them, keep them in mine and remember them everafter unless you get some new ones in your pocket. Now whenever I come across a file with the surname zzz, I will look in my pockets for a chit that says zzz. If I find it, I will treat the file in the way specified in the chit, or else I will display a truncated icon telling the user that I do not recognize the file.
This is where Visual C++ figures. Creating a dll file is not really a Herculean task to do (even if you have the faintest idea what a dll is all about). Especially if you follow these simple steps obediently...
oops.cpp
oops.rc
readme.txt
stdafx.cpp
From these, delete all the files except oops.cpp and oops.rc. We will delete the contents of these two later and put our own code.
Well, that's about the size of the requisite settings. So lets click on OK. Now lets get cracking, full throttle.
The project oops.mak was made for us by the MFC AppWizard with a number of files. Still, we will have to give the program a major facelift to cleanse out all the trash code. So we suggest you type the text in oops.cpp all over again. This might give you the impression that if we had to write the code anyway and delete all the rest of the trash, why in blistering blazes did we ask the Wizard to serve up the code. But as we mentioned earlier, we played it safe and saw the settings of the program provided by the Netscape folks. Besides, the code is not all that unaffordably untypable. If you are ready to hit the clutch, here's the first gear...
If that's not an inspiring program, wonder what is! That sort of code must be second nature to you, yet let us revise a little of C/C++ again. If you already understand it well, please bear with us. The afxwin.h is a header file given to us by Microsoft Visual C++. With this header file, we get #defines, function prototypes, structures tags, typedefs and above all, class prototypes. The remaining two, npapi.h and npupp.h are the headers given to us by Netscape. As these two header files do not have any class prototypes, we can safely consider them to be more towards CSDK rather than C++. It will be a good idea to copy these two into the subdirectory oops. We have our set of superstitions, so we prefer slipping into the DOS mode and copying these files into the oops subdirectory. In normal weather, these files can be found in the subdirectory called "avi" of your plugin SDK.oops.cpp #include <afxwin.h> #include "npapi.h" #include "npupp.h" short _stdcall NP_GetEntryPoints(NPPluginFuncs *p) { MessageBox(0, "in get entry", "Hi", 0); return 0; }
The _stdcall is a calling convention, which is nothing but the order in which the parameters of the function will be placed on the stack. The only good thing about calling conventions is that you don't have to know anything about them.
The CPP and the RC make strange bedfellows. Nevertheless, we have no choice but to have a decent RC file as well. Let us have the following code in oops.rc...
The afxres.h is the indispensable header in an RC file. You will be exhibiting lack of discernment if you ask why the "versioninfo" is required. The goal of computer science today is to build something that will last atleast until we have finished building it. With newer versions being released by the hour, it is just fine to have version info in the RC file. The Block with the value "040904e4" signifies two things. One is the language used (English in our case) and the second refers to the character code page (or the character set) we are using. For instance, the ASCII 7-bit character set (which will allow you to have an unarguable maximum of 128 characters), UNICODE, Windows Greek etc. The problem with ASCII is that if our language has more than the number of characters that it provides, what are we supposed to do? For that purpose, we use something called 'Windows, Multilingual' which permits a flexible set of code (and that's good news for the Chinese because they seem to have an endless list of the alphabet :) ). As regards the strings MIMEType and FileExtents , Netscape will use them to figure out what are the mime types and the file extensions that our dll supports. That is how it will decide whether our file john.zzz has to be deciphered or not.oops.rc #include <afxres.h> VS_VERSION_INFO VERSIONINFO BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "FileExtents", "zzz\0" VALUE "MIMEType", "jack/jill\0" END END END
Before explaining the rest, let us compile our program. After compilation, a dll file called oops.dll will be made in the subdirectory windebug of oops . Let us find out for ourselves what the dll will do for us.
Thanks to some technological quirk, Netscape looks into a subdirectory called "plugins" for the dll file. Therefore, let us put our oops.dll there. In the Netscape software you have downloaded, you will have a subdirectory called "program". Here create a subdirectory called plugins if it is not already there. Besides, Netscape refuses to work fine unless the alphabets np are not prefixed with the name of the dll . As regards us, well we prefer doing it the old way, i.e., we slip into the DOS mode and rename the dll file into npoops.dll . What's more, it is important to load Netscape afresh everytime you make any changes to the programs because everytime your friendly browser is loaded, it puts the latest dll into its pockets. A friendly tip here...since we are going to have to do this every time we compile, let us make a batch file for the purpose. In our case, for example, the batch file looks like this..a.bat
copy d:\oops\windebug\oops.dll d:\netscape\program\plugins\npoops.dll
If, by some quirk of accident or purposeful sleuthing, you come across any other novel way of executing the dll file, please do not hesitate to let us know about it.
Get back to Netscape and try to load the a1.html again. But Netscape still does not seem to like it. Lets see why.
If you have noticed, the function in our cpp file starts with NP, which implies that it is a Netscape Plugin Function. The one glip here is that every time we use a Netscape Function in a C++ program, we have to explicitly tell it that the function can be used by outsiders/foreigners as well; or in other words, we have to export it. In our case, this outsider is Netscape. This can be done by exporting the function through the .def file.
Before actually seeing how we can do that, let us stop here and get a few things cleared out. When a project is created, it usually comprises of various files like the CPP, RC etc. One noteworthy issue is -- who is going to read these files? Like a header file will always be read by the preprocessor, a cpp file by the compiler and .def file is to be read only by the linker. A compiler does not know what a .def file is. A linker has the capability to create an exe file for almost any operating system, be it DOS, Windows, UNIX etc. It can also create a dll file. For you and me as programmers, an exe file and a dll are more or less the same. But a dll file, or the Dynamic Link Library, is a file that contains a set of functions except the crucial WinMain() . That is the reason why we do not execute it. However, it remains an intriguing mystery how a linker understands that it is supposed to create a dll and not an exe ? This is where a .def comes to the rescue. Let us see the contents of our def file called oops.def. Unless your stars are upset with you, you will find the file. After a little manicuring, it looks like this.
A def file contains information required by the linker. If the first line here starts with the word "Library", the linker infers that a dll file has to be created. The word following "Library" will be the name given to our dll . For instance, in the file you just opened, the word is oops . One glitch, though, is that the functions inside the dll cannot be called by other programs. So if Netscape wants to call the functions inside this dll it is not allowed to, unless we explicitly lists the function names under EXPORTS . The word EXPORTS tells the linker to allow outsiders like Netscape to have access to a function. We want Netscape to call our function NP_GetEntryPoints , so we export it.oops.def LIBRARY OOPS EXPORTSCompile the project now. You are extremely unfortunate if it doesn't successfully compile after all that clump of efforts. Our dll file is ready now and as expected, has been christened as oops.dll . Keeping the glop in mind, let us switch over to the DOS mode and copy oops.dll as npoops.dll into the plugins subdirectory.oops.def LIBRARY OOPS EXPORTS NP_GetEntryPointsNow if you load the a1.html in Netscape, you will see a small Message box that says "in get entry". Voila! we have our first plugin working, however unavailing it might be!
From a fairly justified perspective, its about time we got to know what the function NP_GetEntryPoints is all about. This function gets called first, every time you try to load a plugin. Note the parameter passed with it. It is a pointer p to NPPluginFuncs which undoubtedly is a structure (or a function table, as it is ostentatiously called). This structure consists primarily of pointers to various functions, as will be clearer with subsequent examples.
With that wisdom behind us, lets accelerate to the second gear. To avoid confusion, we will give you the entire code with the new features of every program appearing in bold. Consider the following code...
Bet you forgot that all the functions beginning with NP (i.e., the Netscape Plugin functions) have to be exported. In oops.def , we will have to add this function as well, just below the NP_GetEntryPoints so that our oops.def looks like this..#include <afxwin.h> #include "npapi.h" #include "npupp.h" short _stdcall NP_Initialize(NPNetscapeFuncs *p) { MessageBox(0, "in initialize", "Hi", 0); return 0; } short _stdcall NP_GetEntryPoints(NPPluginFuncs *p) { MessageBox(0, "in get entry", "Hi", 0); return 0; }Now compile the program and copy the dll as required. When you load the file a1.html , you will see two message boxes, one that shows "in get entry" and the other "in initialize". This means that the functions are being successfully called by Netscape and as they have been exported , it does not have any problem calling them. Note that the NP_GetEntryPoints gets called first. This will be clear soon.oops.def LIBRARY OOPS EXPORTS NP_GetEntryPoints NP_InitializeFor now, let us try this out...
Before opening up the bonnet of the function and peering into the cogs, let us get a few facts clear. Remember the callback function in CSDK? It is one of the limitations of Windows. We give Windows the address of a function. This function gets called each time Windows wanted to notify us about an event, say, a mouse click, a menu option being selected etc. This function has a mandatory four parameters which are not really relevant for all the events. The problem there is that every event that took place, whether a mouse click, QueryOpen, a menu etc, the same function got called. Do you realize that for a mouse click, the last parameter is not a long, it is two ints. That for a QueryOpen, none of the parameters never really matter, except for the message id. For the menu, on the other hand, we are bothered only with the menu number. Having one sole function to handle all the events is not really a wise idea. What does one do if a certain function wants to send (or receive) 22 bits of information? The four will fail to suffice. It implies that when the callback function is called, all we can surely say is that it has been called because Windows wants to deliver an event to us. What these events are, Windows decides.#include <afxwin.h> #include "npapi.h" #include "npupp.h" short _stdcall NP_Initialize(NPNetscapeFuncs *p) { MessageBox(0, "in initialize", "Hi", 0); return 0; } short aa(char * p, NPP i, unsigned short m, short g, char * n[], char * v[], NPSavedData *s) { MessageBox(0, "in aa", "Hi", 0); return 0; } short _stdcall NP_GetEntryPoints(NPPluginFuncs *p) { MessageBox(0, "in get entry", "Hi", 0); p->newp = aa; return 0; }In a similar but slightly better vein, Netscape also wants to inform us about the events that take place. Again, the exact nature of the events is to be decided by Netscape, not by us. Wouldn't it make more sense if we gave Netscape the address of the functions it must call for each event and Netscape calling the right function at the right time? One way to do this is with the return statement. Which is a crippling handicap at this point because when we use the return statement, we cannot give more than one value. The only handy solution, therefore, is using a structure. This structure could have a set of the requisite functions or better still, pointers to these functions.
That is the purpose of the structure NPPluginFuncs . Whenever a1.html is loaded, Netscape calls the function NP_GetEntryPoints . Before doing so, however, it creates a structure that looks like NPPluginFuncs and puts its address on the stack. It is a structure tag in npupp.h header file that comes from Netscape. A structure, as we know by heart, can have character variables, ints, longs, pointers or pointers to functions. Likewise, NPPluginFuncs comprises of many members, including pointers to the functions to be called for the various events. One of these pointers is newp . To access newp , or any other member NPPluginFuncs for that matter, we pick up the address on the stack and store it in our pointer p . Now when NP_GetEntryPoints wakes up, it sees the pointer p . Through p , we initialize newp to aa . aa happens to be the name of our own function. Note that it is vital to have this function defined before the NP_GetEntryPoints lest it should give compilation errors.
All the cumbersome procedure of compilation etc. carried out, our program will display three message boxes. In addition to the usual two we also see the Message box "in aa". At this point you will also see a small white window with width 200 and height 300. In case you forgot, those were the values we specified in our HTML file (in the embed statement). Now let us refashion the code a bit. Consider this...
If you see the above code work, besides the three message boxes you will now get one called "in bb" and this one is shown more than once. The first time the message box "in bb" appears, "0..." is displayed inside it . After this, a new value gets displayed (and the same value is displayed hereafter, everytime this message box appears). Which indicates that newp gets called only once, while the setwindow gets called constantly. Through our pointer p , we access the member setwindow of the structure NPPluginFuncs . This member we initialize to the function bb . We are calling the bb () with two parameters. An object i that looks like the structure NPP and a pointer to NPWindow w . Having seen that work, let us understand the GetEntryPoints() a little more clearly. That indicates that Netscape does not create a window the first time around. The only way it can create a window is by using CreateWindow (). This returns a handle that represents the window just now created. The number that you see inside the message box is the number Microsoft Windows provides to identify the window Netscape created. Now every time we do something with the window, e.g., resize it , or move the scroll bar, this function bb () gets called. That shows that the setwindow member is activated every time the window is redrawn. Towards the noble cause of self-enlightenment, we display the value of w->window . Since Windows programming has no avenues for direct statements like printf , we have an array a in which we place the values of w->window . An array by itself is about as useful as a nibless pen so we display it in a message box.#include <afxwin.h> #include "npapi.h" #include "npupp.h" char a[100]; short _stdcall NP_Initialize(NPNetscapeFuncs *p) { MessageBox(0, "in initialize", "Hi", 0); return 0; } short aa(char * p, NPP i, unsigned short m, short g, char * n[], char * v[], NPSavedData *s) { MessageBox(0, "in aa", "Hi", 0); return 0; } short bb(NPP i, NPWindow *w) { sprintf(a, "%d...", w->window); MessageBox(0, a, "in bb", 0); return 0; } short _stdcall NP_GetEntryPoints(NPPluginFuncs *p) { MessageBox(0, "in get entry", "Hi", 0); p->newp = aa; p->setwindow = bb; return 0; }When you run the above program, four message boxes will appear in a row. Try to tinker with the window a little and see for yourself how the bb () is called everytime.
It is nearly impractical to work with C++ and MFC without having classes. With the following program, you might get the nostalgic flavor of hard core C++, the usual classes and all...
We have a class yyy that is derived from CWnd . In the constructor of yyy , we have a message box that displays "in yyy" and a function called OnLButtonDown (which gets activated whenever the mouse is clicked, though that's needless to explain) where we have another message box. The load of MESSAGE_MAPs etc. must not be new fry. Then we declare a pointer b that looks like yyy . Note the bb (). The crummy message box has been replaced with a little more challenging stuff. Most of you must be aware of a SubclassWindow . It is a member of the CWnd class. It says "Give me a window and I will see to it that all the events will be passed to it." Which means all the actions that were taking place in the window of our browser till now will now be inside the the w->window because we are specifying this window as the sub classed window. The only snag is that subclassing a window once is enough. For this purpose, we have the if statement. As the specified condition says it all, the if statement will be executed when b is equal to 0 and w->window is non-zero. Since b is a global variable, its value will be 0 for the first time bb () gets called. Likewise, w->window will have the same saga. But the second time the function is called, w->window gets some other value. Thus the if statement is executed. The next time around b will not be equal to zero. Did you notice that we don't have any WinMain() or CWinapp in our program? Unfortunately, we get an "Assertion Failure" if we do not have an object that looks like CWinapp . So lets add the following statement after yyy *b ...#include <afxwin.h> #include "npapi.h" #include "npupp.h" char a[100]; class yyy : public CWnd { public: yyy() { MessageBox("in yyy"); } void OnLButtonDown(UINT, CPoint p) { MessageBox("in lbuttondown"); } DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(yyy, CWnd) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() yyy *b; short _stdcall NP_Initialize(NPNetscapeFuncs *p) { MessageBox(0, "in initialize", "Hi", 0); return 0; } short aa(void * p, NPP i, unsigned short m, short argc, char * argn[], char * argv[], NPSavedData *s) { MessageBox(0, "in aa", "Hi", 0); return 0; } short bb(NPP i, NPWindow *w) { if (b==0 && w->window) { b = new yyy; b->SubclassWindow((HWND)w->window); } return 0; } short _stdcall NP_GetEntryPoints(NPPluginFuncs *p) { p->newp = aa; p->setwindow = bb; MessageBox(0, "in get entry", "Hi", 0); return 0; }CWinApp c;The program will work just fine. Everytime you click with the mouse inside the small plugin window, you will see the message box that says "in lbuttondown". After all that let us introduce CClientDC instead of MessageBox. Our plugin will display "Hello" everytime you click in the small window, at the position where you have clicked. Retain all the code and in the OnLButtonDown() , put the following code...void OnLButtonDown(UINT, CPoint p) { CClientDC d(this); d.SetTextColor(RGB(255, 0, 0)); d.TextOut(p.x, p.y, "Hello"); }Fine. A full-fledged plug-in might be a little more complex and involved than that, but remember it was the underlying rudiments that mattered more. The next topic revs up the meat in a little more intricate detail.
The names that got lost
It took us a mouthful of gall to confront the alleged 'samples'. After scraping the innards of Netscape and yanking some meaning out of the hairy plug-in samples, we gave their spectacularly unsanitary code a thorough scrubbing. In the process, some grime (or rather some function names) got laundered. If you are not inordinately fond of jargon, this section is not for you. Nevertheless, here are some functions names as they liked to call them and as we fancy calling them...
The names that got lost Their names Our names NPP_New aa() NPP_SetWindow bb() Unless you are running out of fuel, swerve to the next topic "Maximum overdrive" or just pull up and leave your comments to fuel us.
Back to Java page
Vijay Mukhi's Computer Institute
B-13, Everest Building, Tardeo, Bombay 400 034, India.
http://www.vijaymukhi.com
e-mail : vmukhi@giasbm01.vsnl.net.in