我正在构建一个iPhone开发人员可以在他们的项目中包含的SDK。它以编译的“.a”形式提供,没有源代码。我们称我的SDK为“AAA”。
他的项目中的客户(我们称之为“BBB”),除了使用AAA之外,还可以使用名为“CCC”的第三方库 - 它也是预编译的,闭源的。我不卖CCC,它是一家不同的公司。
我的SDK,AAA,可以选择使用CCC来改进产品,使用这些第三方功能。例如,假设CCC是用于加密某些内容的安全SDK。 AAA不要求CCC,但如果客户选择在他们的项目中包含CCC,则会更加安全。
现在这里有一个特别棘手的部分 - CCC库,是纯C代码,由C Structs和C函数组成 - 没有任何面向对象的。
问题是:
答案 0 :(得分:8)
使用dlsym
按功能名称获取C函数指针。如果它能找到它们,它们会在那里。否则他们不会。只需使用RTLD_DEFAULT
作为第一个参数。
编辑:为了一个iOS示例,请参阅Mike Ash的write up of PLWeakCompatibility,特别是关于' Falling Through'的部分。您将看到他检查是否存在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或其他地方,它们只是编译器关于如何格式化数据的指令。因此,您需要在代码中包含实际结构或兼容重述。
答案 1 :(得分:3)
你可以使用弱功能来做到这一点。 在静态库中,声明要使用的所有ccc函数,如下所示:
int cccfunction(void) __attribute__((weak));
不要在你的lib中包含ccc。 由于函数声明为弱,编译器不会抱怨它们的缺失,但是你可以在代码中引用它。 然后,当您将库分发给用户时,给它们一个内部有空ccc函数的.c文件,返回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文件,您将看到
架构xxxxxx的未定义符号: &#34; _cccfunction&#34;,引自: libstaticfirst_universal.a中的_wrapper_cccfunction(wrapper_cccfunction.o)
这就是您需要发送optional.c文件的原因,因此用户编译器不会抱怨未找到的方法。 当用户拥有CCC lib时,他只需删除或注释optional.c文件即可。 在您的库中,您将能够测试CCC库的存在,查看某些控制函数的返回值
编辑 - 旧答案:在意识到你在iOS上之后,下面(和第一个)答案变得无效。动态链接仅适用于OSX。但是,我为使用OSX的人留下旧的答案
OLD ANSWER
我想那个
我认为CCC是一个静态库(如果它是动态的,它更简单)。在这种情况下,AFAIK,你不能做任何事情&#34;自动&#34;,但是使用动态库可以做出类似的妥协
用户项目--include - &gt;你的静态库--include - &gt;动态库 - 可以包括 - &gt; CCC图书馆
创建两个版本的动态库:
实现CCC库的空函数的一个 - &gt;当你调用函数时,它们返回0 / null,你知道库没有实现。你甚至可以使用更聪明的东西(简单的控制功能)
向用户提供第二个动态库的源代码,只需在项目中拖放CCC库,然后在正确的位置移动编译库即可编译。这不是库的源代码(您的代码是在静态部分编译的),而只是您从静态库调用的包装函数的代码。
您的静态库不直接调用CCC库的函数,而只调用始终存在的包装函数(在&#34;空动态库&#34;以及&#34;逐个编译的动态库&#34;)
通过这样做,用户可以替换&#34;空&#34;包含CCC的动态库。 如果动态库是与CCC链接的库,最终项目将使用CCC的功能,否则它不会。
现在,打开LibTests(应该是你的用户的项目),复制/ usr / local / lib /中两个编译的动态库中的第一个,然后执行LibTests:你会看到&#34; 10&#34;在控制台中。 现在,用第二个更改动态库,您将看到&#34; 20&#34;。
这是用户必须做的事情:你用动态的#34;空的&#34;零件。如果用户购买了CCC,您将提供有关如何使用与之捆绑的CCC编译动态组件的指令和代码。构建动态库之后,用户只需切换.dylib文件
答案 2 :(得分:1)
这很棘手,但可以管理。如果您只需要来自CCC的Objective-C类,这将更容易,但您明确表示您需要访问结构/函数。
围绕所有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.
}
答案 3 :(得分:0)
所以这是你问题的要点......
静态库不能由您自己的进程交换...即在链接时我链接到libfoo.1.a现在在运行时此过程无法可靠地交换符号libfoo.2.a
所以你需要绕过这个限制。
最简单的方法是使用动态库和动态链接器......但是您使用的是iOS,因此您无法访问它。
如果您可以运行帮助程序,您可以在第一个进程中更改实际对象,但是您在iOS上并且不能正常工作......
这样叶子试图让一个对象修改自己的内容......哪个代码签名不会让你做...
这样就可以在程序中构建溢出并尝试让它执行:)
实际上它比那简单得多......作为旁注,我写了一个小东西,演示了在运行时的动态绑定......但是你需要一个编译器等......这个策略不能在iOS上运行
https://github.com/gradyplayer/cfeedback
编辑我实际上重新阅读了您的问题,而且我认为您试图解决这个问题要轻松得多......
你可以使用其他标题的#defid来进行条件编译...... 如果有一些地方你必须将这些结构中的一个包含到一个对象中,你只需键入该结构然后只使用它的指针,只要该库具有构造和销毁功能。
答案 4 :(得分:0)
它不完全是运行时,但可以根据CCC许可证解决您的问题。
选项1(编译时)
使用#ifdef创建一个CCC_wrap库,并提供使用和不使用CCC_library进行编译的说明。
对于每个CCC_function,您必须具有等效的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库与源代码一起发货。
答案 5 :(得分:0)
您的问题在编译时更容易解决,因为您的客户已经被要求自己链接所有内容。
由于您的客户应该静态链接您的所有&#34; AAA&#34;代码与&#34; CCC&#34;代码,你的问题可以通过指导你的客户一起编译来解决#34; AAA.a
&#34;使用&#34; AAA_with_CCC_glue.a
&#34;如果他们有&#34; CCC.a
&#34;或&#34; AAA_without_CCC_glue.a
&#34;如果他们不这样做。两个_glue.a
都会实现可能使用CCC.a
的一组函数,区别在于它们是否实际使用它。
要在运行时解决此问题,您至少需要能够拨打dlsym()
(这post让我认为是的,你可以,但它已经过时了)。尝试在应用程序自己的内存中查找您关心的所有CCC.a
函数。