Detect and Use Optional External C Library at Runtime in Objective-C

Detect and use optional external C library at runtime in Objective-C

Use dlsym to get the C function pointers by function name. If it can find them, they're there. Otherwise they're not. Just use RTLD_DEFAULT as the first parameter.

EDIT: having cast around for an iOS example, see Mike Ash's write up of PLWeakCompatibility, particularly the section on 'Falling Through'. You'll see he checks whether objc_loadWeakRetained (a runtime call related to weak references) is present. Under 5+ it is and his version calls the real one directly. Under 4 it isn't so his version does something else instead.

EDIT2: sample code:

Sample 1:

#import <Foundation/Foundation.h>
#include <dlfcn.h>

int main(int argc, char *argv[])
{
@autoreleasepool
{
NSLog(@"%p", dlsym(RTLD_DEFAULT, "someFunc"));
}
}

Outputs 0x0. Sample 2:

#import <Foundation/Foundation.h>
#include <dlfcn.h>

void someFunc()
{

}

int main(int argc, char *argv[])
{
@autoreleasepool
{
NSLog(@"%p", dlsym(RTLD_DEFAULT, "someFunc"));
}
}

Outputs an address other than 0x0.

Sample 3:

#import <Foundation/Foundation.h>
#include <dlfcn.h>

void someFunc()
{
NSLog(@"Hi!");
}

int main(int argc, char *argv[])
{
@autoreleasepool
{
void (* func)();
func = dlsym(RTLD_DEFAULT, "someFunc");
func();
}
}

Outputs Hi!.

Structs have no presence in a .a or elsewhere at runtime, they're just instructions to the compiler on how to format data. So you'll need to include either the actual structs or a compatible restatement of them in your code.

ObjC: How to compile static library that includes optional classes that depend on a third party library

The best practice is always to have the final binary link all the static libs required. You should never bundle one static library into another. You should absolutely never bundle a well-known (i.e. open-source) static library into a static library you ship. This can create incredible headaches for the final consumer because they can wind up with multiple versions of the same code. Tracking down the bugs that can come from this is insanely difficult. If they're lucky, they'll just get confusing compiler errors. If they're unlucky, their code will behave in unpredictable ways and randomly crash.

Ship all the static libraries separately. Tell your clients which ones they need to link for various configurations. Trying to avoid this just makes their lives difficult.

Some other discussions that may be useful:

  • Duplicate Symbol Error: SBJsonParser.o? (Example of a customer who ran into a vendor doing this to him)
  • Linking static libraries, that share another static library
  • Why don't iOS framework dependencies need to be explicitly linked to a static library project or framework project when they do for an app project?

The -ObjC flag should be preventing the automatic stripping of ClassA entirely, whether its used or not (see TN1490 for more details).

If ClassA is never used except in certain circumstances and you want to save space, you should probably move ClassA into its own static library. Or use #ifdef to conditionally compile it.

Alternately, you can remove the -ObjC flag and use -force_load to individually load any category-only compile units (which is the problem -ObjC is used to address).

Static Library using frameworks in specific projects

First of all, you are right in that a static library cannot include any framework nor other static libraries, it is just the collection of all object files (*.obj) that make up that specific static library.

Does anyone know how to compile only the required source files per project?

The linker will by default only link in object files from the static library that contain symbols referenced by the application. So, if you have two files a.m and b.m in your static library and you only use symbols from a.m in your main program, then b.o (the object file generated from b.c) will not appear in your final executable. As a sub-case, if b.m uses a function/class c which is only declared (not implemented), then you will not get any linker errors. As soon as you include some symbols from b.m in your program, b.o will also be linked and you will get linker errors due to the missing implementation of c.

If you want this kind of selection to happen at symbol rather than at object level granularity, enable dead code stripping in Xcode. This corresponds to the gcc option -Wl,-dead_strip (= linker option -dead_strip in the Build settings Info pane for your project). This would ensure further optimization.

In your case, though, as you correctly say, it is the use of the "-ObjC" linker flag that defeats this mechanism. So this actually depends on you. If you remove the -Objc flag, you get the behavior you like for free, while losing the stricter check on selectors.

And prevent from all frameworks having to be included in all projects that use my static library?

Xcode/GCC support an linking option which is called "weak linking", which allows to lazily load a framework or static library, i.e., only when one of its symbols is actually used.
"weak linking" can be enabled either through a linker flag (see Apple doc above), or through Xcode UI (Target -> Info -> General -> Linked Libraries).

Anyhow, the framework or library must be available in all cases at compile/link time: the "weak" option only affects the moment when the framework is first loaded at runtime. Thus, I don't think this is useful for you, since you would need anyway to include the framework in all of your projects, which is what you do not want.

As a side note, weak_linking is an option that mostly make sense when using features only available on newer SDK version (say, 4.3.2) while also supporting deployment on older SDK versions (say, 3.1.3). In this case, you rely on the fact that the newer SDK frameworks will be actually available on the newer deployment devices, and you conditionally compile in the features requiring it, so that on older devices they will not be required (and will not produce thus the attempt at loading the newer version of the framework and the crash).


To make things worse, GCC does not support a feature known as "auto-linking" with Microsoft compilers, which allow to specify which library to link by means of a #pragma comment in your source file. This could offer a workaround, but is not there.


So, I am really sorry to have to say that you should use a different approach that could equally satisfy your needs:

  1. remove the -ObjC flag;

  2. split your static library in two or more parts according to their dependencies from external frameworks;

  3. resort to including the source files directly.

Programatically determine shared libraries in use by running application

Since it looks like you're using something UNIX-y, just use dlopen instead of dynamically linking your driver app against the missing symbol.

Full sequence is:

  1. iterate over all submitted .so library filenames somehow (maybe you have one directory with studentname.so or something)
  2. load each library
  3. get the entry point function
  4. call it
  5. unload library (optional, I guess)

like so:

void *lib = dlopen(filename, RTLD_LOCAL);
void *libfun = dlsym(lib, "doAction");
if (libfun == NULL)
cout << "student failed by not providing doAction() in " << filename << endl;
else {
void (*doAction)(void) = (void (*)(void)) libfun;
// no, I can't remember the correct syntax for casting to function pointer
cout << "calling " << filename << ":doAction()" << endl;
doAction();
// is there some way to tell if it succeeded?
cout << "unloading " << filename << endl;
dlclose(lib);
}

Notes:

  • if the interface is the same in each case (ie, void (*)()), you could make this configurable by directory name and symbol name, and it'd work for more than one test
  • in fact, if the interface is NOT what you expect, the function pointer cast will do horrible things, so careful with this
  • finally, if the student used C++, their function name symbol will be mangled. Tell them to declare the entry-point as extern "C" void doAction() to avoid that.
  • the RTLD_LOCAL flag should stop anything in one student's library interfering with another (if you don't unload), but there are other flags it may be sensible to add
    • specifically, RTLD_NOW will cause dlopen to fail if the student lib has an unresolved external reference it can't figure out (so you can handle it gracefully, by failing them): otherwise your program may just crash when you call doAction.

Although I think the above is better than the solution you're directly asking for help with, I did also find a reference to dl_iterate_phdr while double-checking the docs. If you're on Linux specifically, and if the dl_phdr_info.dlpi_name is actually the filename ... you might be able to get it that way.

I still think it's much uglier, though.

Binding protocol fails to pass conformsToProtocol

Okay, think I might have cracked it. I added the Adopts attribute to my class (which is vaguely mentioned in various places in the Xamarin documentation, but not really suggested as something to do when creating a binding library). So now I have this:

[Adopts("JRCaptureDelegate")] 
public class SignIn : JRCaptureDelegate
{
// overrides for each method
}

And it now passes the conformsToProtocol check. I'm not sure why this isn't automatic since I'm implementing the JRCaptureDelegate interface/protocol.



Related Topics



Leave a reply



Submit