How do I properly link to a Rust dynamic library from C? - rust

I have a Cargo project named foo that produces a libfoo-<random-hash>.dylib. How do I link against it? clang only finds it if I rename it to libfoo.dylib but libfoo-<random-hash>.dylib is needed for running the program. My workaround is to manually copy and rename the lib but this can’t be the proper procedure.
My procedure to compile and run the C program is:
$ cargo build
$ cp target/debug/libfoo-*.dylib target/debug/libfoo.dylib
$ clang -lfoo -L target/debug -o bar bar.c
$ LD_LIBRARY_PATH=target/debug ./bar

The hash serves a purpose: It gives a link error in case of a version mismatch (especially important with cross-crate inlining). It also allows one binaries to link to two versions of a crate (e.g., because your application uses two libraries that internally use different versions). But in the C world, nobody cares about that. If you don't care either, or care more about making it easy to link to your library, just rename the shared object to get rid of the hash.
Note that the hash is added by Cargo. If you build by directly invoking rustc, no hash is added — but nobody wants that. AFAIK Cargo doesn't offer an option to omit the hash, but you might want to try asking the developers or filing an issue.
Alternatively, you could extract the full library name (including hash) from the file name, and instruct clang to link to that. Then you get the above advantages, but it makes the build process of everyone who wants to link to your library more complicated.

Since Rust 1.11, Cargo supports the
cdylib
artifact type for this exact purpose:
[lib]
name = "foo"
crate-type = ["rlib", "cdylib"]
This definition yields a ./target/{release,debug}/libfoo.so with
all the Rust dependencies statically linked in so it can be linked into C
projects right away.

Related

Setting the include path with bindgen

I'm writing a Rust interface to a small C library, which has headers spread in a few locations. It's not a system library, and is normally used by some executables in the same package; I'm currently including it as a git submodule in my Cargo project.
Building the library seems to be pretty easy; I've opted to use the gcc crate from build.rs:
gcc::Config::new()
.file("external/foo/dir1/file1.c")
.file("external/foo/dir2/file2.c")
.include("external/foo/dir1/")
.include("external/foo/dir2/")
.include("external/foo/config_a/")
.compile("libfoo.a");
Now I was hoping to use the bindgen crate to generate the FFI interface without too much fuss, but it doesn't seem to have a way of setting include paths.
I can create a wrapper.h as suggested by this blog and include several headers, but if dir1/dir1.h includes conf.h directly, which works when building due to .include("external/foo/config_a/") it can't be found.
I can't find anything in bindgen's API to help here (essentially I want to pass the equivalent of gcc/clang's -I option). Am I missing anything?
The best option I can think of so far is to copy the various headers from the library source into some temporary directory in build.rs and run bindgen on that, but that seems somewhat messy if there's a nicer way.
With the API you can use Builder::clang_arg with arbitrary arguments:
let b = bindgen::builder().header("foo.h").clang_arg("-I/path");
From the command line you can do the same by appending arguments after --, like:
bindgen foo.h -- -I/path

rust-sfml linking with cc failed, how to specify lib/include/share path

How can I specify where my libs live when building examples, or even writing new projects that use rust-sfml? I currently have both SFML and CSFML installed in /usr/local/{include,lib,share} as opposed to /usr/{include,lib,share}.
Is there a flag in cargo I can apply that will set the include/library path accordingly? Something like cargo build --lib=/usr/local/lib --include=/usr/local/include.
here is the rust-sfml page.
Each library that links against C libraries needs to support the appropriate linker parameters, including finding the library itself. It appears that there is an open pull request that adds support for specifying the install path to rust-sfml.
Specifically, Cargo allows specifying a build script. The build script communicates to Cargo by printing items to standard out. Two of those values that are interesting in this case are:
rustc-link-lib— indicates that the specified value should be passed to the compiler as a -l flag.
rustc-link-search — indicates the specified value should be passed to the compiler as a -L flag.

A workaround for the “Template Haskell + C” bug?

I've got the following situation:
Library X is a wrapper over some code in C.
Library A depends on library X.
Library B uses Template Haskell and depends on library A.
GHC bug #9010 makes it impossible to install library B using GHC 7.6. When TH is processed, GHCi fires up and tries to load library X, which fails with a message like
Loading package charsetdetect-ae-1.0 ... linking ... ghc:
~/.cabal/lib/x86_64-linux-ghc-7.6.3/charsetdetect-ae-1.0/
libHScharsetdetect-ae-1.0.a: unknown symbol `_ZTV15nsCharSetProber'
(the actual name of the “unknown symbol” differs from machine to machine).
Are there any workarounds for this problem (apart from “don't use Template Haskell”, of course)? Maybe library X has to be compiled differently, or there's some way to stop it from loading (as it shouldn't be called during code generation anyway)?
This is really one of the main reasons that 7.8 switched to dynamic GHCi by default. Rather than try to support every feature of every object file format, it builds dynamic libraries and lets the system dynamic loader handle them.
Try building with the g++ option -fno-weak. From the g++ man page:
-fno-weak
Do not use weak symbol support, even if it is provided by the linker. By default, G++ will use weak symbols if they are available. This option exists only for testing, and should not be used by end-users; it will result in inferior code and has no benefits. This option may be removed in a future release of G++.
There is another issue with __dso_handle. I found that you can at least get the library to load and apparently work by linking in a file which defines that symbol. I don't know whether this hack will cause anything to go wrong.
So in X.cabal add
if impl(ghc < 7.8)
cc-option: -fno-weak
c-sources: cbits/dso_handle.c
where cbits/dso_handle.c contains
void *__dso_handle;

How to restrict access to symbols in shared object?

I have a plug-in in the form of a shared library (bar.so) that links into a larger program (foo). Both foo and bar.so depend on the same third party library (baz) but they need to keep their implementations of baz completely separate. So when I link foo (using the supplied object files and archives) I need it to ignore any use of baz in bar.so and vice versa.
Right now if I link foo with --trace-symbol=baz_fun where baz_fun is one of the offending symbols I get the following output:
bar.so: definition of baz_fun
foo/src.a(baz.o): reference to baz_fun
I believe this is telling me that foo is referencing baz_fun from bar.so (and execution of foo confirms this).
Solutions that I have tried:
Using objcopy to "localize" the symbols of interest: objcopy --localize-symbols=local.syms bar.so where local.syms contains all of the symbols of interest. I think I might just be confused here and maybe "local" doesn't mean what I think it means. Regardless, I get the same output from the link above. I should note that if I run the nm tool on bar.so prior to using objcopy all of the symbols in question have the T flag (upper-case indicating global) and after objcopy they have a t indicating they are local now. So it appears I am using objcopy correctly.
Compiling with -fvisibility=hidden however due to some other constraints I need to use GCC 3.3 which doesn't appear to support that feature. I might be able to upgrade to a newer version of GCC but would like confirmation that compiling with this flag will help me before heading down that road.
Other things to note:
I do not have access to the source code of either foo or baz
I would prefer to keep all of my plug-in in one shared object (bar.so). baz is actually a licensing library so I don't want it separated
Use dlopen to load your plugin with RTLD_DEEPBIND flag.
(edit)
Please note that RTLD_DEEPBIND is Linux-specific and need glibc 2.3.4 or newer.

Explanation sought: libtool, automake, shared libraries (and Fortran)

The problem I had is solved. I'm posting this to solicit an explanation as to why the solution actually works. I've gotten great feedback here before.
I have a legacy code base that used a very simplistic build system, and my project is to migrate that to Autotools for customization and, particularly, building shared libraries. The main library is written in C, but must also be linkable from Fortran (for legacy purposes), and is distributed with some test codes in F77. The authors organized the source code into modules...
src_module1/
src_module2/
...
testc/
testf77/
Their built the library lib/libmain.a by compiling code in the src_*/ directories and archiving the objects with ranlib.
My first approach was to build a shared library from each src_*/ separately and "link" all of these into one shared library. Using Autotools, the src_module1/Makefile.am would contain
noinst_LTLIBRARIES = libmodule1.la
libmodule1_la_SOURCES = ...
and so on for the other modules, and finally the lib/Makefile.am would need only:
lib_LTLIBRARIES = libmain.la
libmain_la_SOURCES =
libmain_la_LIBADD = $(top_srcdir)/src_module1/libmodule1.la ...
This seemed to work perfectly. However, when the code in testc/ was compiled and linked against libmain.la, a "symbols not found" error was issued.
Thinking that this was an issue with libtool or shared libraries, I tried building static only, basically changing all .la to .a and all _LTLIBRARIES to _LIBRARIES. Same problem. This time, however, noticing the error "ranlib: warning for library: libmain.a the table of contents is empty (no object file members in the library define global symbols)" when trying to link libmain.a itself.
The solution that I found seems like a hack. I did not build Makefiles for any of the src_*/ directories, but instead used only for the lib/ directory and its Makefile.am had the lines:
lib_LTLIBRARIES = libmain.la
libmain_la_SOURCES = [all sources from all ../src_modules/ ]
This worked. The compiled programs in testc/ linked against libmain.la without issue. One of the "modules" is a set of Fortran bindings that wrap other C functions in the library. Even the Fortran codes in testf77/ linked against libmain.la properly.
Could someone carefully explain what happens when libtool builds a shared library? Or even when building a static library? Why is it that several static libraries can't be linked together to make one static library? Why are symbols only available when libtool/ranlib builds the library "from sources"? And what about installing a shared/static library, i.e. moving it to the /usr/local/lib --- what happens there? The Wikipedia article on static and shared libraries isn't really detailed enough for me.
I do appreciate all efforts to make sense of my longwinded question.
What you first tried ought to work. I am using this kind of setup all the time (in a C++ context). It's also documented, and part of the Automake test suite (although maybe not with Fortran).
A libtool library that is not installable, i.e., one declared with noinst_LTLIBRARIES, is called a libtool convenience library. That noinst_ makes a big difference in what is built. Even if Libtool is configured to build shared libraries, a libtool convenience library is not actually a shared library: it is just a set of object files (compiled as PIC so that they can be latter be used in a shared library) stored in an archive. You can use a libtool convenience library anywhere using this set of objects would make sense, e.g., to build a shared library.
When multiple libtool convenience libraries are LIBADDed to an installable libtool library (such as your libmain.la), Libtool has to unpack the archives containing the objects of each convenience library and link them into the final library.
There is a trap that is worth noting here: when building a shared library out of
convenience libraries, if the _SOURCES variable is empty Automake does not know which linker to use and default to the C linker. If you want to trick Automake into using the linking rule for some specific language, you can declare a nodist_EXTRA_..._SOURCES source file that do not have to exist. (See the Libtool Convenience Libraries section of the Automake manual for an example.)
Maybe that was your problem? If you have some Fortran files in the sources of some of your modules (your description suggests these are only C files), the Fortran linker will be used to build libmain.la only if a Fortran file appears in the source files declared for that libtool library. And the C linker will be used when libmain_la_SOURCES is empty.
Otherwise, I have no idea why it didn't work.
There is an small error in your example:
libmain_la_LIBADD = $(top_srcdir)/src_module1/libmodule1.la
should be
libmain_la_LIBADD = $(top_builddir)/src_module1/libmodule1.la
because the library is not created in the source directory. However I assume this is just a typo, and you won't see the difference unless you do a VPATH build or run make distcheck.
Your second try, using _LIBRARIES without Libtool is not expected to work.
_LIBRARIES can only be used to declare static archives, and in this case _LIBADD may only contain object files, not other static archives. Unpacking an archive to reuse its objects into another archive can be tricky to do portably. Automake's answer to this problem has always been: install Libtool and use _LTLIBRARIES (Libtool can be configured to build only static libraries).

Resources