Tuesday, February 18, 2014

shared object files in MSVC, GCC and XCODE

Shared object libraries are what many MS Windows users know as dynamic-link library (DLL), a library that can be loaded at runtime allowing shared objects in the library to be used by other programs. The other option would be statically linking dependent libraries together. Both options have pros and cons, but I am going to write about how to compile shared object libraries for all three platforms: MS Windows, GNU Linux and Apple Macintosh.

MS Windows MSVC

Microsoft Visual C++ doesn't have a compiler option to create a shared object library, instead objects that are to be exported as DLLs and objects that need to be imported are declared in the source. It's convenient to define macros for the DLL export and import declarations. The following example defines the macros and then uses them to import the first object which is required by the second object which is also exported.
// define macros for import and export delcarations
#define DllImport   __declspec( dllimport )
#define DllExport   __declspec( dllexport )

// imported_object from dll
DllImport long imported_object( arg_type *arg_name );

// exported_object to dll
DllExport long exported_object( arg_type *arg_name );
{
// some code

// call imported object
imported_object( arg_name );

// more code
}
Ideally you should include a header file with the signature of the imported object, but MSVC won't complain about it, however Linux and Apple will. Also MSVC uses the command `nmake` instead of `make` to call Makefiles. Finally, you will need to use the `SetEnv.cmd` script from the version of Windows SDK used to set all of the MSVC variables that the compiler requires and set the release mode, either DEBUG or RELEASE and the target, either /x86 or /x64.
C:\> setenv /RELEASE /X64
When it's time to compile the libraries they need to be linked. You don't need to do anything to tell MSVC where the files are located as long as their relative positions stay the same, you can use the following in a Makefile, and call it using `NMAKE` in your SDK environment after calling `SetEnv.cmd` with your release mode and target.
cl /LD /Wall /Fo output\folder /Fe output\folder\exported_library.dll \
exported_library_src1.c exported_library_src2.c exported_library_src3.c  \
/link output\folder\imported_library.lib
The compiler command is `cl`. The `/LD` flag indicates to build a DLL not an EXE. The `/Fo` flag is used to specify the output folder, and the `/Fe` flag is the desired name of the output object. All of the source files should be listed and the `/link` flag pass the shared object library to be imported at runtime to the linker, so there's no need to call the linker separately. Note that the linked library is not the DLL but the library, `lib` object also created. The `/Wall` flag enables all compiler warnings, which can be helpful.
There is another way to compile a shared library with Microsoft compilers. Pass a definitions .def file that contains the names of the exports to the linker. It's fine to use both declarations and a definitions file.
Also beware, if you are using a C++ compiler or using the .cpp extension for your source, your exports may get mangled. Use the extern "C" declaration before your DllImport or DllExport declarations. You may also need to turn off the generation of debugging info.

Linux GCC

The GNU Compiler Collection uses the `-shared` flag to indicate that the file should be a shared objected. That's it. But if you want the library to be relocatable relative to its dependencies, use the `-rpath` linker flag and set it to $ORIGIN which expands to the current location of the library, wherever it is. Because variables may get expanded this is wrapped in quotes, and in a Makefile it will be necessary to use an extra `$`. Make sure to include a header that contains the signatures for all of the linked objects that are called in your source or the compiler will complain. This is also true for Darwin.
cc -Lpath/to/links -Wl,-rpath='$${ORIGIN}' -shared -fPIC -Wall \
exported_library_src1.c exported_library_src2.c exported_library_src3.c \
-o output/folder/libexported_library.so -limport_library
The compiler here is `cc`, `-L` is the flag used to specify where the linked libraries are, -Wl,X,Y is a flag that passes arguments X=Y to the linker. Here we set the linker flag `-rpath` to the linker variable $ORIGIN but with some extra quotes and an extra `$` since this is in a Makefile. Later using either `objdump` or `readelf` you can see what RPATH is set to. The position independent flag, `-fPIC` is always necessary, although I honestly don't know why. The outputs are specified by the `-o` flag and any linked files are specified by the `-l` flag, so calling the linker separately is not necessary. Note that the output name includes both the "lib" prefix and the ".so" extension, but the library references by the linker omits both!

Macintosh XCode

The compiler on Macintosh is called XCode, and it can be downloaded for free, after registering for a free Apple Developers account. The commands are very similar to GCC, since it is GCC ported to Darwin, except that XCode's linker doesn't have an $ORIGIN variable, instead it has `@loader_path` and `@rpath`. Also even though Darwin will look for both *.so and *.dylib files, I use *.dylib extension for Apple's shared objects because IMO this makes it easy to distinguish between Linux and Macintosh. Finally, set the libraries name using the `-install_name` flag with the `@rpath` variable so that it will look for other libraries relative to itself.
cc -Lpath/to/links -Wl,-rpath,'@loader_path' -shared -fPIC -Wall \
exported_library_src1.c exported_library_src2.c exported_library_src3.c \
-o output/folder/libexported_library.dylib -limport_library \
-install_name @rpath/libexported_library.dylib
So now the loader path is set, and the name of the output file was set by the compiler to use the relative path variable `@rpath` instead of the absolute path where it was compiled which makes it relocatable anywhere, because it will look for its dependencies using the relative path of where it's actually located instead of where it was when it was first built. The result is identical to using `$ORIGIN` on a linux box. Now when `libexported_library.dylib` calls `libimport_library.dylib` it will use a relative path. You can see the library's name and path by using the `otool` command with `-l` flag.

What if you need to change the compiled name of a dynamic library that was already compiled? You can also use (deprecated) `install_name_tool` with the `-id` flag to change the path to `@rpath` after compilation and linking. How would you know what the library's compiled name is? Use the (deprecated) `otool -l` command to read the load commands which will show he name of the library and its RPATH.
$ install_name_tool -id @rpath/libexported_library.dylib output/folder/libexported_library.dylib
$ otool -l libexported_library.dylib
[UPDATED 2014-02-26] use -install_name @rpath/libname.dylib to the compiler as an option instead of calling install_name_tool after compilation. Also unfortunately, Apple has updated their manpages, so there are no longer any links to otool or install_name_tool.

References

[1] DYLD (1)
[2] Run-Path Dependent Libraries
[3] Dynamic Libraries - Introduction

No comments:

Post a Comment

Fork me on GitHub