Created by JediKnil
During one of my projects, it became necessary to find out the class hierarchy of an ObjectiveC object. The catch? The object had to be identified by name, and it didn’t have to be part of the Cocoa framework. Because of this, I had to create an Objective C class that could find Objective C class definitions (the Class type), and return usable data to my JaVA class.
In the original ProjectBuilder, it was a lot easier. There was built-in support, and the documentation (in the Legacy section on Apple’s site, all about WebObjects) actually applied. In XCode, a whole ton of this was broken, probably because Apple didn’t see the need to fix it. By now, it seems like JNI is the way to go; however, this may still be of interest to someone.
After many failed attempts (involving JNI, private JavaBridge header files, and the enigmatic .jobs file), I was finally able to get my bridged class to work (thanks to EricWang). So, here’s how you do it using a dynamic library in XCode 1.5:
Create a new “Empty Project” and name it whatever you like (maybe something descriptive?)
Add a Target (Project -> New Target) with the “Cocoa -> Dynamic Library” type.
Switch into the Target Settings’ Expert View and set the following keys and values. If you don’t have the specified key, add it to the list by clicking the little plus button. Note that my computer’s copy of jni.h seemed to be missing, so you will have to provide your own path
Header Search Paths — /Developer/Java/Headers */path/to/jni/dot/h Library Search Paths — /usr/lib/java *Product Name — libWhatever* *BRIDGE_OBJC — YES
3a. If you want to embed your library in your final product, add “-seg1addr 0xC0000000” to “Other Linker Flags” and set “Installation Path” to “@executable_path/../Resources” (like when embedding a framework). This prevents memory collisions between your library and your main product. If you have multiple libraries, try different numbers – the same offset will end up with another collision. Check out Apple’s prebinding documentation ( http://developer.apple.com/documentation/Performance/Conceptual/LaunchTime/Tasks/Prebinding.html ) for available, valid values to use with -seg1addr.
instead of
In my case, newWithClassName: actually calls initWithClassName:. This is fine, just don’t expose your init: functions in your .jobs file. More about this later.
Other than that, just write it like a normal class. Normal, unparametized init seems to work fine.
Create a header file that has the same name as your library. In it, put #imports for all your bridged classes. This is the easiest step in the entire project.
Create a .jobs file. This has become much harder since the WrapIt tool has disappeared from DeveloperTools (I don’t know when). Here’s a sample file. Anything in italics must be replaced by your code.
name project
header projectheader.h
import ObjCJava.jobs
stub-import aheader.h
selector -instance:method:that:takes:aLot:of:args = longInstanceMethod
class MyObjCClass = my.package.TheObjCClassIWantToExpose +classMethod +classMethodThatTakesAnArgument: -instanceMethod -instance:method:that:takes:aLot:of:args:
@{
*public static final int three = 3; // Java code to be added at somewhere*
*// in the bridged class. Often used to implement enums*
@}
@top{
*import my.other.package.*; // Java code to be added before the bridged class.*
*// Often used to add import statements*
@}
@end{
*private class SecretClass { /* My secret class */ }*
*// Java code to be added after the bridged class. No use I can think of.*
@} # end of the class
protocol MyProtocol = my.package.JavaInterfaceVersionOfMyProtocol -specifiedFunction:
type MyTypedefNumber = int SomeFloatingValue = double
map MyObjCMapOrSomething = java.util.Map using MyMapToAJavaMap JavaMapToMyMap
preinit-callout callBeforeInit postinit-callout callAfterInit
Whew! Now that you’ve finished, we can actually build the library – almost. Please post any questions about .jobs files on this page…after looking at Apple’s Legacy documentation at http://developer.apple.com/documentation/Cocoa/Conceptual/Legacy/JavaBridge/JavaBridge.5.html
Open up Terminal and cd to the same directory as your .jobs file (which should be in the same directory as your header files). Type in the following, replacing anything in italics..
bridget -java -stub -init -o output/directory/path bridgefile.jobs
You should now have Java classes generated for all your classes. If you used packages, your classes will be in a normal Java class tree (my/package/name/MyObjCClass.java)
There should also be stub files for your classes, in the form of .m code, as well as a file called “project-init.m”. Add these to your project and make sure they are included in the build as well.
Add the /usr/lib/java/libObjCJava.dylib library to your project. (Thanks to Mark Allan for helping me catch this crucial step). If you don’t know how to get to the hidden /usr folder, just click on this link: file:///usr/lib/java and drag the libObjCJava.dylib alias into your project.
Finally! Click the build button. Don’t worry if it takes a while – this is normal. Bridging takes a lot of work! There might also be warnings that pop up in the bridget-generated files; you can ignore these as far as I know (the one I get says `__ret’ might be used uninitialized in this function).
That’s it! If you put your library in one of the standard search paths (usually /usr/lib/java or /usr/local/lib/java), and include your Java class in your final product, it should just…work! Isn’t Cocoa awesome when you don’t stop to read the documentation?
You’re probably feeling pretty self-satisfied right now. But isn’t it kind of annoying when you put your product up for download, and some idiot sends you an email saying the program crashed because it couldn’t find your Java library? Let me tell you how to get around that…
If you skipped step 3a above, you should probably go back and do it…
Open your final product project and drag your bridged library into your files list.
If Xcode is functioning properly, your library should show up in your Frameworks & Libraries build phase. Drag your library from the file list on the left (not the build phase file list) into the Resources section of your project. Your library should now be in two different build phases: Resources and Frameworks & Libraries.
The way your bridged Java class loads its Objective C counterpart is through NSRuntime. If you check Apple’s NSRuntime documentation ( http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Java/Classes/NSRuntime.html ), you’ll see that the Resources directory is automatically searched for libraries, right? So you’re done, right?
Wrong. If you call librarySearchPaths(), you’ll see that the Resources directory NSRuntime looks at doesn’t exist, and indeed cannot exist in any valid bundle (they left Contents/ out of the path). Hopefully Apple will fix this at some point, but for now, go back to your .jobs file and add the following (bold) lines to the bottom of each of your class definitions. Example:
class MyObjCClass = my.package.TheObjCClassIWantToExpose -instanceMethod ** @{ static { NSRuntime.addPathToLibrarySearchPaths(NSBundle.mainBundle().resourcePath()); NSRuntime.loadLibrary(“ObjCJava”); NSRuntime.loadLibrary(“project”); } @}**
"*project*" should, of course, be replaced by the name of your project. Run bridget again, making sure to remember the -java option and adding -noload to your flags. Now when your class is initialized, your library *will* (which means "should") be loaded properly. Don't try to modify the generated java file in any way unless you're sure you know what you're doing...
Just kidding. Play around with it, by all means. Just don’t complain to me that your file isn’t working unless you’ve tried a fresh copy (and don’t forget to add the above code to your .jobs file).
March 16, 2005: Updated a whole lot of stuff to work with my version of XCode, which suddenly started acting weird… :) –JediKnil
Please help, I can’t get this to work and desperately need it for a large project I’ve written! Could you possibly upload your source code of this sample please? I find it a lot easier to follow instructions if I’ve got access to the full sample code. Anyway, I’ve followed your instructions to the letter (except the bits specific to my own code), here’s the error I’m getting
Linking /Users/mark/Desktop/testingBridge/build/libMJA.dylib Undefined symbols: _JAVAThrow _NSIdToJavaHandle __JavaStringToNSString __NSAllocateObjcObjectForJavaHandle __NSAssociateJavaObjectNoRetain __NSDisassociateJavaObjectNoRelease __NSJavaStubEnter __NSJavaStubExit __NSRaiseExceptionInJava ___NSRealObjcClassForId _NSSetJavaClassAndConstructorsForObjcClass internal link edit command failed Build failed for target “libMJA” using build style “Development”
Ouch! I completely forgot one step: adding the /usr/lib/java/libObjCJava.dylib library to your project! This should fix everything (I tried pulling it and got all of these errors). Thanks for helping me catch this. Also, remember that the JavaBridge is deprecated, which means Apple might (but probably won’t) remove support for it at any time. I (and probably you as well) will eventually have to rewrite our libraries using JNI. And as per your request, my ObjC introspection code is temporarily available for download (without the build directory) at http://www.geocities.com/jediknil/belkadan/objcjava.sit –JediKnil