ObjC框架与.mm源文件链接时未定义符号问题咨询
聊聊ObjC++混编时C函数链接失败的那些坑
这个问题本质上就是C和C/Objective-C的符号命名规则不一样在搞事——C为了支持函数重载,会把函数名、参数类型这些信息打包成一个奇奇怪怪的符号名(比如void foo()会变成_Z3foov);而C或者Objective-C里的普通函数,编译出来的符号就是简单的_foo,完全不带额外信息。
第一个疑问:为啥我自己在.mm里把#include框架头包进extern "C"没用?
你肯定以为这样能强制编译器用C的规则处理头里的foo声明,但没生效的原因通常是这几个:
- 预编译头抢了先:如果你的项目预编译头(PCH)里已经提前
#include了这个第三方框架的头,那在你的.mm文件执行extern "C" { #include ... }之前,编译器已经把foo当成C函数处理了,预期要找带后缀的符号。后面再包extern "C"相当于重复声明,编译器会因为冲突忽略后面的,结果还是生成C风格的引用,自然找不到框架里的_foo。 - 其他编译单元拖后腿:如果项目里还有别的.mm或者.cpp文件也包含了这个框架头,而且没加
extern "C"包裹,那这些文件会生成对C++风格foo符号的引用。哪怕你自己的文件声明对了,链接器看到有不匹配的引用,还是会报错。 - 框架头内部有冲突逻辑:要是框架头本身就有
#ifdef __cplusplus这类条件编译,你外部包裹的extern "C"可能和内部逻辑打架,导致foo的声明还是被当成C++函数。
而直接把框架头里的void foo();包进extern "C",相当于从根上给所有编译单元(不管是C、ObjC还是C++)发通知:这个函数就按C的规则来!自然所有地方的符号预期都统一了,链接也就没问题了。
第二个疑问:为啥.m文件也找不到foo()的符号?
按道理说,.m是纯Objective-C文件,编译器默认用C的规则处理函数声明,框架的.m编译出来的foo也是C风格符号,应该能正常链接才对。出现问题大概率是这些细节没注意:
- 漏了包含框架头:你的.m文件里真的
#include了框架的头吗?如果没加,编译器会把foo当成“隐式声明的返回int的函数”,虽然链接时找的还是_foo,但如果框架里的foo是void类型,可能会触发链接器的类型不匹配报错,看起来像是找不到符号。 - 框架没链接到对应的Target:你确认.m文件所在的Target已经把第三方框架加进去了吗?比如Xcode里,有没有把框架拖进Target的
Frameworks and Libraries列表?有时候.mm所在的Target加了框架,但.m所在的Target没加,就会出现这种“有的文件能找到有的找不到”的情况。 - 框架头的声明被条件编译藏起来了:比如框架头里的
foo声明被放在了#ifdef __cplusplus或者其他只有C++才会触发的宏里面,导致.m文件(不会定义__cplusplus)看不到这个声明,编译器还是按隐式声明处理,最终链接出错。 - 遇到了符号冲突:如果你的项目里其他地方也定义了一个叫
foo的函数,链接器会因为符号重复报错,看起来像是找不到框架的foo。
不想改第三方框架的小妙招
直接改人家的框架头毕竟不太优雅,你可以自己写个适配头文件,比如叫MyFrameworkWrapper.h:
#ifdef __cplusplus extern "C" { #endif #include "第三方框架的头文件.h" #ifdef __cplusplus } #endif
然后所有需要用这个框架的文件,都#include这个适配头,而不是直接包含原框架头。这样就能保证所有编译单元都用正确的规则处理foo的声明,完美绕开修改源码的问题。
内容的提问来源于stack exchange,提问作者AceOfTheBase
相关产品推荐
相关产品推荐

