17

I am trying to build an R package that uses some C code. I have a C library that is compiled into an executable, that can be called from the command line. There is a Makefile associated with it.

I am trying to grok the information here, where it says

If you want to create and then link to a library, say using code in a subdirectory, use something like

 .PHONY: all mylibs

 all: $(SHLIB)
 $(SHLIB): mylibs

 mylibs:
         (cd subdir; make) 

Be careful to create all the necessary dependencies, as there is a no guarantee that the dependencies of all will be run in a particular order (and some of the CRAN build machines use multiple CPUs and parallel makes).

If I create a new subdirectory of the src folder in my package, called someLibrary, with the code and Makefile unchanged, and in turn, in the original Makevars file for my package I add the above code unchanged, then I will be able to build that shared library to be exported using useDynLib?


Edit 1:

Following information here, I changed the Makefile to create a shared library by adding

CFLAG = -fPIC -g -O3 
LDFLAGS= -shared

However, this leads to the problem that the .so file is not exported directly to the libs directory of the package. If I hard code the path into the target, then the file is sent to the libs directory of the package (this is all by the way of calls to R CMD INSTALL myPackage).


Edit 2:

Lastly, I would like to know how to make calls to the shared library, given that it has a main() method that I could call from the command line executable.

What is the procedure to expose this to the R NAMESPACE, so that it can be called via .Call?

PS. Please let me know if I should make the last bit a separate question.

Community
  • 1
  • 1
tchakravarty
  • 10,736
  • 12
  • 72
  • 116
  • Maybe you should compile them to `.o`, and put these `.o` in `OBJECTS` of `Makevars`. R will link them and load them correctly if the wrapper is correct. – wush978 Aug 27 '13 at 17:02
  • Have you investigated the possibility of using `automake` and `libtool`? I know that GNU Autotools is quite a heavyweight solution, but it does make compiling a shared object in one directory and then linking against it from another quite achievable. – Peter T.B. Brett Mar 03 '14 at 10:13
  • @PeterBrett Peter, thanks for your comment. I still struggle from time to time with this. Would you be able to post a simple example of the entire tool chain to help me get started and adapt for my purpose? If you want I can add a bounty to this question. :) – tchakravarty Mar 03 '14 at 10:38
  • @fg-nu I will post a minimal hello-world-in-shared-library example this evening - might take a while to put together. Bounties are always appreciated! :-) – Peter T.B. Brett Mar 04 '14 at 12:37
  • @PeterBrett Bountied! – tchakravarty Mar 04 '14 at 14:09

2 Answers2

18

The original question author asked, in the comments on the question, for an example of using Automake, Libtool and LDADD to link a program compiled in one directory with a shared library compiled in a second directory. This is a complete, standalone, fully worked example of how to compile a library and program in separate directories of the same source tree using GNU Autotools.

Directory structure

We need to set up a directory structure as follows:

├ A/
│ ├ Makefile.am
│ ├ helloworld.c
│ └ helloworld.h
├ B/
│ ├ Makefile.am
│ └ foo.c
├ configure.ac
└ Makefile.am

The shared library will be compiled in directory A/, and the program that uses it in directory B/.

Writing the source code

There are three source files.

A/helloworld.c is the source code of the library. It exports one procedure, say_hello(), which prints the message "Hello world!" to standard output.

#include <stdio.h>
#include "helloworld.h"

void
say_hello (void)
{
  printf ("Hello world!\n");
}

A/helloworld.h is the header file that contains the declaration of the say_hello() function. It only has one line:

void say_hello (void);

Finally, B/foo.c is the source code of the program that uses the shared library. It includes the library's header file, and calls say_hello().

#include <helloworld.h>

int
main (int argc, char **argv)
{
  say_hello ();
  return 0;
}

Compiling the library

We will use Automake and Libtool to compile the shared library. Both of these tools are very powerful, and actually remarkably well-documented. The manuals (Automake, Libtool) are definitely worth reading.

The A/Makefile.am file is used by automake to control the compilation of the library.

# We're going to compile one libtool library, installed to ${libdir},
# and named libhelloworld.
lib_LTLIBRARIES = libhelloworld.la

# List the source files used by libhelloworld.
libhelloworld_la_SOURCES = helloworld.c

# We install a single header file to ${includedir}
include_HEADERS = helloworld.h

Compiling the program

The B/Makefile.am file controls compilation of the library. We need to use the LDADD variable to tell automake to link against the library we compiled earlier.

# Compile one program, called foo, and installed to ${bindir}, with a single C
# source file.
bin_PROGRAMS = foo
foo_SOURCES = foo.c

# Link against our uninstalled copy of libhelloworld.
LDADD = $(top_builddir)/A/libhelloworld.la

# Make sure we can find the uninstalled header file.
AM_CPPFLAGS = -I$(top_srcdir)/A

Controlling the build

Finally, we need a top level Makefile.am to tell Automake how to build the project, and a configure.ac file to tell Autoconf how to find the required tools.

The top-level Makefile.am is fairly straightforward:

# Compile two subdirectories.  We need to compile A/ first so the shared library is
# available to link against.
SUBDIRS = A B

# libtool requires some M4 scripts to be added to the source tree.  Make sure that
# Autoconf knows where to find them.
ACLOCAL_AMFLAGS = -I m4

Finally, the configure.ac file tells Autoconf how to create the configure script.

AC_INIT([libhelloworld], 1, peter@peter-b.co.uk)

# This is used to help configure check whether the source code is actually present, and
# that it isn't being run from some random directory.
AC_CONFIG_SRCDIR([A/helloworld.c])

# Put M4 macros in the m4/ subdirectory.
AC_CONFIG_MACRO_DIR([m4])

# We're using automake, but we want to turn off complaints about missing README files
# etc., so we need the "foreign" option.
AM_INIT_AUTOMAKE([foreign])

# We need a C compiler
AC_PROG_CC

# Find the tools etc. needed by libtool
AC_PROG_LIBTOOL

# configure needs to generate three Makefiles.
AC_CONFIG_FILES([A/Makefile
                 B/Makefile
                 Makefile])
AC_OUTPUT

Testing it out

Run:

$ autoreconf -i
$ ./configure
$ make
$ B/foo

You should see the required output: "Hello world!"

Peter T.B. Brett
  • 1,250
  • 11
  • 20
  • Peter, do you know any R? Can you figure out if this will work with the R packaging & compiling infrastructure? – tchakravarty Mar 05 '14 at 04:41
  • Hi @fg_nu, I've just read some of the relevant R documentation and I suspect not (also, OMG that compiling & packaging stuff is pretty gnarly). I'll try and figure it out later! – Peter T.B. Brett Mar 05 '14 at 05:38
  • Peter, no worries. :) Thanks for your great answer. I will go through it and let you know how I get on. – tchakravarty Mar 05 '14 at 05:59
  • Peter, I am going to give you the bounty, but not the check mark, since I hope to spend some time to build on your answer and make it about R as the original question was about R. I hope that is okay, let me know if not. :) – tchakravarty Mar 11 '14 at 09:31
  • You're very kind! I'm sorry I couldn't answer your question exactly! I keep meaning to come back and look into the problem -- I know other people who use R who've asked similar things -- but lack of time... – Peter T.B. Brett Mar 11 '14 at 10:24
  • Shouldn't `LDADD = $(top_builddir)/A/libhelloworld.la` be `foo_LDADD = $(top_builddir)/A/libhelloworld.la`? – wirrbel Oct 05 '19 at 23:12
  • am I missing something when I get `ld: cannot link -lhelloworld` *edit*, yup, i wrote -lhelloworld literally in the LDFLAGS, my bad – ThorSummoner May 07 '22 at 02:05
2

callable.c

#include <stdio.h>

int main(int argc, char **argv) {
    printf("Hello World\n");
    return 0;
}

To get a .so file from the C file callable.c use

R CMD SHLIB callable.c

So now we have callable.so

R normally requires that all C arguments be pointers, but if your main method ignores argc, then we can get around this. So to call main from callable.so from R we can write a method like so.

main <- function() {
    dyn.load("callable.so")
    out <- .C("main", argc=0, argv="")
}

Calling that main function will run the C main function.

lthreed
  • 444
  • 4
  • 11
  • Thanks, that was actually fairly insightful. I will try to put this together with Peter's answer to get the whole thing going. – tchakravarty Mar 11 '14 at 09:34