Обнаружение и использование дополнительной внешней библиотеки C во время выполнения в Objective-C

Я создаю SDK, который разработчики iPhone могут включать в свои проекты. Он поставляется как скомпилированный ".a", без исходного кода. Позвоните в мой SDK "AAA".

Клиент в своем проекте (пусть его называют "BBB" ), помимо использования AAA, может также использовать стороннюю библиотеку под названием "CCC", которая также поставляется с предварительно скомпилированным закрытым исходным кодом. Я не продаю CCC, это другая компания.

Мой SDK, AAA, может дополнительно использовать CCC для улучшения продукта, используя эти сторонние функции. Например, скажем, CCC - это SDK безопасности для шифрования. AAA не требует CCC, но будет более защищен, если клиент также захочет включить CCC в свой проект.

Теперь вот лишняя сложная часть - библиотека CCC - это чистый C-код, сделанный из функций C Structs и C - ничего объектно-ориентированного.

Проблемы:

  • Как я могу скомпилировать свой SDK AAA для использования функций/структур из CCC, не включая CCC в моем проекте (не разрешено законом и не хочет обновляться с обновлениями версий).
  • Как я могу определить, имеет ли клиент CCC в своем проекте, использовать эти дополнительные функции только в том случае, если они доступны?

Ответ 1

Используйте dlsym, чтобы получить указатели на функции C по имени функции. Если они найдут их, они там. В противном случае это не так. Просто используйте RTLD_DEFAULT в качестве первого параметра.

EDIT: опустившись на iOS-пример, см. Mike Ash напишите PLWeakCompatibility, в частности раздел "Падение". Вы увидите, что он проверяет наличие objc_loadWeakRetained (вызов времени выполнения, связанный со слабыми ссылками). В возрасте до 5 лет это и его версия называет реальным. В возрасте до 4 лет это не так, его версия делает что-то другое.

EDIT2: пример кода:

Пример 1:

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

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

Выходы 0x0. Пример 2:

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

void someFunc()
{

}

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

Выводит адрес, отличный от 0x0.

Пример 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();
    }
}

Вывод Hi!.

Структуры не имеют присутствия в .a или где-либо еще во время выполнения, это всего лишь инструкции для компилятора о том, как форматировать данные. Таким образом, вам нужно будет включить в свой код либо фактические структуры, либо совместимое их повторение.

Ответ 2

Вы можете сделать это, используя слабые функции. В вашей статической библиотеке объявите все функции ccc, которые вы хотите использовать следующим образом:

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

Не включайте ccc в lib. Поскольку функции объявлены слабыми, компилятор не будет жаловаться на их отсутствие, однако вы сможете ссылаться на него в своем коде. Затем, когда вы распространяете библиотеку для своих пользователей, дайте им файл .c с пустыми функциями ccc внутри, возвращая 0/null. Это необходимо, когда ccc lib недоступен.
Пользователь должен удалить этот файл, если импортирована библиотека CCC.

ПОСМОТРЕТЬ в этом проекте

выполните IOSLibraries и посмотрите журнал. При первом выполнении вы увидите в журнале

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

если вы заходите в файл optional.c и комментируете cccfunction(), вы увидите в журнале

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)

Если вы удалите файл ccc lib и файл optional.c, вы увидите

Undefined символы для архитектуры xxxxxx: "_cccфункция", на которую ссылаются:     _wrapper_cccфункция в libstaticfirst_universal.a(wrapper_cccfunction.o)

Вот почему вам необходимо отправить файл optional.c, поэтому компилятор пользователя не будет жаловаться на не найденные методы. Когда пользователь имеет CCC lib, он может просто удалить или прокомментировать файл optional.c. В вашей библиотеке вы сможете проверить наличие библиотеки CCC, обращаясь к возвращаемому значению некоторых функций управления.

РЕДАКТИРОВАТЬ - старый ответ: после осознания того, что вы находитесь на iOS, нижний (и первый) ответ стал недействительным. Динамическое связывание работает только с OSX. Тем не менее, я оставляю старый ответ для лиц, использующих OSX

OLD ANSWER
Я думаю, что

Я предполагаю, что CCC является статической библиотекой (если она более динамична). В этом случае AFAIK вы ничего не можете сделать "автоматически", но хорошим компромиссом может быть что-то вроде этого, используя динамические библиотеки

пользовательский проект --include → ваша статическая библиотека --include → динамическая библиотека --can include → библиотека CCC

создать две версии динамической библиотеки:

  • тот, который реализует, например, пустые функции библиотеки CCC → , когда вы вызываете функцию, они возвращают 0/null, и вы знаете, что библиотека не реализована. Вы даже можете использовать что-то умнее (простая функция управления)

  • предоставить пользователям исходный код второй динамической библиотеки, которую они могут скомпилировать, просто выполнив перетаскивание библиотеки CCC внутри проекта, а затем переместив скомпилированную библиотеку в нужное место. Это не исходный код вашей библиотеки (ваш код скомпилирован в статической части), а только код функций обертки, который вы вызываете из своих статических библиотек.

  • ваша статическая библиотека не вызывает непосредственно функции библиотеки CCC, но только функции-оболочки, которые всегда существуют (как в "пустой динамической библиотеке", так и в "динамической библиотеке скомпилированных по-умолчанию" )

Таким образом, пользователь может заменить "пустую" динамическую библиотеку той, которая включает CCC. Если динамическая библиотека является той, которая связана с CCC, последний проект будет использовать функцию CCC, иначе это не будет.

Посмотрите приведенный пример:

  • Проект LibTests реализует lib libstaticlib.a и вызывает его функцию "usedynamic (int)"
  • libstaticlib.a реализует динамическую библиотеку libdynamic1 и вызывает ее функцию "firstfunction (int)"
  • libdynamic1 имеет две разные копии: у одной есть первая функция(), которая возвращает пройденное число, другая возвращает номер * 2

теперь откройте LibTests (это должен быть проект вашего пользователя), скопируйте первую из двух скомпилированных динамических библиотек в /usr/local/lib/, затем выполните LibTests: вы увидите "10" в консоли. Теперь измените динамическую библиотеку на вторую, и вы увидите "20".

Это то, что пользователь должен делать: вы продаете библиотеку с динамическим "пустым" компонентом. Если пользователь купил CCC, вы даете инструкцию и код о том, как скомпилировать динамический компонент с CCC в комплекте с ним. После создания динамической библиотеки пользователь должен просто переключить файл .dylib

Ответ 3

Это сложно, но управляемо. Если вам нужны только классы Objective-C из CCC, это было бы проще, но вы специально сказали, что вам нужен доступ к структурам/функциям.

  • Создайте класс прокси вокруг всех функций CCC. Все функциональные возможности CCC должны быть инкапсулированы в методы экземпляра прокси. Все типы CCC должны быть адаптированы к вашим собственным типам. Никакая часть CCC не может быть включена во что-либо вне файла реализации прокси-класса. Я буду называть этот класс MyCCCProxy.

  • Никогда не ссылайтесь непосредственно на объект класса MyCCCProxy. Подробнее об этом позже.

  • Создайте свою библиотеку без ссылки MyCCCProxy.m

  • Создайте вторую статическую библиотеку только с MyCCCProxy.

  • Клиенты, у которых есть CCC, должны будут связать AAA, CCC и CCCProxy. Клиенты, у которых нет CCC, свяжут только AAA.

Трудный шаг - номер 2.

В большинстве случаев, когда вы создаете экземпляр класса, вы используете:

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

Это напрямую ссылается на объект класса для MyCCCProxy и вызывает проблемы с привязкой пользователей, если MyCCCProxy не включен.

Вместо этого, если вы вместо этого напишите:

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

Это не ссылается непосредственно на объект класса, он динамически загружает объект класса. Если MyCCCProxy не существует как класс, то NSClassFromString возвращает Nil (версия класса Nil). [Nil alloc] возвращает Nil. [nil init] возвращает Nil.

MyCCCProxy *aCCCProxy = [[NSClassFromString(@"MyCCCProxy") alloc] init];
if (aCCCProxy != nil) {
    // I have access to CCC through MyCCCProxy.
}

Ответ 4

Итак, вот суть вашей проблемы...

статическая библиотека не может быть заменена вашим собственным процессом... это время связи, которое я связывал с libfoo.1.a, во время выполнения этот процесс не может надежно поменять символы в libfoo.2. а

поэтому вам нужно обойти это ограничение.

проще всего использовать динамическую библиотеку и динамический компоновщик... но вы используете iOS, чтобы у вас не было доступа к этому.

если вы могли бы запустить помощник, вы могли бы изменить фактические объекты в первом процессе, но вы на iOS, и это не сработает...

поэтому остается пытаться заставить объект изменить собственное содержимое..., что подписание кода не позволит вам сделать...

чтобы у вас возникло переполнение в вашу программу и попытка заставить его выполнить:)

на самом деле это намного проще, чем это...

  • создать буфер
  • заполнить его фрагментом кода
  • установить фрейм фрейма uo (требуется немного asm)
  • настроить аргументы для функции, которую вы планируете вызывать
  • запустить буфер + смещение к вашему методу
  • прибыль

как побочная заметка Я написал небольшую вещь, которая демонстрирует динамическое связывание во время выполнения... но вам нужно иметь компилятор и т.д.... эта стратегия не будет работать на iOS

https://github.com/gradyplayer/cfeedback

EDIT Я действительно перечитываю вашу проблему, и это намного проще, чем я думал, что вы пытаетесь решить...

вы можете использовать все, что было # def'ed другими заголовками, чтобы выполнить условную компиляцию... и если есть места, в которых вы должны включить одну из этих структур в объект, вы можете просто набрать структуру, тогда используйте только указатели на нее, если библиотека имеет функции построения и уничтожения.

Ответ 5

Это не совсем время исполнения, но может решить вашу проблему в зависимости от лицензии CCC.

Вариант 1 (время компиляции)

Создайте библиотеку CCC_wrap С#ifdef и дайте инструкции для ее компиляции с и без CCC_library.

Для каждой функции CCC необходимо иметь эквивалентную CCC_function_wrap

Если HAVE_CCC == 1 функция обертки должна вызывать библиотеку CCC, в противном случае ничего не делать или возвращать ошибку.

Создайте дополнительную функцию, чтобы узнать, как была скомпилирована ваша библиотека.

int CCC_wrap_isfake(void) {
#if HAVE_CCC
    return 0;
#else
    return 1;
#endif
}

Вариант 2 (готовый к бинарному)

Создайте две новые библиотеки, CCC_wrap и CCC_wrap_fake

Обе библиотеки должны содержать все функции/классы, необходимые для запуска программы, но в поддельной библиотеке все функции ничего не будут делать, просто return 0;

Чем вы создадите дополнительную функцию CCC_wrap_isfake

CCC_wrap_fake:

int CCC_wrap_isfake(void) { return 1;}

CCC_wrap:

int CCC_wrap_isfake(void) { return 0;}

Теперь вы знаете, работает ли ваш код с реальным оберткой или поддельным.

В момент компиляции вам нужно установить флаг, чтобы определить, как ваша библиотека будет связана с вашим клиентским программным обеспечением.

CCC_wrap_fake:

LDFLGAS=-lCCC_wrap_fake

CCC_wrap:

LDFLGAS=-lCCC_wrap -lCCC

Оба варианта должны правильно связываться.

О требованиях к лицензии

Если вы доставляете исходный код библиотеки CCC_wrap, ваш клиент сможет обновить библиотеку CCC без доступа к вам основного источника.

В обоих случаях вам не потребуется отправлять библиотеку CCC вместе с исходным кодом.

Ответ 6

Ваша проблема более легко решается во время компиляции, так как ваши клиенты уже должны сами связывать все.

Поскольку ваш клиент должен статически связать весь ваш код "AAA" вместе с кодом "CCC", ваша проблема может быть решена путем указания вашему клиенту скомпилировать "AAA.a" либо с помощью "AAA_with_CCC_glue.a", если они имеют "CCC.a" или "AAA_without_CCC_glue.a", если они этого не делают. Оба _glue.a будут реализовывать набор функций, которые потенциально используют CCC.a, разница в том, что они действительно используют его.

Чтобы решить эту проблему во время выполнения, вам понадобится хотя бы позвонить dlsym() (этот пост заставило меня подумать, что да, вы можете, но Это старое). Попробуйте найти все функции CCC.a, которые вам интересны в вашей собственной памяти приложения.