|
Linking to the same static library in few pluginsHi ! I'm working on some plugins that all link to the same static lib, that is included in the plugin Resources folder of each of the plugins. No problem, apart that I can't use the isMemberOfClass test in obj-c, because it seems that I declare my custom class twice. Does anybody had the same trouble and managed to resolve it ? Thanks !
|
First, you're not using a static library (a static library gets built into your code, so there's no extra file), but probably a dynamic library.
To resolve this, I often made the loading conditional -- check to see if the class exists, and if so, use it, else, load it. This can be annoying if you end up changing the lib though, as you can't control which version gets used.
Alternatively, change the namespace in each incarnation so there are no collisions. This is annoying and chews up address space though :(
Thank you very much for your simple and clear explanations !
I'm building a file with the .a extension, I thought that was a static lib... But I have a lot (a lot !) to learn... I begin to understand that I should include ALL the code in each of my plugins, like vade is doing with his "utils"...
I'll try it, hoping that will resolve the problem...
ok, a .a is a static library, but you don't need to distribute those (beacuse they should be in the binary already). You can't disambiguate obj-c classes in statically linked libraries. That's why you want to use a dynamic library, and conditionally load it (that way each plugin does contain all the necessary bits, but you're still able to disambiguate it).
I'm a bit ashamed :-) that I didn't know a static library was included in the binary... Hummm...
One more question : how to "conditionally load" a library ? If I link the dylib and include it in the bundle I guess I have to link with it in the XCode project settings (as I was doing with my static one...) Then how can I tell the plugin to load it or not ?
Sorry for these basic questions. I realize I'm very empiric in my learnings... :-/
at the lowest level, dlopen and dlsym are the functions used to load code and resolve symbols. You probably shouldn't use this though, because it's not really useful for obj-c code (obj-c classes have different kind of symbols that dlsym isn't very good for finding).
NSBundle is the higher-level way to load code dynamically, but it has to be packaged in a bundle. (so a bundle in your bundle... yo dawg, i herd.. and all that jazz). This is probably the easiest. Then when you initialize your plugin for the first time, you see if a given class exists (NSClassFromString will return nil if the class with a given name doesn't exist) and if not load the code, else just use it without loading the bundle in your plugin.
There's another more complicated approach that I often use here at work, but I don't bother explaining that unless we find that you actually need it.
[note: it's critically important to not link against your bundle if you want to conditionally load it. If you link against it, it will be loaded unconditionally]
Wow ! Thank you so much for all this ! I'll try this and tell you.
A problem with conditionally loading the shared code arises if you have two plugins with different versions of the same code but they expect different behaviour of the shared code (perhaps because you fixed a bug or added a feature in a later plugin). You're going to be lumped with the version that happened to get loaded first, and unable to (safely) load the newer version if the newer plugin requires it.
If the code is your own and is going to be used whenever your plugin is loaded anyway, there usually isn't much reason to package it separately.
What we do for the v002 plugins is compile the shared code as part of the plugin, and we have a simple mechanism to generate unique class-names for the shared classes. This is necessary to avoid class conflicts and means there are never two versions of the same class in two different plugins. Instead of eg, v002Shader twice you end up with v002BlursShader and v002KinectShader, and each plugin is using the version which provides the behaviour it relies on. If you are keen to link your shared code into a separate library you could use the same class-naming strategy in a library and link it normally without having to set up conditional loading.
For the v002 shared classes it's set up with a few preprocessor macros and the Objective-C @compatibility_alias directive (so we can still refer to classes by their common names in code) - see v002UniqueClassNames.h, and any of the shared classes for it in use.
https://github.com/v002/v002-Utilities
For changed behavior, definitely. However, it's still possible to unload code (as long as you haven't really used it yet and aren't holding on to any symbols from it) -- just have a class method that reports the version number, and check the bundle for a higher version -- if it's there, unload the former and load the newer one. At the end of all plugins loading you'll have the latest. Version numbers with multiple dots are useful here (some dot changes should indicate bug fixes only, and a higher dot would indicate a feature addition, etc).
[Note: unloading might be really bad for the obj-c runtime, I haven't really messed with it to know]
The problem with uniquing classes is that it bloats your icache footprint even more than the runtime already does. Not a huge deal, but it does have costs.
I'm generally inclined to think your approach is a better general plan unless there are circumstances that would make you want to do something more elaborate.
I think it's nice to just be pragmatic and do whatever is a best fit for the scenario. There's much to be said for a library being external of all of the plugins that use it. I tend to like to do this for my own working methods.
This could make sense for QC-ers, if developers approached it more like providing libraries, and then made an example plugin. It depends on what one is after I guess. It makes sense to me that things like model loading, cv, box2d/physics stuff should maybe just be external libs that are maintained with some QC helper methods, and devs would make plugins using those libs, instead of the repetition that happens.
@bangnoise, yes I had noticed your particular way to change classes names. I find it very elegant by the way...
My first problem with my library is that two different plugins are loading twice the same class but don't recognize them (They use the same renderer patch and isMemberOfClass or isKindOfClass in this patch doesn't work for one of the plugins). I managed to resolve it by calling respondToSelector instead of the previous methods).
Well, I could just do like this and distribute my lib in the bundle... But you guys are proposing so many approaches that it seems a real question, ain't it ? I like the conditional laoding approach. Doing like this I'll have to implement a versioning protocol though and to keep my versions backward compatible...
The purpose of this library (which is my code, except poly2tri), is to be reusable by... myself (well, if it does interest someone one day, hmmm...) for coding plugins. It should be transparent for the end user / qc-er.
For Box2D / OpenCV, I've been working for months on wrapping a lot of functions to be easily (for what means "easy" for me, again) usable directly in QC, and I'm sure it would be too much my way of doing things (empiric !!!) to be used by others in coding. I posted an early version of the OpenCV plugin with blob tracking around here... Box2D should follow, but my approach was a bit complicated with memory management... Whatever... Yes, it would be great if we had a bunch of libraries adapted to QC !