Despite its name, the "Simple Plug-In" that Netscape supplies with the Plug-in Software Development Kit (SDK) is far from simple. It includes a full implementation of the major methods, including LiveConnect. LiveConnect (described in more detail in Part V) is Netscape's integration technology that allows your plug-in to communicate with Java and JavaScript.
To get an idea of what Simple does, start by compiling the plug-in and running it. This chapter uses the Windows version of the plug-in, but Netscape supplies a version for the Mac as well.
Netscape uses Microsoft's Visual C++ on Windows and MetroWerks' Code Warrior on the Macintosh. You can simplify the installation process if you use the same environment they do.
In Visual C++'s Developer Studio, choose File, Open Workspace and open Simple32.mdp in the Examples\Simple\Windows subdirectory of the Plug-ins SDK directory. After the project opens, choose Build, Build NPSimp32.dll. The project should compile and link with no errors or warnings.
Copy the Java class file Simple.class to the Navigator plug-ins
directory.
Tip |
You may want to skim Chapter 7, "Overview of Java," and Part V (Chapters 14-16), "LiveConnect," for a bigger picture of Java and LiveConnect. In Part V, you learn how to use javac and javah to make your own class, header, and stub files. |
Copy the NPSimp32.dll file, which you just built, to the plug-ins directory and restart Navigator. Make sure that you're using a version of Navigator that is at least v3.0b5, or you won't see all the features of this plug-in working.
When Navigator restarts, it examines the files in the plug-ins directory and registers plug-ins based on their MIME media type. Chapter 11, "Step-by-Step Through the NPP Methods,"describes this process in more detail.
Once Navigator is up, use File, Open File to open You'll probably want to bookmark this page while you learn about plug-ins.
Choose Options, Show _Java Console and make sure that this item is checked. The Java Console is a small window to which your Java classes can send messages. When it opens, you'll see that your Simple plug-in has already been busy. If you have enough room on-screen, try to position your windows so that you can see both the Java Console and the plug-in's part of the Navigator window. Figure 13.1 shows the SimpleExample.html and the Java Console.
Figure 13.1: Simple writes to both the Java Console and the Navigator window.
Notice that Simple wrote "Hello, World!" to its Navigator window. To see that Simple is listening to JavaScript as well, click the "Click Me" button a few times while watching the Java Console.
Although you may not want to ship Simple as a finished product, Simple is rich enough to have hooks for many of the actions that you may perform with a more sophisticated plug-in.
As you saw when you ran Simple, a plug-in has two interfaces-one to Navigator and another to the user. Because the plug-in cannot do anything with the user until most of its Navigator functions are called, you start by tracing the flow of control between Navigator and Simple. Follow along with Figure 13.2.
Figure 13.2: Navigator makes a series of calls to the plug-in to get things started.
How Simple Is Called Return to Microsoft Developer Studio and use it to open SimpleExample.html. This file isn't part of the project-you need to use File, Open to open it. The plug-in is loaded because of the following line:
<EMBED type=application/x-simple-plugin name=simple1 width=400 height=300>
This line tells us that Simple doesn't read a stream from the Internet. The TYPE attribute forces Navigator to load this plug-in by specifying the MIME media type. Notice also that the plug-in has a name-Simple1. This name gets used by JavaScript later, in the section that reads as follows:
<form> <input type=button value="Click Me" onclick='document.simple1.doit("Hello from JavaScript!")'> </form>
NPP_GetJavaClass() Chapter 11, "Step-by-Step Through the NPP Methods," didn't mention NPP_GetJavaClass(). If your plug-in isn't using LiveConnect, you don't need to do much with it-just return NULL.
If you want to support LiveConnect, you may want to include an NPP_GetJavaClass() function similar to the one in Simple:
/* ** NPP_GetJavaClass is called during initialization to ask your plugin ** what its associated Java class is. If you don't have one, just return ** NULL. Otherwise, use the javah-generated "use_" function to both ** initialize your class and return it. If you can't find your class, an ** error will be signaled by "use_" and will cause the Navigator to ** complain to the user. */ jref NPP_GetJavaClass(void) { #ifdef __MC68K__ return NULL; #else struct java_lang_Class* myClass; env = NPN_GetJavaEnv(); if (env == NULL) return NULL; /* Java disabled */ myClass = use_Simple(env); if (myClass == NULL) { /* ** If our class doesn't exist (the user hasn't installed it) then ** don't allow any of the Java stuff to happen. */ env = NULL; } return myClass; #endif }
Note |
If you're writing a plug-in for the Macintosh, be sure to include these lines: #ifdef __MC68K__ Netscape doesn't support LiveConnect on the 680X0 Macintosh, but they do support it on the Power Macintosh. |
Part V goes into more depth on LiveConnect. This chapter shows why Navigator is calling this function, and why you might want a Java class. For now, just note that NPP_GetJavaClass() calls NPN_GetJavaEnv(), to put a pointer to the Java runtime environment into env. If this step succeeds, the plug-in calls a function named use_Simple(). use_Simple() is a function written by an automated tool (javah) based on our Java class, Simple. use_Simple() performs certain initializations and connections in the Java Runtime Interface. As a plug-in programmer, you don't have to be familiar with too many Java details yet. Figure 13.3 illustrates the flow of control between the Java runtime and the plug-in during NPP_GetJavaClass().
NPP_New() Now Navigator is ready to make the plug-in instance. Open NPP_New() with ClassWizard. Note the following line:
instance->pdata = NPN_MemAlloc(sizeof(PluginInstance));
This line is a "heads-up" that the designer chose to associate a PluginInstance with the instance's pdata. Recall that pdata points to a block of Navigator memory that will be passed back to your plug-in on every NPP call. Inside the function, the programmer uses This to point to this instance.
Simple and most other Netscape sample plug-ins use the structure PluginInstance to hold the data passed through pdata. Netscape defines PluginInstance in npsimple.c:
typedef struct _PluginInstance { NPWindow* fWindow; uint16 fMode; /* Windows data members */ #ifdef _WINDOWS HWND fhWnd; WNDPROC fDefaultWindowProc; #endif /* _WINDOWS */ /* UNIX data members */ #ifdef XP_UNIX Window window; Display *display; uint32 x, y; uint32 width, height; #endif /* XP_UNIX */ } PluginInstance;
Although Simple doesn't store anything special in PluginInstance, this structure is custom in each plug-in because most plug-ins will store instance-specific data in the structure.
Here is Simple's version of NPP_New():
/* ** NPP_New is called when your plugin is instantiated (i.e. when an EMBED ** tag appears on a page). */ NPError NP_LOADDS NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved) { NPError result = NPERR_NO_ERROR; PluginInstance* This; char factString[60]; if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; instance->pdata = NPN_MemAlloc(sizeof(PluginInstance)); This = (PluginInstance*) instance->pdata; if (This == NULL) return NPERR_OUT_OF_MEMORY_ERROR; { /* mode is NP_EMBED, NP_FULL, or NP_BACKGROUND (see npapi.h) */ This->fWindow = NULL; This->fMode = mode; #ifdef XP_UNIX This->window = (Window) 0; #endif /* XP_UNIX */ #ifdef _WINDOWS This->fhWnd = NULL; This->fDefaultWindowProc = NULL; #endif /* _WIDOWS */ /* PLUGIN DEVELOPERS: * Initialize fields of your plugin * instance data here. If the NPSavedData is non- * NULL, you can use that data (returned by you from * NPP_Destroy to set up the new plugin instance). */ } if (env) { jint v; /* ** Call the DisplayJavaMessage utility function to cause Java to ** write to the console and to stdout: */ DisplayJavaMessage(instance, "Hello world from npsimple!", -1); /* ** Also test out that fancy factorial method. It's a static ** method, so we'll need to use the class object in order to call ** it: */ v = Simple_fact(env, class_Simple(env), 10); sprintf(factString, "my favorite function returned %d\n", v); DisplayJavaMessage( instance, factString, -1 ); } return result; }
Once you've defined PluginInstance for your plug-in, the following three lines provide a formula to open nearly any plug-in:
instance->pdata = NPN_MemAlloc(sizeof(PluginInstance)); This = (PluginInstance*) instance->pdata; if (This == NULL) return NPERR_OUT_OF_MEMORY_ERROR;
In the following line from NPP_New(), env is a global pointer to a JRIEnv:
if (env)
If all went well in NPP_GetJavaClass(), env points to the Java runtime environment. To show that all this works, the Simple plug-in makes a series of gratuitous calls to Java, writing its results to the Java Console. You'll see these calls to the Java Console scattered around the plug-in, in much the same way as you used calls to MessageBox() in Chapter 11, "Step-By-Step Through the NPP Methods."
Note that if the Java runtime is successfully set up, the plug-in goes on to call a Java method on class Simple (Simple_fact()). The return value of that function is a Java int-a jint. Chapter 14, "Principles of LiveConnect," shows an entire table of these C/C++ names for Java native types.
DisplayJavaMessage() DisplayJavaMessage() is one of the larger and more interesting functions in this plug-in. Its parameters include a char pointer and an int (for length).
/* ** This function is a utility routine that calls back into Java to print ** messages to the Java Console and to stdout (via the native method, ** native_Simple_printToStdout, defined below). Sure, it's not a very ** interesting use of Java, but it gets the point across. */ void DisplayJavaMessage(NPP instance, char* msg, int len) { jref str, javaPeer; if (!env) { /* Java failed to initialize, so do nothing. */ return; } if (len == -1) len = strlen(msg); /* ** Use the JRI (see jri.h) to create a Java string from the input ** message: */ str = JRI_NewStringUTF(env, msg, len); /* ** Use the NPN_GetJavaPeer operation to get the Java instance that ** corresponds to our plug-in (an instance of the Simple class): */ javaPeer = NPN_GetJavaPeer(instance); /* ** Finally, call our plug-in's big "feature" - the 'doit' method, ** passing the execution environment, the object, and the java ** string: */ Simple_doit(env, javaPeer, str); }
The function calls the Java Runtime Interface (JRI) to allocate a string in Java:
jref str; str = JRI_NewStringUTF(env, msg, len);
Then the function calls NPN_GetJavaPeer() to get a Java reference (jref) to its own peer object. Because the peer is known to be an instance of Java class Simple, the plug-in calls the C stub of its peer class and runs Simple_doit(). In following sections of this chapter, you read about Java class Simple and its doit() method.
Figure 13.4 traces the flow of control between the plug-in and the Java runtime.
Figure 13.4: Netscape illustrates how the plug-in calls Java, with DisplayJavaMessage( ).
NPP_SetWindow() Because there's no stream associated with this plug-in, you don't have to worry about NPP_NewStream(), NPP_WriteReady(), or NPP_Write(). These functions are available in this plug-in, but they do nothing useful.
The next call the plug-in expects is NPP_SetWindow(). Find the place in NPP_SetWindow() that reads as follows:
This->fDefaultWindowProc = (WNDPROC)SetWindowLong( (HWND)window->window, GWI_WNDPROC, (LONG)PluginWindowProc); This->fhWnd = (HWND) window->window; SetProp( This->fhWnd, gInstanceLookupString, (HANDLE)This); InvalidateRect( This->fhWnd, NULL, TRUE ); UpdateWindow( This->fhWnd );
This code does the work of saving off the old WindowProc (into This->fDefaultWindowProc) and installing the new PluginWindowProc. Now that PluginWindowProc is installed as the WindowProc, any messages sent to our window by Windows are passed to your plug-in (and directed to PluginWindowProc). Figure 13.5 shows how subclassed windows work.
SetProp() is a Windows API call that adds a new entry to the property list of the window. This call puts a link in your Navigator-supplied hwnd back to your pdata. Recall that there can be more than one copy of our plug-in running at a time. By tying your instance data to the window, you can always find your own windowProc and your own data.
PluginWindowProc() Lower down in the same file (npsimple.c) is PluginWindowProc(), which is the callback function the programmer defined to handle messages to our window. In this simple plug-in, the only message handled in PluginWindowProc() is WM_PAINT. All other messages are routed back to the default windowProc.
The WM_PAINT handler consists of just five lines, as follows:
PAINTSTRUCT paintStruct; HDC hdc; hdc = BeginPaint( hWnd, &paintStruct ); TextOut(hdc, 0, 0, "Hello, World!", 15); EndPaint( hWnd, &paintStruct);
When a WM_PAINT message comes
to this plug-in, it sets up a device context (so it has someplace
to draw), draws the string "Hello,
World!", deallocates the device context, and
exits.
Note |
Did you spot the defect in the TextOut the programmer passes in the string "Hello, World!", which has 13 characters, not counting the terminating NULL? The Programmer has told Windows to display 15 characters. The last two characters are garbage characters. (cbString, the last parameter in TextOut, should be the number of printable characters, and doesn't need to take the trailing NULL into account.) Fix the bug while you're here-change the 15 to a 13 in this line. |
NPP_Print() What will happen if the user selects Print? Because the plug-in is embedded, NPP_Print() gets called only once-when printInfo->mode is NP_EMBED. In this case the plug-in runs the following and returns:
NPWindow* printWindow = &(printInfo->print.embedPrint.window); void* platformPrint = printInfo->print.embedPrint.platformPrint;
These two lines retrieve the window into which the plug-in is to draw, and the device context of the printer, but they don't do anything with them. If you'd like to complete the task, use TextOut to send "Hello, World!" to the printer's device context. You can use the WM_PAINT handler in PluginWindowProc as a starting point.
Because Simple doesn't get a stream, there's not much else for it to do as far as Navigatoris concerned. If the user resizes the window, Navigator obligingly sends another NPP_SetWindow()-you can watch this happen in the Java Console. All the user interaction, such as it is, comes through LiveConnect. Recall from Figure 13.3 the interaction between JavaScript, Java, and the plug-in.
Recall that, to install Simple, you copied a file named simple.class into the plug-in directory. The Java compiler, javac, turns Java classes (in .java files) that are human-readable into .class files, which contain bytecodes for the Java Virtual Machine. If there's a simple.class, you should look for a human-readable simple.java-and you find it in the source subdirectory:
import netscape.plugin.Plugin; class Simple extends Plugin { /* ** A plug-in can consist of code written in java as well as ** natively. Here's a dummy method. */ public static int fact(int n) { if (n == 1) return 1; else return n * fact(n-1); } /* ** This instance variable is used to keep track of the number of ** times we've called into this plug-in. */ int count; /* ** This native method will give us a way to print to stdout from java ** instead of just the java console. */ native void printToStdout(String msg); /* ** This is a publicly callable new feature that our plug-in is ** providing. We can call it from JavaScript, Java, or from native ** code. */ public void doit(String text) { /* construct a message */ String msg = "" + (++count) + ". " + text + "\n"; /* write it to the console */ System.out.print(msg); /* and also write it to stdout */ printToStdout(msg); } }
Note |
Chapter 7, "Overview of Java," describes the structure of a Java class file and shows what each line means. This section concentrates on the methods and data members defined inside class Simple. |
While you may not be a Java programmer, you can read the definition of the Java function fact() from this file. It's a simple function of the sort commonly used to teach recursion in freshman programming classes. You'll also recognize doit()-the star of the show on the Java console.
fact() fact() is a simple (and not very efficient) implementation of the factorial function. Its function here is to show how to call a native Java function.
count Remember that back on the Java Console, each line had a number. This little int here is responsible for that task. count gets managed by doit(), which will be described shortly.
printToStdout() On a Windows machine, we don't think much about standard out, and it's difficult to actually see that this function is running. Still, this function shows how Java can call native (C/C++) code that's back in the plug-in.
Note the word native in the following function definition:
native void printToStdout(String msg);
This keyword tells Java to look for the definition of this function in a C or C++ function. Now go back over to the ClassView tool and search through the Globals section. Here you find a function native_Simple_printToStdout(). The header for this function was produced by the Java Runtime Interface of javah. The programmer added the following two-line implementation:
const char* chars = JRI_GetStringUTFChars(env, s); printf(chars);
Here env is the Java runtime environment and s is the Java string to be printed. Both variables are passed in as parameters from Java.
doit() Simple's "big feature" is doit(). This function is called when the user clicks the button on the HTML page. The next section shows how messages get from JavaScript to this Java class. This section shows how doit() does its work:
public void doit(String text) { String msg = "" + (++count) + " " + text + "\n"; System.out.print(msg); printToStdout(msg); }
The first line of the function uses the plus operator to concatenate a series of strings. The sequence starts with an empty string so that the finished result is a string. After the message is built, it's sent to the console by using the print method of the System's out class. Finally, the program sends a copy of the message to standard out (for what it's worth). Figure 13.6 illustrates this process.
Figure 13.6: The doit( ) function, such as it is, is one of the few pieces of complexity in Simple.
Recall that the HTML file contained the following tiny form:
<form> <input type=button value="Click Me" onclick='document.simple1.doit("Hello from JavaScript!")'> </form>
This form directs a function call, doit(), to the named plug-in. LiveConnect does not allow direct communication between a plug-in and JavaScript. But because Simple, the plug-in, has an associated Java class (also known as Simple), all is well. The JavaScript handler onClick calls doit() on the named plug-in Simple1. That method gets picked up by Java, which calls the Java method doit(). Java, in turn, handles the message construction and the write to the system console internally, but calls the native method printToStdout back in the plug-in. Figure 13.3, shown earlier in this chapter, traces the flow of control between JavaScript, the Java peer object, and the plug-in.
When the interaction is over and the user is ready to leave Simple, Navigator calls NPP_Destroy(). Because there is only one instance, NPP_Shutdown() is called immediately after the instance is deleted.
NPP_Destroy() Recall from Chapter 11, "Step-by-Step Through the NPP Methods," that NPP_Destroy() is the place to undo the allocations made in NPP_New(). Simple's implementation of NPP_Destroy() calls NPN_MemFree(), passing it the pdata pointer it's been carrying from one function call to the next. Finally, it displays a message through Java to the Java console and then exits. Here's Simple's version of NPP_Destroy():
NPError NP_LOADDS NPP_Destroy(NPP instance, NPSavedData** save) { PluginInstance* This; if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; This = (PluginInstance*) instance->pdata; /* PLUGIN DEVELOPERS: * If desired, call NP_MemAlloc to create a * NPSavedDate structure containing any state information * that you want restored if this plugin instance is later * recreated. */ if (This != NULL) { NPN_MemFree(instance->pdata); instance->pdata = NULL; } DisplayJavaMessage(instance, "Calling NPP_Destroy.", -1); return NPERR_NO_ERROR; }
Caution |
Always remember to free pdata in NPP_Destroy(). If you don't, you'll leak memory out of Navigator and eventually crash the browser. Detecting the cause of such leaks can be time-consuming, and it's frustrating to the end user (who will naturally blame Netscape for the supposedly buggy browser). |
NPP_Shutdown() Just as NPP_Destroy() undoes the work of NPP_New(), so NPP_Shutdown() is the counterpart of NPP_Initialize(). NPP_Shutdown() verifies that this plug-in has been running with a Java environment. Because it has, it severs the link between Java class Simple and the plug-in. Here's how easy it is to disconnect from the JRI:
/* ** NPP_Shutdown is called when your DLL is being unloaded to do any ** DLL-specific shut-down. You should be a good citizen and declare that ** you're not using your java class any more. This allows java to unload ** it, freeing up memory. */ void NPP_Shutdown(void) { if (env) unuse_Simple(env); }
Note |
The plug-in actually connected to the Java class as part of NPP_GetJavaClass(), but because this function is called just after NPP_Initialize(), it's reasonable to use NPP_Shutdown() to undo NPP_GetJavaClass's actions. |
In this chapter, we built a complete Windows plug-in, using Visual
C++'s AppWizard. Although the plug-in doesn't "do" much,
it is quite complete in terms of functionality, including support
for LiveConnect.
ON THE WEB |
http://home.netscape.com/comprod/development_partners/plugin_api/index.html This page contains information on LiveConnect and Navigator Plug-ins. From this page, you can download the Plug-In Developer's Kits for Windows, OS/2 Warp, UNIX, and the Macintosh. If you download a Developer's Kit, you don't need the Developer's Guide, which is listed at the bottom of the page-it's included in the kit. Once you have downloaded the Developer's Kit, uncompress it. You'll find a directory named PluginSDK30b5 (or something similar-the exact name depends upon which version of the SDK you have). Inside that directory, look for a directory named Examples. There you will find the directory for Simple. The source is in the sub-directory named Source, and can be used in a Windows or Macintosh environment. Windows and Macintosh-specific files are in separate directories. |