The question you linked references the "Link Binary With Libraries" functionality, which is somewhat different than an embedded binary.
"Link Binary With Libraries" means what you'd expect it to with respect to linkage: Regardless of whether the binary is a static library, dynamic library, or framework it will be linked to your object code at link time after compilation.
When you think of linkage with a static library, what happens is pretty clear: the linker copies the code from the library (e.g. libFoo.a
) into your output binary. Your output file grows in size but doesn't need to resolve any external dependencies at runtime. Everything your program needs to run (with respect to the static library) is present after it is built.
With a dynamic library (.dylib, or system-supplied framework), the expectation is that the library you are linking against will be present somewhere in the system's dynamic-library loader path when you run your program. This way you don't have the overhead of copying all the third party external libraries into your binary, and all the different programs on a computer that also link to that library will be able to find it, which saves minimally disk space, but also potentially memory space, depending on how and where the system caches libraries.
A framework is much like a dynamic library, but can contain resources in its directory structure (images, audio, other frameworks, etc.). In this case a simple static-library or .dylib file won't cut it so you might have to link to a framework just so it can find what it needs to run properly.
When you link to a third-party framework (say something you downloaded from github and built yourself), it might not be present on the system you intend to run on. In this case, you'd not only link to the framework, but embed it inside your application bundle as well using the "Copy Frameworks" phase. When your program runs, the runtime-linker (aka the resolver) will look inside your bundle in addition to the system loader path, find the embedded framework, and link it so your app will have the code it needs in order to run.
Finally, what is properly an "embedded binary" is an executable you both embed in your application bundle via a Copy-Files Phase, and that you execute yourself, perhaps with a call to popen()
or similar. The embedded binary may be called by your program, but it isn't linked with it. It is a fully external entity (like programs in the /bin
directory).
In practice, for system-supplied libraries and frameworks you will link against them and that's all you need to do.
If you need to link a library you built that doesn't need any embedded resources (i.e. doesn't require a framework to exist), then you can just link against a static library. If you find you have multiple modules in your program that want to use the same library code, then converting it to a framework or dynamic library and linking against that can save space and may be convenient (particularly if memory usage is a concern).
Finally, frameworks can include not only resources, but header and/or license files. Using a framework to convey these files is actually a convenient distribution mechanism so often you may want to incorporate a framework just so these things can tag along with your binary (i.e. license requirements may make this mandatory).
--- EDIT ---
Adam Johns posted the following question as a comment:
This is a great answer. There is something I'm still a little confused on, however. What does it mean to execute the binary yourself? Do you mean simply using the embedded framework's code? I know you mentioned popen(), but you're saying my app is calling popen()? I don't really know what that means.
I'm saying an embedded binary is just another resource file in your bundle, like an audio file or image, although the file is instead an executable command-line tool. The popen()
function (man popen
from your terminal to read more about it) lets you execute arbitrary programs from another running program. The system()
function is another way. There are others, and I'll give a historical example here that may make understanding use of an embedded binary a bit more clear:
As you're probably aware, when you launch an app on Mac OS X it is launched with a user id of the current user. Under most common installations that's the default user-at-the-Desktop admin
user, who is given user id 501
.
On Unix-based operating systems only the root
user (user id 0
) has full access to the entire filesystem. Sometimes it happens that an installer program launched by the Desktop user needs to install files in a privileged directory (drivers for example). In this case, the application program needs to escalate its privileges to the root
user so it can write in these restricted directories.
To facilitate this in operating systems through OS X 10.7, Apple provided in its Authorization Services API the function AuthorizationExecuteWithPrivileges() (this is now deprecated, but is still a useful example).
AuthorizationExecuteWithPrivileges()
took as an argument a path to a command-line tool to execute as root
. The command line tool was an executable shell script or compiled binary that you wrote to run your install logic. This tool was installed inside your application bundle just like any other resource file.
When called, the OS put up an authorization dialog asking for the user's password (you've seen this before!) and when entered would execute the program as root
on your app's behalf. This process is similar to just executing a program with popen()
yourself, although popen()
alone doesn't give you the benefit of privilege escalation.