Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2005-631
$eventId
ID of event: wwdc2005
$eventContentId
ID of session without event part: 631
$eventShortId
Shortened ID of event: wwdc05
$year
Year of session: 2005
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC05 • Session 631

JNI Development on Mac OS X

Enterprise IT • 1:19:39

The Java Native Interface (JNI) enables you to integrate Mac OS X-native code with your Java application. Additionally, JNI allows Cocoa or CoreFoundation applications to call out to Java. Bring your laptop, we will explore how best to use JNI from both angles, and explain what to be aware of when applying it. Topics include creating a mixed-language Xcode project, threading, invoking the JVM, AWT-AppKit interaction, and debugging techniques.

Speaker: Matt Drance

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Okay, welcome to 6:31, last session of the day. My name is Matt Drance, and I work in DTS, supporting Java, among other things. So I'm here to talk to you about the JNI. What is the JNI? I hope everybody here knows, but, you know, in case you don't, it's the Java Native Interface.

It's a mechanism that's been around since Java 1.0, and it lets you integrate pure Java code with native code. In the case of Mac OS X, that means C, C++, Objective-C. It's got a number of uses. In the older days, it was really heavily used for incrementally porting a native code base over to Java. So you want to do it in steps, keep some of your native code in place, and just hook it into the Java code that you've written so far.

It's useful still today for leveraging some legacy or open-source libraries that you might run into if you have a pure Java app, and you want to make use of something that happens to not be written in Java. It's also useful for the other direction of the first point, which is if you have a Java code base that, for whatever reason, you might want to port to Cocoa or Core Foundation or some other native framework, you can do that incrementally. And it also allows you to provide some platform-specific features that might be absent from the Java 2 Standard Edition. Java can't do everything, and, you know, the JNI can come in to help you bridge some of those gaps.

So what are we going to talk about today? We're going to talk about building a JNI application in Excel. We have templates and samples that, you know, already do this for you, but they can kind of be a little confusing and a little overwhelming, so I thought we'd walk through that and get used to some of the targets in Xcode and things like that.

And then we'll talk about -- most of this talk is going to be about Java native graphical user interface integration, mixing Java and native components. We'll talk about Cocoa Component, which is one of our extended EAWT APIs that lets you go ahead and embed an NSVP code. We'll talk about the Java container. We'll talk about the AWT native interface, sometimes called JWT for short.

And we'll also talk about the threading implications that come into play when you start to play these games. All through the talk, I'll talk about some of the simple things that you need to keep in mind when you're sharing data between the Java and native side. And we'll briefly touch on invocation of a virtual machine from C and Objective-C apps.

This is a loaded talk. We have a lot to talk about. I'm going to try to do it before 5:00. So we're going to move kind of fast. So let's start with building JNI projects with Xcode. Why Xcode? Well, aside from the fact that this is an Apple conference, Xcode has a pretty cool advantage in terms of JNI development in that it's the only IDE that can give you a single project that will go ahead and build your Java code, your native code, link everything, bundle it all together into a single double-clickable application with a single project with the click of one build button. And we're going to show you how to do that.

So what goes into creating a JNI project in Xcode? Well, obviously, you have your Java application, your double-clickable, just like a normal Swing or AWT app on Mac OS X. You need to create a target to generate some native headers using some command-line JDK tools. Then you need to actually create your native implementation using a native target in Xcode, set up dependencies between all of these different targets, and we go ahead and copy these files that are the products of these individual targets into your final bundled app.

So let's get hands-on right away. Can we go to demo two, please? How many of you have downloaded the supporting DMGs for the sessions and had a look at the projects that came with this talk? Okay. So those of you who have, go ahead and feel free to work along with me.

I had initially planned to really do this from scratch and go from the beginning and walk you through it, but something happened on Monday that requires I talk about some extra things. So we're going to cut this one a little short. We're just I'm just going to go ahead and open up I'm in Xcode 2.1 here.

Those of you who are working in 2.1 may have noticed a couple things that changed, and we'll get to that in a second. So I'll just start by running this first. And... We're going to go ahead and create-- we're going to go ahead and build this. And it's going to fail.

And that's because Xcode 2.1 changed something in terms of building. And so those of you who may have tried to do this in Xcode 2.1, this is a big difference. Those of you who have worked in Xcode in the past, the build products have been broken up into development and deployment subdirectories now. And we're doing a couple of references into the build directory.

And so we're going to have to change that along the line. But let's just start from the beginning. And I'll show you what this project does basically. So here's our JNI project. And we get through the legalese. And we basically want to go check out the paint method.

So in the original, this is basically a straight modification of the standard Java swing application template in Xcode. Now, normally, the paint method goes ahead and paints something like swing application or hello, world. I can't exactly remember. Instead of that, we have this method called get my full name, and we declare this as a native method that returns a string. So this is the first step to hooking into native services using the JNI.

So we declare our native method, and now we're going to have to-- in order to write a native library to implement that method, we're going to have to generate some native headers based on these classes. So normally, I would go up into the project menu and create a new target.

[Transcript missing]

So, the fruit of this shell script phase, which is going to be a .h file, I've added that to the project just for reference. And right now, at the time of, at press time, this was referencing something from the build directory. And we need to actually go ahead and go into the development subdirectory. So that's, if you were having problems building this in Xcode 2.1, that was the problem. So go ahead and change that.

And now let's have a look at this header file that got generated. There we go. So there's a lot of noise in here. A lot of this stuff, this is all generated by the Java H tool. And now at the bottom, Here is our function declaration for the native method. So you can see it's called myFirstJNIProject, which is the class name, and then getMyFullName. And it returns a J string, which is a Java string.

And it takes in a JNIEnv pointer, which is the pointer to the Java environment above us. And that's going to allow us to make JNI function calls to do everything we need to communicate back and forth. And all of the JNI functions that you create have either a J object or a J class passed in as a parameter. And that is the caller. So this J object is a pointer representing the Java object that made the JNI call down into C. In the case of a static method call, this would be a J class reference instead.

So we've got our header file, so now we want to go ahead and look at the native library. And this is actually very straightforward. You can see I just copied and pasted the signature from the header file. And I'm not going to go into the address book stuff, but basically we go ahead and we get -- we call AB get me, which is the currently logged in user. And we go ahead and we concatenate those strings using core foundation, and then we go ahead and we create a Java string that can be returned back.

So this is the money right here. We get our core foundation string, we get the characters out of it, and we create a Java string, return that back up. And now if I go back to the JNI project, which is the top level thing, I should go ahead and build and run. Give it a couple seconds. There it goes. Okay. Come on now. Okay.

So now instead of swing example, it says WWDC 2005, which is... which is the name of the currently logged in user. So obviously, this is not incredibly sexy or special. But it's a very simple example of what you need to do to go down into JNI and talk to a native API, return information back up. So I had mentioned that something happened on Monday.

And I was as surprised as you to hear about it, believe it or not. So we've got some additional things that we need to talk about, namely, this whole universal binary thing. So in order to create a universal binary, which is something you will need to do for your JNI libraries, if you have a Java application that is brought up, Java obviously is going to be native on either hardware platform.

So it's going to be your responsibility as well to make sure the JNI libraries that Java calls are native as well. Because Java is not going to come up in Rosetta. And by the time the application is launched, by the time your JNI libraries are loaded, the option to load Rosetta has already passed us by. So you need to make sure that your JNI libraries are going to be universal. So in order to do that, we're going to get the information on the current project.

And right now, there's a cross-development option here. And as you can see down here, there's a Mac OS X 10.4 universal option. And it says, are you sure you want to change? Yes, I do want to change the directory. So now, we go over to our JNI lib target here.

I'm going to go over to the Build tab. And there's an architectures item here, and that needs to be set to Native Arc, as you can see here. And it really should be that simple. So let's go ahead and build this and make sure we indeed did do the right thing, because I'm as new to this as you guys are. So now I want to go to my build directory. Let's go ahead and use the file utility. Have a look at this JNI lib.

And no, I didn't do it correctly. Help me out here guys, what am I missing? And no, I didn't do it correctly. Help me out here guys, what am I missing? Build succeeded. Now let's just do it again. Ah, now we have two files. Now we have two lines here.

So first one is for Architecture PowerPC, and the second one is for i386. So it really is just a matter of checking a few boxes in Xcode and rebuilding, and you'll be ready for these transitions that we were talking about. So I think I've beaten this to death, so let's go back to the slides.

Okay, so running through real quick what we just did. Create a Java application target just like you normally would in Xcode. Now, there's a number of ways to do this. Some of our examples do it a little differently. Our templates in Xcode do it a little differently. This is an alternate way to do it using all native targets, which is going to be important if you're going to build a universal binary.

If you have older legacy targets that were made with earlier templates, you're going to need to upgrade those to native targets, and then that will allow you to build a universal binary. We create our native header target, which is really just a shell script phase that calls Java H to get the signatures we need. Then we create the native library using the header that was just generated. We copy and paste that stuff, implement the function.

And the other thing that you see here, this is in the build settings of the JNI library. I skipped over this. The product name, which is going to be the central part of the file name, needs to be the same as your call to system load library. System.load library is what you do in the static initializer to go ahead and load the JNI library. So the name that you see here in product name needs to match the string that you pass to system.load library. And then you wrap the file in a lib prefix and a JNI lib suffix. So in this case, the file name would be lib JNI lib.jnilib.

And target dependencies. In the case of the project that we had there, the native target obviously had to depend on the headers. We needed the headers to get generated in order to use them. And the application target, which is going to be the final double-clickable bundle, also depends on the native library. And then that's going to allow us to build the entire thing in one piece once the JNI lib is done.

Finally, we need a copy files build phase, which is going to Move the JNI library right into the bundle. And actually, can we go back to the -- to Demo 2 for a second, please? I just want to show you these dependencies in person. So the first thing we're doing is creating the Java headers, doing the build script with the Java headers.

And here is the information for our JNI library. And you'll see here there's a dependency to the Java headers target. And I'll just remove this real quick and show you what it means to add one. You basically just click this plus button, click Add Target. And what this does is it basically just runs through the list. And whichever target you've got selected up here is the active one. It goes and runs through the dependencies. And it says, if I try to build my first JNI project-- and let's have a look at that one. This is the main one.

This depends on the library. So when we try to build my first JNI project, it's going to say, has the JNI lib been built? No. Then we go ahead and build the JNI library first. It's going to go to the JNI library and say, has the headers target been filled? No. Then we go ahead and build the headers target first. So just make sure that everything is in the right place. And then at the very end, we have that copy files build phase.

And we're going to move it into the Java resources, which Resources, Java. Contents, Resources, Java inside the application bundle. And luckily, all you have to do is select the Java Resources dropdown and just drag your JNI lib here from the products over into copy files. And that will do that for you right before -- right at the end of the build phase.

Okay. Back to slides, please. So one of the things we did in the previous example was we went and we got a string from address book, which was a CoreFoundation string, and we returned it to Java. If those of you who may be familiar with Live Connect or, you know, the web scripting protocol in Objective-C, unfortunately, it's not quite that easy. You do have to do a little work to pass your strings back and forth. Thank you for doing that. It's a lot better.

[Transcript missing]

Okay, so now let's talk about the really cool stuff that I can't wait to show you guys, which is the Java GUI integration. And first we're going to talk about Coho Component. And Coho Component is something that's been around since our first JDK 1.4 release.

It's a pure Java class, sort of. You extend com.apple, E-A-W-T, Coho Component. It's an Apple API. You basically extend this class, and this is going to allow you to embed any Coho NS view that's available to you on Mac OS X inside a Java AWT container. You have to write your native implementation underneath, but this basically allows you to drop it right into the component hierarchy. You can transmit events right through, or you can stop them using standard Java event listeners, and it's a really lightweight way of integrating Java and native components. So there are two key things to actually, doing a Coho Component extension or implementation.

You need to implement first the create NS view long method, which is, this is going to be your hook between the Java and native world. This is where you're going to go ahead, it's basically going to be declared as a native method, and you're going to create whatever NS view you're hoping to embed.

And when you get that pointer, that NS view star, you return that up as a 64-bit Java long, or a J long in the JNI world. There's also a 32-bit method called create NS view, which returns a J long. So if you're using a J int instead of a J long, we've deprecated that as of Tiger, because the Sun recommendation has always been, no matter what kind of system you're actually on, the recommendation has always been to treat your native pointers as J longs. And if you need to, if you're on a 32-bit system, you can safely get that long and cast it to an int, and the correct bits will be chopped off and saved, and you will have a legitimate pointer.

The other one is send message. And this is basically a configuration that you can use to send messages. So if you're using a convenience API, it lets you send really lightweight, asynchronous requests to the native side, rather than polluting all of your source code with a bunch of native methods.

This is a mechanism that's already in place, and all you have to do is just send different message identifiers down and just have a single native method implementation that maybe switches on that identifier, and then it tells--and then that will tell you which task you need to use. It's an easy way to do non-blocking requests. The key word is non-blocking. And I'm gonna show you how to do that. And I'm gonna show you how to do that. So let's see what's cool about that in a little bit.

So our first demo here is going to be something called the Quartz Composer Cocoa component, QC Cocoa component. It uses the Quartz Composer QC view, which is a new feature in Tiger. And this is basically gonna let us display Quartz Composer compositions in a J frame. You already saw that--those of you who went to the Java overview, you saw Scott demo this. We're gonna show you how it actually works.

So we're gonna go to Java class, implement the native side, and just move along as you normally would. So can we go to demo two, please? Okay. How many of you were at the overview and saw this demo? About half of you. Okay. So let's -- For those who didn't see it, I will go ahead and demo the built product first.

So up comes our project. And by the way, for those of you who are actually are going around -- going -- working along with me, that problem I mentioned about the development -- the development build and -- I'm sorry, the development directory for the header, that's going to happen in every project. So we're going to have to go ahead and do that every time. So let me just start with the Java file.

And like I said in the slides, the key things here are the CreateNSVLong method, which is going to go down across the JNI and create our pointer, return it back as a Java long, and here's our CreateNSV, which just calls CreateNSVLong, cast it to an int. And we've declared a few constants here, and these are going to be used for the Send Message API, which I had mentioned earlier. And this is just going to be a real easy way to go ahead and tell the underlying NSView, do something.

And then the NSView is going to go ahead and switch based on these constants, rather than have two JNI methods, two native methods I had to declare, more things in the header file, more implementations to copy and paste from the header file to the C file. And notice that the sendMessage method actually isn't defined here. That's because it's part of Cocoa component. All the implementation stuff that does that message send, that message, that request transmission, it's already done for you. So you just need to call sendMessage.

So just like previously, we have our Java headers target, and I think... There's another problem here, which is that a lot of these shell script variables changed between 2.0 and 2.1. So, those of you who are working along with me, we need to go into the Java headers target.

So, now we should be able to successfully build the Java headers target. Now we can retask. Now that the header file has actually been generated, we can go ahead and assign it to our project, and we'll be able to build everything else after that. But let's have a look at the header file for starters.

It's a lot of the same noise that you saw in the address book example, but there's a couple things here that are interesting. You see these pound defines here regarding Java QC view load message. So it actually not only did it get the native methods, but it went ahead and got the static constants that I declared in the Java file and defined those on the C side.

This is actually pretty useful. You know, it's a stylistic difference whether people actually go ahead and import the generated header file or because they don't like this noise, maybe they just copy and paste these function declarations into a header file of their own that's more readable. But if you're going to use send message heavily, I would really recommend just importing this header directly because now you have these defines in place for you.

The alternative would be to go ahead and define an enum in your own implementation, and then you'd have to make sure you updated it every time you changed something on the Java side. If you just use this header directly, all of these defines are going to be updated for you. And as you can see, let's go over to the implementation here.

This is the .m file. This is where we actually create our... NSView. And here's our create NSV long implementation. And it's just going to do a -- this is, you know, standard Cocoa stuff, allocate, initialize, auto release an object, and we're going to cast the pointer to a Java -- 64-bit Java long, and that's going to go ahead and be used by the underlying AWT to go ahead and embed this guy into the component hierarchy. And here is our AWT message implementation. This is the native side of the Java send message function that's made available to you by Cocoa component.

And you can see we're switching the message ID and we're using the pound defines from the header file. So these match the constants that we declared in the Java source. So in the case of a load message, we're going to go ahead and get a composition based on the string that was passed.

So here's our Java to native string conversion that we did in the slides. Get the characters out of the string argument. Create a new NSString. And load composition from file is actually just a method in QC view. It's not something we declared. It's just part of the standard ports compositor view.

And then we release the string characters when we're done, of course. And then the second message would be to actually start the animation. StartRendering is another QC view method that we just call. I probably could just have done this all in one message, but I wanted to demonstrate the difference or what it would look like to have multiple messages sent through the AWT message method. So let's go ahead and build this thing.

So it has to do a couple things. It has to compile the Java files, it has to create the header file, and then it has to go ahead and build the native targets. So let's see what's missing here. I'm guessing that the header target was not found. We're missing our source files for some reason. I wonder where those could have gone.

Okay, well, when in doubt, just build it a second time. Okay, so now here's our Java frame, and these are standard Java JButtons, and this black thing that you see here, that is our QC view inside the content pane of the JFrame. And I click this Load button. It's an ActionPerform standard action listener, and the ActionPerform method is going to go ahead and call sendMessage, which you can see right over here. We're calling LoadComposition.

"Which is then going to be a call to send message. And then play is likewise going to do the same thing. The action listener on this button goes ahead and does a send message using that start message." And there we have a Quartz Composer composition playing inside Java. It's not as exciting as Scott's demo. He kind of one-upped me on it. But I wanted this to be a very simple demonstration of how you can use Cocoa component to just throw something in to a Java application with minimal effort.

And there we have a Quartz Composer composition playing inside Java. It's not as exciting as Scott's demo. He kind of one-upped me on it. But I wanted this to be a very simple demonstration of how you can use Cocoa component to just throw something in to a Java application with minimal effort.

the exception of Jstring and Jclass which respectively represent string and class objects. So you're going to get Jobjects pretty much across the board no matter what you pass down. And if you need to hold on to these Java objects in the native side, your C function might return some value or just finish what it's doing and then maybe you have some callbacks that you're going to receive on the C side that you then want to propagate back up to Java. How do you do that? You need to hold on to that Java object. And when you do that by creating what's called a global reference. And we have these JNI functions called new global ref.

There's also new weak global ref which is basically a weak reference if you know what those are in Java. The important thing to notice -- to know when you start working with global refs is that they affect garbage collection. These are legitimate Java references on the Java side and the virtual machine does keep track of them. So you need to use them sparingly and responsibly. When you're done using a global you need to actually delete it.

If you don't, your Java object will not be garbage collected. So, again, all you need to do to create a memory leak in Java is write some C code. And the easiest way to do this in the case of native peers, which is what we're doing with this Cocoa component class, is just implement the removeNotify method on the Java side, which is a method that gets called when a component gets removed from the hierarchy.

And in your remove notify method, if you need to do anything, you usually don't need to do anything in the Java side. But you go ahead and call a Call down into the native code via JNI, and that is an opportunity for you to delete any global refs that you may have been holding onto, because presumably this thing's been removed from the hierarchy. It's probably not needed anymore, and it's probably bound due to be garbage collected.

So now we're going to talk about calling Java methods from C or Objective-C or C++. A lot of people have a hard time with this. The documentation is hard to find, and it's even harder to read. There are some special codes for, you know, for Java method signatures and the equivalents in C when you go ahead and look up a method in JNI and that you want to call across the JNI bridge.

So, you know, you can use the Java P command line tool and pass it the dash S flag, and this will give you JNI-friendly signatures for all your Java methods. So if you need to make a callback into Java, use Java P to go ahead and get the method that you -- the signature that you need, and then you can call back into Java. So, you know, we're going to talk about calling Java methods from C or Objective-C or C++.

So, you know, we're going to talk about calling Java methods from C or Objective-C or C++. and then grab that and then in your C code when you want to create your callback, use this string that you get out of Java P. And you always obtain the method from the class.

Even if it's an object method, you obtain the method definition from the class and then invoke it on the object later. You can call find class with an explicit class string or if you have access to a J object, you can call get object class and passing in the object.

And so right here you see an example of calling get method ID and class is presumably a class that we got using one of those two functions. And so you can see the example here of the signature at the end there. So in this case, it's a method with no parameters. So you have the open and closed parens there and the capital V means void. So that's the return value.

So you would put the parameter values inside those open and closed parens and then the return value comes at the end. And there's also an equivalent get static method ID if you need to call static methods. So that brings us to an even better Cocoa component demo, one that we did not show you in the overview. Can we go back to demo two, please? Okay.

So we can start in a bunch of different places here. Has anybody actually played with these demos? Has anybody seen the CW Cocoa component? No? Okay, good. Well, we'll have a surprise then. Okay. So first I'm going to bring up this Java example. This is a pure Java application with a JLabel, and I'm going to click this Colors button, and it's going to show me the J2SE color chooser. And I'm going to

[Transcript missing]

What I'd like to do, and assuming this guy's running, I'll give you a little taste.

So, we're going to start off with a little bit of a demo of what this looks like. And we're going to embed this thing as a Cocoa component. We're going to extend Cocoa component just like in the previous demo, and embed it right into the hierarchy. So now instead of this colors button, you see this color well. So that is an NSColorWell native view that we're creating in Objective-C and embedding using the Cocoa component mechanism. And I click on this guy and the native color picker has shown up. And this is not, I didn't have to do any code to do this.

This is standard NSColorWell behavior. The difference is that it's inside a J frame. Now we're just sitting around an Objective-C world and, you know, if I select these colors, Cocoa knows about them, but we need a way to go ahead and tell Java that a color selection has changed. You know, this work is not done for us like it is with the J color chooser. So.

In order to do that, we're going to need to call back up into Java from C. And as you can see, it's working. I select brown, I select magenta, green, got my crayons. So to be clear, the color well change in color, that's standard Cocoa behavior, but this J label is actually being changed because we're creating a callback every time the color well receives something.

So let's show you how that works. So here's our, let's see, I want the Java color well. For those of you who have the project open, feel free to follow along. The Java color well has a Create NSV Long, just like the Quartz Composer. It's got a casted Create NSV, that's nothing new. And we've also got some generic listener support, you know, just like any other AWT kind of component.

So we have an array of listeners, and I've created these color selection listener and color selection event classes. And this is going to be the framework that's going to allow us to actually communicate this stuff back to Java. We could do it real lightweight with callbacks, but I want it to be a good Java citizen and create an event structure for this. So the key to all of this is this Cocoa color change method.

And, you know, I've been working This method, you notice I listed it as private. This method is what's going to be called back from Cocoa when a color change occurs in the NSColorWell. And so at this point, there's been a color change in the NSColorWell, and we want to go ahead and notify Java of it.

So this is, I mean, if you've looked at any of the JDK sources, this is very standard stuff. We go ahead and we loop through all the selection, through all the listeners, we create an event, and in this case we're past the RGB and alpha values that came in from the new color on the Cocoa side.

And then we go ahead and slap those onto the event queue asynchronously, which is a key point that I'll be talking about a little later. And I listed this as private basically because I don't want Java code accidentally calling this. The only purpose of this is really just to get a callback from the native code. So now we can go check out our header file.

And this doesn't look much different. You notice we have a bunch of constants here. Close panel, deactivate, and here's our remove notify. And I mentioned that in the slides, and I'll show you how that actually works. And we've got a couple JNI functions here. There's this one called init method IDs. Actually, I skipped over that. I'm going to go back to the Java class for a second. Here's our static initializer where we load the library, just like in all the other examples.

And then we do another call here to initialize the method IDs. And this is just, you know, at class load time, we go ahead and we look up the callbacks that we know we're going to need later. And the reason we do this is because the Java method IDs live as long as the class does.

So there's no need to reflectively look up these callbacks every time an event is fired. Because as you can imagine, that's going to really bog things down real quickly. So we just initialize the callbacks that we know we need at the very beginning. So here, in this case, it's just one. It's that color change method that we declared up in Java. And this is how we look it up.

Because it's a static native method, we get the class as the caller, so we don't need to look it up, and we go ahead and we request an ID. We request a hook to the CocoaColorChange method, and here's the signature. Four floats and return a void. In this case, it was actually a very similar, I'm sorry, a very simple example. Let me show you how I did that. I was talking about the Java H.

The Java H utility a second ago, I'm sorry, the Java P utility. So I'm going to do Java P-S. And let's point to this java headers.jar and Apple VTS sample code. CW Cocoa component. And I'm just basically, you know, doing the fully qualified class name of the Java color well class. and I am blocked. I must have some kind of permissions error here. Let me try to fix that.

So what the Java P utility should normally do is give you a list of all the methods, all the Java methods, but the "-s" flag is going to go ahead and give you this little piece, which is going to be the native signature that's used by JNI. And I basically cut and pasted this from the Java P output.

So now let's go take a look a little further down and see what we're actually doing here. Here's our general initializer, our createNSViewLong method. which is just going to initialize a new color well. And you notice instead of just creating it normally, we're actually going to pass in the object that called this method, in which case it's the Java object sitting on top of us, the Java peer, if you will.

And we're actually going to save this by creating a global reference, and we need to do that because we're going to make a call back at some point in the future. And we don't know when that is. This function inevitably has to return. Later on down the line, we need to inform people that this color change occurred. And we're going to do that by holding onto a global reference. So that's going to be relevant later.

After that, we go ahead and, you know, I had mentioned that you need to maintain your global references when you create them. And so I use removeNotify because it's not really a special thing. We don't need to go ahead and do any kind of parameter passing or anything. We've declared removeNotify as a native -- well, we haven't declared it as a native method. We've overridden it from the standard component.

And we're just going to call super, but before we do that, we're going to do a send message, which is that standard Cocoa component message transmission API. And at that point, we just go on faith from receiving the message that we're done, we don't need this anymore, and we go ahead and delete the global reference. And this will allow the Java object to be garbage collected later on.

So where's the real exciting and interesting stuff? Well, This is the standard activate method from the NSColorWell class. And we're just going to bring up an NSColor panel. pass it right along. The only thing we're doing here is showing the opacity slider. It's obviously not necessary, it's just something I thought would be cool.

And then the passing up activate to super inevitably shows the color well, I'm sorry, the color picker and ties it to the active color well. And the other thing we did is we just overrode the set color method. So that's every time I change something here, obviously set color is being called on the NS color well.

And that's our opportunity to go ahead and call back to Java and say, hey, the color just changed. Why don't you go ahead and change that J label? And here's our panel color change method. First thing we need to do is get a valid JNI environment pointer. We do that by using this utility function called attach current thread. JNI environment pointers are only valid for the current thread that you're operating in.

because there's also a Java VM pointer. I'm going to scroll up here. And the Java VM here we've declared a static, and we basically just cache it using onLoad. The Java virtual machine is always, you know, a-- basically a singular-- a singleton instance. You can only have one JVM per process. And you guys probably know this already. So you can cache the VM, but the Java environment is tied to a specific thread. So we need to create a fresh one using attach current thread.

And then we go ahead and we get the color from the standard Cocoa callback, and we're converting this into something that basically we know is going to be safe for Java to use, a generic RGB color. And then we go ahead and create four floats out of this, red, green, blue, and alpha, and then we call our color change method. So here's the method ID we cast when we loaded the class, and here's the peer that we saved using global ref when we created the ColorWell instance.

And then we go ahead and we pass along. This is obviously a Verargs declaration. We pass the red, the green, the blue, and the alpha. So let's go back to the ColorWell, and I'll show you what we're doing. So here's the method. It's expecting four floats, and this is what happens when we make that callback and we do an invoke from the native side. This method's going to be called, and that's going to let us go ahead and change things. So right now, this is just generic. In the actual test case, I have a color selection listener.

which So, I click on this, and we get a call to set color in the NSColorWell, and we go ahead and we pull up the object and the method ID, call back to Java, Java goes and fires up all of its listeners. So, you think this is a lot of work, and you may be worried about performance, but actually it responds pretty darn well.

As you can see, every time, every single one of these little color changes is a Cocoa call, and then a callback across the JNI over to Java, across that bridge, and then we're creating AWT events and putting them asynchronously onto the AWT event queue, and they're being processed pretty well, perfectly in time. The opacity works fine, too.

So that... I know a lot of people have been kind of grieving over the standard JColorChooser, so I'm hoping people will get some use out of this. And obviously with the code that you have here, which is part of the DMG, it's not going to be much harder to modify this sample to use, for example, the font panel, which is another thorn, tends to be another thorn in people's sides when working with pure Java. So I think we're ready to move back to the slides.

So, now that we're really getting into back and forth talking, you need to keep in mind, you know, once you go down this road, you are a Cocoa programmer, and you need to be mindful of what that means. So, we have the Cocoa AppKit, which is the event system in Cocoa, and we also have the Java AWT. Now, these two, the AWT is built on top of Cocoa, as you know, but they really are separate environments, and they can coexist.

But you need to help them, and you need to, well, not specifically help them, you need to not stop them from coexisting. We have two separate event queues, and they're operating on two separate threads. And you may have noticed that in the example that I just showed you, we were doing our Java callbacks using an event queue that invoke later call. And this gets us on the correct thread, and it also does so asynchronously, and it allows us to not block one of these threads against the other.

So the key is when you're communicating from one environment to another, if you have an action performed and then you go ahead and need to do some AppKit drawing or some kind of event transmission, you need to do that asynchronously. And in this case, if you need some kind of return value, you should really set up callbacks, much like I did in the previous example with event transmission. And now it is time for the only diagram you're going to see in this entire talk. You go home, you ask your manager, your manager asks you how technical the session was. You can tell them there was only one picture the whole time.

So we have here the AppKit that you can see up here running on top, and the AWT event queue below. So this MyAppKit notification, let's say your JNI code has registered for a notification from AppKit, and those notifications always occur on the main AppKit thread. And the AWT is actually busy processing a resize from either the user moving the resize box, or maybe something on the AWT thread explicitly called set size. So the app, our AWT implementation is going to go ahead and make a call to set frame, which is the Cocoa equivalent of set size.

And it's going to wait to get into the, into the AppKit queue to do that. So assuming your, let's say your notification is currently being processed, and you make a Java call back because you want to, you want to tell the Java world that this notification has occurred and update something in your AWT GUI. You make a call to event queue dot invoke and wait, which is halfway there. You've at least properly gotten yourself onto the AWT thread.

And your AWT callback properly gets into the AWT event queue. The problem is, because you used invoke and wait, you are blocking. And none of this is going to return until your do-AWT-work callback has returned. Unfortunately, it can't do that, because it's still waiting for the resize implementation to be finished. That can't do it, because you have blocked up the AppKit loop with your notification handler. And the result here is our famous friend, the spinning beach ball.

So what can you do about this? Well, when you're calling the AWT from AppKit, we really strongly recommend you use the event queue.invoke later. And the difference here is that it will not block. It will put you on the thread, on the AWT thread, just like invoke and wait does, but it will not wait. It will simply post what it needs to, the runnable that you pass, onto the AWT thread to be executed, and then returns. And it allows you to get out of the way.

[Transcript missing]

So let's see the difference. So here's the same setup as before, but now instead, our notification handler is going to actually call Invoke Later. And you can see the dotted line we've done differently, meaning that it's non-blocking. So we get our callback on the queue, and then Invoke Later is able to return immediately.

Nothing has really changed. You're still going to get your callback done, but this is going to allow your notification to get out of the way. So that resize can get processed. The set size call can get off the AWT queue, and then your AWT callback can finally get done. And this is a happy JNI application.

[Transcript missing]

Okay, so I'm going to open up this fourth demo with the incredibly revealing title of JSheets. So here is a pure Java application, and you may be used to the J file chooser. Best friend to some, not to others. And so this is a very simple demo. It's just going to let me choose a file. I'm going to open the J file chooser, and now I'm going to show a J option pane saying, here's the file you selected.

So, a lot of people don't like the J file chooser, they'd like to do more, they don't think the AWT file dialogue is a viable solution. So, maybe, just maybe, we could do something different, like say, So, this is a standard Cocoa NSOpenPanel that we have attached to a JFrame. And let me go ahead and show you what we have done.

Select another file. Now I'm going to click Open here. And then the Cocoa panel goes away and our J option pane shows up. So this was a Cocoa component that came down. We got a call back from it receiving the file name. And then we went ahead and posted it to AWT. We passed the string across the JNI. And then AWT was able to react to it by showing up a dialog. So how do we do all of this? Let's open up the Xcode project.

And we have this JSheet delegate class, and we've got a bunch of, uh, The real star here is the native show sheet method. And we're passing an integer for the type open save. We pass the component that we want to be the parent, and this is going to be where we start using our JWT code. And we're also going to pass in a listener, which is basically going to be our callback handler for when the sheet closes.

The thing about JWT is that the sheets in Cocoa don't behave like file choosers or file dialogs do in Java. When you create a J file chooser or a file dialog in AWT, you basically assign an int value, you basically wait for it to return an int value. Let me, I've got my J file chooser code in here, and let's have a look at it.

So right, so here's your pure Java code that calls JFileChooserShowSaveDialog. This is going to show the save dialog, it's going to block, and then when the user dismisses it or clicks cancel or OK, we get an integer result value. This is the pure Java example. Well, because of that big diagram I just showed you, this may be a problem, blocking against Java, waiting for AppKit to return something to us.

Luckily, Cocoa makes it easy for us, or depending on how you want to say it, forces it down our throat, because the sheet model with NSOpenPanel does not block like that and return a value anyway. Even if you're just doing pure Cocoa, you have to specify a callback that is then called when the sheet is dismissed.

So that part is taken care of for us, and I'll show you in a second. But here's our method, showSheet. And we have wrappers for it, show open sheet, show save sheet, and we've tied those to the action listeners on our JMenu items. So let's go over to the Objective-C world.

And here's our sheet delegate implementation. Let's start moving down. And now you can see the value of the init method ID's function. The earlier example with the color well, we only had one callback. Now it's starting to get a little hairy, and there's a bunch of different things that we may need to do in case it was the beginning of -- in case it was the end of a save, in case it was the end of an open, if it was a cancel. So now we can do all these things in one place, and then just know later -- and know later on with confidence that we have them.

So here's our native show sheet implementation. And we're going to -- This is where the JWT comes in. This getWindowFromComponent is a function that I have created right further down. And now you get into standard JWT code. A lot of this may look very familiar to those of you who have seen the JWT example. It's almost identical. These JWT drawing surface and drawing surface info, these are standard structures that are part of the Sun AWT Native Interface API. And then there's this Mac OS X drawing surface info that's part of our own implementation. And you can see that.

: So, we have a JVM header file here. Here we've implemented the JWT machine-dependent header. Okay, so going back, we get the AWT, we get the drawing surface from the parent component, in this case, the component that we passed into the native show sheet method. We get the drawing surface info, and we go ahead and we get the platform-specific struct.

From there, our implementation provides you with an NSView, and this is the NSView corresponding to the component that you passed in to the request. So once we get that, we can just call viewWindow. This is a standard Cocoa call, and that's going to give us the NSWindow pointer that we need.

Now, granted, I'll admit that this may not be what the founding fathers had in mind for JWT. You know, like I said earlier, this is really supposed to be a native drawing thing, but everything we're doing here is completely legitimate, and as you saw earlier with the example we showed you, it works.

So now we have our NSWindow, and we just switch. This is a similar, you know, kind of a similar example to send message. We switch whether it's an open panel, the calls are slightly different. We go ahead and we call performSelector on main thread to show either an open panel or a saved panel.

[Transcript missing]

Create an NSOpenPanel, set it to allow multiple selection, round of applause, native file dialogs with multiple selection. Okay, so you're too kind. And then we go ahead and we show this as a sheet.

We start at the home directory, obviously you could customize this. And now we have this delegate, which is the self, and the did end selector. So this is the callback that's going to happen in Cocoa when the user either selects a file or cancels the sheet. And in this case, it's open panel did end.

So now, at this point, when this method is called, the user has dismissed the sheet one way or another. And we check the return code. We get a valid JNI environment again. If it was an okay, meaning something was selected, we go ahead and enumerate through the file names. We create an array.

So this is something that's a little different that we didn't talk about before. You create an array of objects in JNI using this new object array method function. There are also primitive array functions like new int array, new long array, so on and so forth. So we create this array, and we do our standard string conversion. We get the characters out, cast it to a JCAR, and call new string.

And now we make this call to set object array element. Working with Java objects across the JNI is not as easy as just referencing them using open and closed brackets like you're used to. You have to actually set object array element in here. So, we do this for every case.

Now you can set, you can pass in one element in here, or you can pass in a series of elements. There's matching functions for both of those. And when we're done here, we go ahead and we delete the local ref to the string. This is different from a global ref, but it's more cleanup work that's necessary when you're creating objects.

And now we do our callback. And much like we had the Cocoa color change callback, we have the open finish method. And we're going to call that back in Java, and I'll show you up here what that looked like. Fire Open Sheet Finish. So let's see what that looks like on the Java side. We're jumping back and forth.

So here is...

[Transcript missing]

"But, just for fun, we'll do it again. And you can see I've got some stuff here. So I'll show you what I was talking about with that diagram. It may have been a lot to swallow the first time, but we'll show you exactly what we mean in a second." And as soon as this comes up, I can show you when I just do Command-O for open.

And you notice because of the asynchronous stuff that we've done here, I can still resize this window despite the fact that we have gone ahead and shown a sheet using a Java callback. If we were doing-- if we were failing to block-- if we were blocking in our performSelector on main thread call, this would give us some problems because the resize would not be able to be processed. We're busy waiting for this sheet to go away.

And so, I click open, that asynchronous call on the Cocoa side for open panel did end gets called, which goes ahead and checks whether I cancelled or selected a file, and then we see down here user cancelled open save. So let's see what I was talking about earlier with these. I have this handy call here called deadlock.

And what we're going to do instead of the good citizen thing of an invoke later, we're just going to call back our open sheet finished handler, which is, again, going to show that J option pane with our return value. So we're just going to do that from wherever we happen to be when we get this call back.

And we already know that this call back occurs on the app kit thread. So here we are. We're getting a call back from app kit, and we're going to try to show an AWT dialogue from the app kit thread, a blocking one. Let's build again. And I have an error. What did I do wrong? One more time.

Luckily, I'm a little ahead of time, so if we have a problem, we can just do this again. Okay. This side's clean. We haven't changed anything here. Now what we're changing is the callback. Now when I click this Open function, we get that callback from Cocoa. And when we talk to Java again, we're no longer doing the event queue invoke later. So we're going to go ahead and try to show a J option pane from the AppKit thread.

And yeah, we're not going to get too far there. And there's our trusty friend, the beach ball. And so the reason this is happening is, let's go ahead and have a look. If you were at the develop, debug, and deploy session this morning, you would have learned how to do this. We can get our JSheets process ID, 1170. And I do a kill-quit 1170. Is that right? Was that the right PID? We'll find out. And here's our thread dump.

Now let's see what's going on here. Here's our event queue. It's doing some kind of container validation. That's not something that we did. That's just something that's normally going on in the AWT right now. And here's what I told you to look out for. You don't ever want to see your code sitting on the AWT-AppKit thread, because what has ended up happening was here is our Here's our open sheet finished callback from Cocoa. And we've gone ahead and shown a Joption pane, which is going to go ahead and try to get the AWT-TrueLock, which unfortunately is being held by the AWT-Queue already.

And because we're busy blocking the AppKit thread with this, AWT cannot go ahead down into the Cocoa world and do this component validation. So there's your classic deadlock. Oops. All settled by using an invoke later. So let's see the opposite, or not the opposite, but the other way around. Let's change this back to false.

So here's the Cocoa example. Now instead of doing a perform selector on main thread, we're just going to go ahead and call the show open panel directly. And again, this is in response to an action performed on that J menu item. So we're going to go ahead and just right from our action performed, right from the AWT thread, call into AppKit to try to show this sheet. And let's see how that goes. That goes pretty well. I wonder if I-- make sure I actually built the bad stuff.

Oh, yeah, so I was expecting to see a sheet, and instead I saw a crash. So you get the general idea of, you know, whether it be a hang or a crash, if you don't play by these rules, you're not going to get what you expected. That's practically guaranteed. And I think we can go back to the slides now.

Okay, so troubleshooting our deadlocks. You know, you saw the kill-dash-quit command. You saw it this morning in Develop, Debug, Deploy. You saw me do it right there. In order to troubleshoot the native side, if you end up locking up your native code, you need to use GDB. And there's a couple useful commands, namely the backtrace.

command, which gives you a dump of the current thread, which is, in the case of a deadlock, possibly the one that you want to see, see where it is. And then there is the thread apply all backtrace command or T-A-A-B-T in shorthand. And this will print everything, all of the native threads. So that will be able to tell you maybe where your code is hung up and where you may have gone wrong.

And even from GDB, you can get that kill-dask-quit style right from the same utility. If you pass in the call void PS, just like you see it here, you type that into GDB, the PS command will give you a backtrace of the current Java thread, which may or may not be useful.

In the classic deadlock case, the Java thread that's hung is probably the one where you forgot to do your invoke later or are holding on to something you shouldn't be. And then also if you want to print all of the threads, you just call PSS, which gives you all of them. So some tips.

To store your native pointers, again, use longs. And the creatensview kind of hits the creatensview long function kind of hits this point home. But if you're just doing other things to create native structs or whatever and you need to pass those back up to Java for later use, make sure you store them as Java longs.

Create a cache at class load time when system load library is called. It will save you a lot of time and trouble for when you need those callbacks later. And because the JMethodIDs are valid as long as the class does, you can safely cache them. And be careful with your global refs.

Make sure everywhere you have a clear creation point, there is a clear deletion point that you know will be hit. Otherwise, you're going to start leaking objects on the Java side. If you have a component, you can do this in remove notify. It's a very reliable and clean and sensible place to do it.

So doing specific work with Mac OS X, if you are down in the native side, you need to get something inside -- from inside your Java application, your first thought might be to pass this huge URL string down into -- across the JNI that you then had to parse using C. In fact, you can just go ahead and use the CFBundle APIs.

If you have something that you know is in contents, resources, Java, go ahead and pass in the resources constant to the appropriate CFBundle API, and it will get what you need. And again, once you start playing these games, you are a Cocoa programmer, so you need to remember all the memory management stuff that goes along, not just the threading, but the retain, the release, the auto-release. The one exception is Cocoa component. You need to go ahead and retain that for us as long as we need it. Threads that you created need to have an auto-release pool in them.

And you may -- if you look into the JSheets code, you'll see that when we go ahead and create our sheets, we actually create an auto-release pool there because we can't necessarily be guaranteed that there is one in place. The only exception for that is create NSView and AWT message because we know that those are happening on the AppKit thread. And another quick tip, you can use the throw new function in JNI. If you're doing Objective-C exception handling, you can go ahead and create a Java exception and throw that back up, you know, with whatever message it may contain.

So VM invocation, real quick, I think I just hit zero. So it's a very standard call, JNI create Java VM. This is pretty standard to do across multiple platforms. It's C code, so it's not compile portable. But the code should be very similar. You create a struct of VM initialization args. You pass in JNI version 1.4 if you want 1.4.

The key on Mac OS X is to make sure you not start the virtual machine on the main thread. Don't do it from your main method. So if you're in a CoCoL application, go ahead and call detached new thread with selector. And in this case, your selector is going to be the method that actually calls JNI create Java VM.

Now for 1.5, we have an environment variable that you need to set. Just do a simple set end. And you set Java JVM version to J2SE5 or 1.5. And our updated samples code simple Java launcher, which uses core foundation to start up a Java VM, and Java splash stream, which has a Cocoa example, both of those have been updated to do the new 1.5 invocation. So take a look at those.

And again, I just want to talk a little bit about the Intel transition, we did that in front of you in the first demo. But start building your JNI libraries as universal now. If you go home today and you're interested in doing this JNI work, go ahead and start checking those boxes. I know it's a lot of work. Check the check boxes, build the libraries, and you'll be ready to go for the foreseeable future. Something to keep in mind, if you are writing a native launch or using JNI Create Java VM, that will not work in Rosetta.

You need to have a universal binary if you're building a native application that's going to start a Java VM. So if that's something you are interested in doing, the message here, and I'm sure it's been the message everywhere, is build universal binaries. And for the most part, you don't really need to be too worried about your data. But there are big endian versus little endian issues, depending on the kind of data that you're passing.

So if you're going to be packing-- you saw when I did my color notification across the bridge from the NSColorWell to the J label, I passed each of those RGBA values independently as floats. That's the better thing to do. The alternative may have been to go ahead and pack all of those into a single Jint or Jlong.

And if you're going to go ahead and start packing things, then you're going to run into endian issues when you go across different architectures. We'll be talking about this in more detail in tomorrow's virtual machine exposed session, which I don't think is on your calendar because it was a secret session. So if you really have concerns about this stuff, come to the session tomorrow.

And for more information, Alan Samuel is our Java evangelist, and Wiley is our Java product manager. He's the marketing man. You can contact either of these guys for questions or concerns. And if you're real confused about all this stuff I was talking about, then you probably need to talk to me or my counterpart, Ted Jouzevic, who you saw this morning. And you can get a hold of us by going to [email protected].

Related sessions. Well, unfortunately, these have all happened. But when you guys have access to your online content, you can go ahead and look at these. There's two of them, actually. The Java Performance and Graphics Tuning is tomorrow at 9, followed by the VM Performance session at 10:30, which is actually a lab.

and sample code other resources. There are a lot of resources that I'd like to point you to for this session. Obviously the samples that came with the talk. There are other things like the OS X adapter sample, which one of the things you'll notice when you look at these samples, the ones that are applicable, like the J Color Chooser and the Sheets, those are actually detecting what platform they're on, and in the case that it's not on Mac OS X Tiger, it doesn't even load the class with the JNI content.

So in other words, these built applications will run on Windows, despite the fact that they have JNI libraries, because the libraries won't even be loaded on the other platforms. So you can write platform-specific code without sacrificing your portability, and these examples will show you how to do that. We also have debugging Tech Notes. We have other porting Tech Notes that you're gonna want to look at, and you can see them linked to this session.