directory

  1. The difference between dynamic and static libraries
  2. Creating a dynamic library
  3. Using dynamic Libraries

3.1. Add as a dependent library – Load at startup 3.2. Load at runtime 4. Inject the dynamic library 5.yololib

preface

When it comes to dynamic libraries, we have to mention static libraries. A static library can be thought of as a block of code with a specific function. If a static library is referenced in an app, the static library is copied directly into the app’s executable (i.e. Mach -O) at compile time. Using static libraries can result in large Mach-O files, which directly affect app startup time and the amount of memory used during execution.

1. The difference between dynamic and static libraries

Static libraries end with a.a suffix, and dynamic libraries can end with.dylib or.framework. All system libraries belong to dynamic libraries, and framework is generally used as a dynamic library in iOS.

The following are two official charts of Apple, which show the memory usage of the app after startup, and vividly illustrate the difference between static library and dynamic library

Apps that use static librariesApps that use dynamic libraries

When using static linker to link to an app, static libraries are loaded in their entirety into the app’s Mach-O file (Application File) as part of the Mach-O file. Dynamic libraries are not added to the Mach-O file. This can effectively reduce the size of the Mach-O file. If the app uses the dynamic library as its dependent library, a reference to the dynamic library is added to the Mach-O file; If the app loads the dynamic library dynamically at runtime, no reference to the dynamic library is added to the Mach-O file.

When using the app, both static and dynamic libraries are loaded into memory. When multiple apps use the same library, if the library is a dynamic library, only one copy of the dynamic library will exist in memory because the dynamic library can be shared by multiple app processes (Errata: Only the dynamic inventory of the system library is in one copy, but there are still multiple copies of the dynamic library of app itself. The use of the dynamic library by personal APP does not reduce the size of IPA, but only reduces the size of Macho. If it was a static library, there would be multiple copies, since there would be one copy in each app’s Mach-O file. Compared to static libraries, using dynamic libraries can reduce the size of memory occupied by the app.

In addition, using dynamic library can shorten the startup time of app. The reason is that when using dynamic libraries, the app’s Mach-O files are always small; The dynamic library that your app depends on May already exist in memory (and other launched apps depend on the dynamic library), so it doesn’t need to be reloaded.

2. Create a dynamic library

As mentioned earlier, there are two types of dynamic libraries, ending with the. Framework and. Dylib suffixes, and they are usually called framework and Shared libraries. The Framework is essentially packaged from a Shared Library with a header file and other resource files.

The following uses creating the LibPersonFramework as an example

  1. To create a new project, selectiOS -> Cocoa Touch Framework

  1. Implements the framework and specifies the external header file

Define the header file libperson.h

#import <Foundation/Foundation.h>

@interface LibPerson : NSObject

@property (nonatomic, copy) NSString *name ;

- (void)watch;

- (void)eat;

@end
Copy the code

The specifiedLibPersonFramework.handLibPerson.hIs the external header file

Specify the schema for the framework, in this case the Generic iOS Device model, and then build to create a Generic Mach-O file containing both arm64 and ARM_V7 architectures. If the emulator is selected, an X86_64 architecture Mach-O file is created.

It is important to note that the architecture of the App and the framework it depends on must be compatible, meaning that when creating executables, they must be either real machines or emulators. Of course, you can create Framwork in real and emulator mode, and then use the lipo command to merge the mach-O files of the same name inside the two frameworks into a generic Mach-O file, so that the framework can be used correctly regardless of the architecture of the App.

3. Use dynamic libraries

There are two ways to use dynamic libraries. One is to add the dynamic library as a dependent library, which will load the dynamic library at project startup, and the other is to use Dlopen to load the dynamic library at runtime. The difference between the two methods is the timing of loading the dynamic library.

The first method is usually used on iOS. The second method is usually used on MAC development. If you use this method on iOS, it will not be available on the App Store.

3.1. Add as a dependent library – Load at startup

Create a new projectDylibDemoAnd introducing LibPersonFramework framework, in the main. M file calls this method in the framework

. This time, the app project has LibPersonFramework framework produced dependence, for system framework, to this point is ok, because the system framework has been installed on the iphone in advance. For a custom framework, follow the steps below to copy the framework into the app installation package.

Finally run, call successful!

2018-06-04 16:32:09.076551+0800 DylibDemo[17900:700462] Wang is watching TV! 2018-06-04 16:32:09.078597+0800 DylibDemo[1790:700462] Wang is eating!Copy the code

3.2. Runtime loading

Loading the dynamic library at run time means that there is no need to introduce the dynamic library in the project. Instead, the dlopen() function is used to load the dynamic library in the code. After the call is complete, the dlclose() function is called the same number of times to close the dynamic library. In addition to dlopen() and dlclose(), there is also a dlsym() function to get the address of the corresponding data or function based on the symbol passed in. In this case, the Runtime mechanism is used instead of the DLsym () function. (DLSYm () is generally used in C or C ++)

1. Create a new projectDylibDemo-RuntimeTo add the header file of the called libraryLibPerson.h(there is no need to add LibPersonFramework framework)

2. In the main. M file loading and call LibPersonFramework framework

void loadWhenRunTime(){ // Open the library. NSString *bundlePath = [[NSBundle mainBundle]pathForResource:@"LibPersonFramework" ofType:nil]; void* lib_handle = dlopen([bundlePath UTF8String], RTLD_LOCAL); if (! lib_handle) { NSLog(@"[%s] main: Unable to open library: %s\n", __FILE__, dlerror()); exit(EXIT_FAILURE); } Class class_person = objc_getClass("LibPerson"); LibPerson *person = [class_person new]; person.name = @"wang"; [person watch]; [person eat]; // Close the library. if (dlclose(lib_handle) ! = 0) { NSLog(@"[%s] Unable to close library: %s\n", __FILE__, dlerror()); exit(EXIT_FAILURE); }}Copy the code

The dlopen() function takes two arguments, path, which indicates the path to the dynamic library’s Mach-o file, and mode, which can contain multiple identifiers, such as RTLD_LAZY and RTLD_NOW, which indicate when the symbol in the dynamic library is loaded. RTLD_GLOBAL and RTLD_LOCAL represent the visibility of symbol. (For details, run the man dlopen command.)

In the code above, path specifies that the dynamic library is in the generated app package named LibPersonFramework; The value of mode is RTLD_LOCAL, which means that when using dlsym(), the address of the symbol passed in can only be obtained through the handle returned by dlopen(). Since dlsym() is not used in this example, this value is unnecessary.

LibPerson *person = [LibPerson new] LibPerson *person = [LibPerson new] LibPerson *person = [LibPerson new] LibPerson *person = [LibPerson new]

Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_LibPerson", referenced from:
      objc-class-ref in main.o
ld: symbol(s) not found for architecture arm64
Copy the code

This is because at compile time, if [LibPerson new] is called, the compiler verifies that the class definition is present in the app’s Mach-O file and in the dynamic library on which it depends. Since the LibPersonFramework dynamic library has not yet been loaded at compile time, and the program contains only the LIbPerson class header file without its corresponding.m file (the compiler only compiles the.m file into the final Mach-o file), So the compiler can’t find the definition of the LibPerson class in the app’s Mach-o file or in the dynamic library it depends on, and then the compiler reports an error.

As you can see from the above code, the LibPersonFramework is already loaded when the LibPerson object is created, meaning that the class definition is already in the program at that time. Therefore, the following code is used in the above code to “fool” the compiler.

  Class class_person = objc_getClass("LibPerson");
  LibPerson *person = [class_person new];
Copy the code

3. Add dynamic library LibPersonFramework file

First of all, build the app package file

At this point, a compilation error may be reported saying that LibPersonFramework cannot be found, so you need to add LibPersonFramework next. In the previously created LibPersonFramework. Framework, find LibPersonFramework of dynamic link library

Go to the app package file, right click to show the package contents, and copy the LibPersonFramework file here

4. Re-sign the dynamic library

The dlopen() function fails to load LibPersonFramework. Although the same certificate is used to generate the framework and run the app, this is not the entire framework, so you need to force re-signing using CoDesign.

Add a script

/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$BUILT_PRODUCTS_DIR/$TARGET_NAME.app/LibPersonFramework"

Copy the code

We’re done here, run it, and it should be successful!

4. Inject dynamic libraries

Injecting dynamic libraries means adding a dynamic library to an existing Mach-O so that the dynamic library code can be executed in an existing app. When injecting a dynamic library into an existing app, the dynamic library can only be injected as a dependency library, because no code can be executed in the existing app prior to injection, so you cannot use the dlopen() function to load the dynamic library.

First, take a look at what happens when an app adds a dependency library. In this paper, DylibDemo added a dependent libraries LibPersonFramework. The framework, the following to this project as an example.

  1. The Frameworks file is added to the app package generated by the project. If it is a system dynamic library, it will not be added to the app package.

  1. An entry was added to the Mach-o fileLoad CommandsData, which represents the app’s dependency on the specified dynamic library.

useMachOViewOpen the Mach-O file in the app package

When the APP starts, it will automatically Load the dynamic library according to the path specified by Load Commands, so you must ensure that the corresponding dynamic library exists in the path.

Here’s an example

Create a new dynamic library LibInjectFramework, which will be injected into an existing app+[load]The method will be executed.

Create a project dylibdemo-inject, this project has no code, just an empty project, the next step is to Inject LibInjectFramework into this project.

  1. Copy the LibInjectFramework dynamic library into the app package for this project

  1. Add dynamic library dependencies

This step requires modifying the Mach-O file being injected into the app, which is done using Yololib. Once yololib is downloaded, then compiled, copy the production command to /usr/local/bin or another PATH in $PATH so it can be used on the terminal. Yololib requires two parameters, the first specifying the path to the mach-O file being modified, and the second specifying the path to the dynamic library.

In the project, add two script commands to re-sign the dynamic library and modify the Mach-O file

/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$BUILT_PRODUCTS_DIR/$TARGET_NAME.app/Frameworks/LibInjectFramework"
yololib "$BUILT_PRODUCTS_DIR/$TARGET_NAME.app/$TARGET_NAME" "Frameworks/LibInjectFramework"
Copy the code

Execute, the console should print the following sentence

Inject success 😊 😊 😊 😊 😊 😊 😊 😊 😊 😊Copy the code

Note that this project will only succeed on the first run, because multiple runs will add multiple identical Load commands to the Mach-O file. The solution is to save a raw Mach-O file and replace it before each run.

5. yololib

When yololib is used to add dynamic library dependencies, two places in the Mach-O file are changed

  1. Modify the header of the Mach-o file

The Mach header definition

struct mach_header_64 {
	uint32_t	magic;		/* mach magic number identifier */
	cpu_type_t	cputype;	/* cpu specifier */
	cpu_subtype_t	cpusubtype;	/* machine specifier */
	uint32_t	filetype;	/* type of file */
	uint32_t	ncmds;		/* number of load commands */
	uint32_t	sizeofcmds;	/* the size of all the load commands */
	uint32_t	flags;		/* flags */
	uint32_t	reserved;	/* reserved */
};
Copy the code

Since a Load Command has been added, the two fields that need to be changed are NCMDS and sizeofCMds, which indicate the total number of Load commands and the total size, respectively.

  1. Add adylib_commandThe structure of the body

Dynamic library information is stored in the dylib_command structure, which is defined by dylib_command

struct dylib_command {
	uint32_t	cmd;		/* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
					   LC_REEXPORT_DYLIB */
	uint32_t	cmdsize;	/* includes pathname string */
	struct dylib	dylib;		/* the library identification */
};

struct dylib {
    union lc_str  name;			/* library's path name */
    uint32_t timestamp;			/* library's build time stamp */
    uint32_t current_version;		/* library's current version number */
    uint32_t compatibility_version;	/* library's compatibility vers number*/
};
Copy the code

Create a dylib_command structure and add it to all Load commands,

fseek(newFile, sizeofcmds, SEEK_CUR); struct dylib_command dyld; fread(&dyld, sizeof(struct dylib_command), 1, newFile); NSLog(@"Attaching dylib.. \n\n"); dyld.cmd = LC_LOAD_DYLIB; // The size of CMD is the size of the dylib_command structure plus the size of the path. dyld.cmdsize = (uint32_t) dylib_size; dyld.dylib.compatibility_version = DYLIB_COMPATIBILITY_VERSION; dyld.dylib.current_version = DYLIB_CURRENT_VER; dyld.dylib.timestamp = 2; // Specify where to start: name dyld.dylib.name.offset = sizeof(struct dylib_command); fseek(newFile, -sizeof(struct dylib_command), SEEK_CUR); fwrite(&dyld, sizeof(struct dylib_command), 1, newFile);Copy the code

Add the dynamic library’s path string immediately after the Load_Command is added.

fwrite([data bytes], [data length], 1, newFile);
Copy the code

When a new Load_Command is added, it overwrites the existing data directly with the new data, because there is still some space between the Load_Command and the Section, so the overwrite does not affect the data of the Section.