Export an `OBJC_CLASS` from one static lib as part

2019-05-16 17:23发布

I want to create a static library (actually, a framework, but I know how to do that part) that bundles up code from, among other things, another static library. However, the OBJC_CLASS exports from the original library end up as undefined symbols.

For example, in Xcode 5.1.1 (using default settings/choices at every step, unless otherwise specified):

  • Create a new "iOS Framework & Library Cocoa Touch Static Library" project named LibA.
    • Build (for either simulator or a real device, doesn't matter).
  • Create another new "iOS Framework & Library Cocoa Touch Static Library" project named LibB.
    • Drag libLibA.a from the LibA products to the Frameworks folder in the LibB project tree.
    • Drag LibA from the include directory next to the static lib to the top level of the LibB project tree.
    • Edit LibB.h as shown below.
    • Build (same target as before).
  • Create a new "iOS Application" (any type) project named AppC.
    • Drag libLibB.a from the LibB products to the Frameworks folder in the AppC project tree.
    • Drag LibB from the include directory to the top level.
    • Drag LibA from the first project's include directory to the top level.
    • Verify that LibA appears in the Link Binary With Libraries phase.
    • In any method of any class the wizard generated (e.g., -[MasterViewController awakeFromNib]), add (void)[[LibB alloc] init].
    • At the top of the .m file you just edited, add #import "LibB.h".
    • Build.

Here's the LibB.h promised above:

#import <Foundation/Foundation.h>
#import "LibA.h"
@interface LibB: LibA
@end

I get the following error:

Undefined symbols for architecture i386:
  "_OBJC_CLASS_$_LibA", referenced from:
      _OBJC_CLASS_$_LibB in libLibB.a(LibB.o)
  "_OBJC_METACLASS_$_LibA", referenced from:
      _OBJC_METACLASS_$_LibB in libLibB.a(LibB.o)
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Looking at the files, the problem is obvious:

$ nm -g libLibB.a
         U _OBJC_CLASS_$_LibA
0000031c S _OBJC_CLASS_$_LibB
         U _OBJC_METACLASS_$_LibA
00000308 S _OBJC_METACLASS_$_LibB
         U _OBJC_METACLASS_$_NSObject
         U __objc_empty_cache

The symbols for _OBJC_CLASS_$_LibA and _OBJC_METACLASS_$_LibA are exported as undefined.

I can reference methods, C functions and structs, globals, etc. from LibA. Even categories on Foundation objects (as long as I do the category-dummy trick). It's only the class and metaclass objects that I can't figure out how to export.

Here's what I've tried to fix it:

  • Turn off Dead Code Stripping (in all three projects).
  • Add -ObjC as an extra linker flag (in all projects). (This makes no sense for static libs, and all it does is give you a warning error telling you exactly that, but everyone suggests it to me.)
  • Create an "Exported Symbols File" (for LibB). (This also only makes sense for dynamic libs.)
  • Pass ${PROJECT_DIR}/libLibA.a as an "Other Linker Flags" (for LibB) instead of adding libLibA as a framework (in case -lLibA is processed differently from libLibA.a).

What I've tried that I still think may be on the right path, but I'm not sure:

  • Try to figure out appropriate libtool options that have no corresponding settings in Xcode. (I can wrap it in a Makefile, or and Xcode custom build step, if necessary.)
  • Enable "Perform Single-Object Prelink", then add ${PROJECT_DIR}/libLibA.a to "Prelink libraries". I get warnings about duplicate symbols and then success but with an empty libLibB.a, so obviously there's something else I need to do. I've done this with .dylibs and dynamic Frameworks on OS X, and there wasn't anything else I needed to do there… but never with static libs.

Workarounds that I know about (and I'll use one of these if there's no real solution):

  • Require that anyone who wants to use LibB also has to add LibA to their project. And, in particular, the pre-built copy of LibA that we provide.
  • Distribute LibB as source to be included in your project, instead of a static lib and headers.
  • Manually ar libLibA.a and LibB.o, then ranlib like it's 1999 (although the docs say this doesn't work, it seems to).

(None of these are too terrible for my simple test project, but in real life, this is not an open source project, that LibA is actually 80 different libs from 3 different projects, and some of the LibA code builds fat armv7/armv7s (which means ar doesn't work on it…), and we're planning to do the usual hack of lipo'ing together the simulator and native builds and making a framework out of them, all of which makes things more of a problem.

2条回答
甜甜的少女心
2楼-- · 2019-05-16 18:10

Adding this to Other Linker Flags appears to work

-force_load $(CONFIGURATION_BUILD_DIR)/libLibA.a
查看更多
爷的心禁止访问
3楼-- · 2019-05-16 18:13

I think I may have solved it with single-object prelink (basically this means it does an ld -r to build a giant object file, then passes that to libtool), although I'm still not sure, and I don't love the solution. So, I will post what I've got as an answer, but hope someone else comes along with a better answer.

To get single-object prelink to work, you need to (in LibB):

  • Add libLibA.a as a Framework.
  • Make sure it does not appear in the Link Binary With Libraries build phase.
  • Set "Dead Code Stripping" to No.
  • Set "Don't Dead-Strip Inits and Terms" to Yes.
  • Set `Perform Single-Object Prelink" to Yes.
  • Set "Prelink libraries" to ${PROJECT_DIR}/libLibA.a
  • Set "Preserve Private External Symbols" to Yes.

(The second step is what I was doing wrong earlier…)

Unfortunately, this seems to break the dependency rules completely, so that every build recompiles every .m (and .pch) that's part of the target, even if nothing has changed.

Other than that annoyance, this seems to work for both AppC and my real project just fine.

AppC does not need "Preserve Private External Symbols"; my real project does. I believe this is because one of the third-party libraries does an ld -r with an empty -exported_symbols_list explicitly to "transform all symbols to private_extern. Otherwise, class objects don't end up that way. However, I'm not 100% sure I understand this one.

查看更多
登录 后发表回答