Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am building an SDK that iPhone developers can include in their projects. It is delivered as a compiled ".a", without source code. Let's call my SDK "AAA".

The customer in his project (let's call it "BBB"), in addition to use AAA, may also use a 3rd party library called "CCC" - which also comes pre-compiled, closed-source. I do not sell CCC, it's a different company.

My SDK, AAA, can optionally use CCC to improve the product, using those 3rd party features. For example, let's say CCC is a security SDK to encrypt something. AAA does not require CCC, but will be more secured if the customer chooses to include CCC in their project as well.

Now here is an extra tricky part - the CCC library, is pure C code, made of C Structs and C functions - nothing object-oriented about it.

The issues are:

  • How can I compile my AAA SDK to use functions/structs from CCC, without including CCC in my project (not legally allowed to, and don't want to keep up to date with version updates).
  • How can I detect if the customer has CCC in his project, to use those extra features only if available?
like image 787
Nathan H Avatar asked Apr 16 '14 07:04

Nathan H


2 Answers

You can do it using weak functions. In your static library, declare all function of ccc that you want to use like this:

int cccfunction(void) __attribute__((weak));

Don't include ccc in you lib. Since the functions are declared as weak, the compiler wouldn't complain about their absence, however you will be able to reference it in you code. Then, when you distribute the library to your users, give them a .c file with empty ccc functions inside, returning 0/null. This is necessary when the ccc lib is not available.
The user must delete this file if the CCC library is imported.

LOOK at this project

execute IOSLibraries and look at the log. At the first execution, you will see in the log

CCC not found   <--- this line is printed by libstatic (your library)

if you go in the optional.c file and comment the cccfunction(), you will see in the log

Executing a function of CCC  <--- this line is printed by libccc
CCC has been found and the function has been executed  <--- this line is printed by libstatic (your library)

If you remove both the ccc lib and the optional.c file you will see

Undefined symbols for architecture xxxxxx: "_cccfunction", referenced from: _wrapper_cccfunction in libstaticfirst_universal.a(wrapper_cccfunction.o)

This is the reason why you need to ship the optional.c file, so the user compiler won't complain about not found methods. When the user has the CCC lib, he can simply delete or comment the optional.c file. In your library you will be able to test for the presence of the CCC library looking at the return value of some control functions

EDIT - old answer: after realizing that you are on iOS, the below (and first) answer became not valid. Dynamic linking works only on OSX. However, I leave the old answer for persons using OSX

OLD ANSWER
I think that

I assume that CCC is a static library (if it's dynamic it's simpler). In this case, AFAIK, you can do nothing "automagically", but a good compromise can be something like this, using Dynamic libraries

user project --include--> your static library --include--> a dynamic library --can include–-> the CCC library

create two version of the dynamic library:

  • one that implements, for example, empty functions of the CCC library -> when you call the function, they returns 0/null and you know that the library is not implemented. You can even use something smarter (a simple control function)

  • give to the users the source code of a second dynamic library, that they can compile simply by doing drag-drop of the CCC library inside the project, and then moving the compiled library in the right place. This is not the source code of your library (your code is compiled in the static part), but only the code of the wrapper functions that you call from your static libraries.

  • your static library don't call directly functions of the CCC library, but only wrapper functions that always exists (in both the "empty dynamic library" and in the "compiled-by-user dynamic library")

By doing this, the user can replace the "empty" dynamic library with the one that include CCC. If the dynamic library is the one with the CCC linked, the final project will use the function of CCC, otherwise it won't.

Look at the attached example:

  • LibTests project implements the lib libstaticlib.a and call its function "usedynamic(int)"
  • libstaticlib.a implements the dynamic library libdynamic1 and call its function "firstfunction(int)"
  • libdynamic1 has two different copies: one has a firstfunction() that returns the number passed, the other returns the number*2

now, open LibTests (that should be project of your user), copy the first of the two compiled dynamic libraries in /usr/local/lib/ , then execute LibTests: you will see "10" in the console. Now, change the dynamic library with the second, and you will see "20".

This is what the user have to do: you sell the library with a dynamic "empty" component. If the user has bought CCC, you give instruction and code on how to compile the dynamic component with CCC bundled with it. After the dynamic library has been built, the user has simply to switch the .dylib file

like image 88
LombaX Avatar answered Oct 23 '22 10:10

LombaX


This is tricky, but manageable. If you only needed Objective-C classes from CCC, this would be easier, but you specifically said you need access to structs/functions.

  1. Build a proxy class around all the CCC functionality. All CCC functionality must be encapsulated into instance methods of the proxy. All CCC types must be adapted into your own types. No part of CCC can be included in the anything outside the proxy class's implementation file. I will call this class MyCCCProxy.

  2. Never directly reference the MyCCCProxy class object. More on this later.

  3. Build your library without linking MyCCCProxy.m

  4. Build a second static library with only MyCCCProxy.

  5. Customers who have CCC will need to link AAA, CCC, and CCCProxy. Customers who don't have CCC will only link AAA.

The tricky step is number 2.

Most of the time when you create an instance of a class, you use:

MyCCCProxy *aCCCProxy = [[MyCCCProxy alloc] init];

This directly references the class object for MyCCCProxy and will cause user linking issues if MyCCCProxy is not included.

Instead, if you instead write:

MyCCCProxy *aCCCProxy = [[NSClassFromString(@"MyCCCProxy") alloc] init];

This does not directly reference the class object, it loads the class object dynamically. If MyCCCProxy does not exist as a class, then NSClassFromString returns Nil (the class version of nil). [Nil alloc] returns nil. [nil init] returns nil.

MyCCCProxy *aCCCProxy = [[NSClassFromString(@"MyCCCProxy") alloc] init];
if (aCCCProxy != nil) {
    // I have access to CCC through MyCCCProxy.
}
like image 39
Jeffery Thomas Avatar answered Oct 23 '22 10:10

Jeffery Thomas