Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Ruby Extension with External Libraries

Tags:

c++

ruby

linker

I started a little experiment today: I wrote a C++ class which depends on some other libraries (ALGLIB, Eigen, internal tools) and I wanted to create a Ruby wrapper for that class. I'm currently using Rice to do that. First I wrote a very simple C++ wrapper for my class:

// @file MLPWrapper.h
#pragma once

#include "mlp/MLP.h"
#include <ruby.h>

class MLPWrapper
{
  MLP mlp; // <- my C++ class
public:
  ...
  void fit()
  {
    ...
    mlp.fit(stop);
  }
};

The library's cpp-file is this:

// @file cmlp.cpp
#include "rice/Data_Type.hpp"
#include "rice/Constructor.hpp"
#include "MLPWrapper.h"

using namespace Rice;

extern "C"
void Init_cmlp()
{
  Data_Type<MLPWrapper> rb_cMLPWrapper = define_class<MLPWrapper>("MLP")
      .define_constructor(Constructor<MLPWrapper>())
      ...
      .define_method("fit", &MLPWrapper::fit);
}

And this is the extconf.rb:

require "mkmf-rice"

$CFLAGS << "-O3"

HEADER_DIRS = [
  "..",
  "../../external/libraries/eigen-eigen-3.0.1",
  "../../external/libraries/alglib/cpp/src",
  "../../external/libraries/CMA-ESpp"
]
LIB_DIRS = [
  "../../build/external/libraries/alglib/cpp/src",
  "../../build/external/libraries/CMA-ESpp/cma-es",
  "../../build/src/tools"
]

dir_config("libs", HEADER_DIRS, LIB_DIRS)

have_library("libtools")
have_library("libalglib")

create_makefile("cmlp")

Everything works fine except linking. Obviously the header files of the libraries are included, otherwise it would not compile. But when I run a little test program ("require "cmlp"; mlp = MLP.new") in Ruby it does not find the symbol _ZN6LoggerC1ENS_6TargetESs, which is part of libtools (an enum). This is what happens when I build the C++ extension and execute the test program:

$ ruby extconf.rb
checking for main() in -lrice... yes
checking for main() in -llibtools... no
checking for main() in -llibalglib... no
checking for main() in -llibcmaes... no
creating Makefile
$ make
g++ -I. -I. -I/usr/lib/ruby/1.8/x86_64-linux -I. -I.. -I../../external/libraries/eigen-eigen-3.0.1 -I../../external/libraries/alglib/cpp/src -I../../external/libraries/CMA-ESpp     -I/var/lib/gems/1.8/gems/rice-1.4.3/ruby/lib/include -fPIC -fno-strict-aliasing -g -g -O2  -fPIC -O3   -Wall -g -c cmlp.cpp
g++ -shared -o cmlp.so cmlp.o -L. -L/usr/lib -L../../build/external/libraries/alglib/cpp/src -L../../build/external/libraries/CMA-ESpp/cma-es -L../../build/src/tools -L. -Wl,-Bsymbolic-functions -rdynamic -Wl,-export-dynamic  -L/var/lib/gems/1.8/gems/rice-1.4.3/ruby/lib/lib    -lrice -lruby1.8 -lpthread -lrt -ldl -lcrypt -lm   -lc
$ ruby test.rb
ruby: symbol lookup error: ./cmlp.so: undefined symbol: _ZN6LoggerC1ENS_6TargetESs

The libraries are all compiled with CMake (add_library(...)) and are located at

../../build/src/tools/libtools.so
../../build/external/libraries/alglib/cpp/src/libalglib.so
../../build/external/libraries/CMA-ESpp/cma-es/libcmaes.so

I don't know how to solve this problem on my own and I could not find any helpful documentation for my problem. How do i fix this extconf.rb? I appreciate every hint.

edit: OK, I changed the extconf.rb:

require "rubygems"
require "mkmf-rice"

BASE_DIR = "/bla/"

$CFLAGS << " -O3"

dir_config("tools", [BASE_DIR + "src", BASE_DIR + "external/libraries/eigen-eigen-3.0.1"], BASE_DIR + "build/src/tools")
unless have_library("tools")
  abort "tools are missing. please compile tools"
end

dir_config("alglib", BASE_DIR + "external/libraries/alglib/cpp/src", BASE_DIR + "build/external/libraries/alglib/cpp/src")
unless have_library("alglib")
  abort "alglib is missing. please compile alglib"
end

dir_config("cmaes", BASE_DIR + "external/libraries/CMA-ESpp", BASE_DIR + "build/external/libraries/CMA-ESpp/cma-es")
unless have_library("cmaes")
  abort "cmaes is missing. please compile cmaes"
end

create_makefile("cmlp")

The generated Makefile is:

SHELL = /bin/sh

#### Start of system configuration section. ####

srcdir = .
topdir = /usr/lib/ruby/1.8/x86_64-linux
hdrdir = $(topdir)
VPATH = $(srcdir):$(topdir):$(hdrdir)
exec_prefix = $(prefix)
prefix = $(DESTDIR)/usr
sharedstatedir = $(prefix)/com
mandir = $(prefix)/share/man
psdir = $(docdir)
oldincludedir = $(DESTDIR)/usr/include
localedir = $(datarootdir)/locale
bindir = $(exec_prefix)/bin
libexecdir = $(prefix)/lib/ruby1.8
sitedir = $(DESTDIR)/usr/local/lib/site_ruby
htmldir = $(docdir)
vendorarchdir = $(vendorlibdir)/$(sitearch)
includedir = $(prefix)/include
infodir = $(prefix)/share/info
vendorlibdir = $(vendordir)/$(ruby_version)
sysconfdir = $(DESTDIR)/etc
libdir = $(exec_prefix)/lib
sbindir = $(exec_prefix)/sbin
rubylibdir = $(libdir)/ruby/$(ruby_version)
docdir = $(datarootdir)/doc/$(PACKAGE)
dvidir = $(docdir)
vendordir = $(libdir)/ruby/vendor_ruby
datarootdir = $(prefix)/share
pdfdir = $(docdir)
archdir = $(rubylibdir)/$(arch)
sitearchdir = $(sitelibdir)/$(sitearch)
datadir = $(datarootdir)
localstatedir = $(DESTDIR)/var
sitelibdir = $(sitedir)/$(ruby_version)

CC = gcc
LIBRUBY = $(LIBRUBY_SO)
LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
LIBRUBYARG_STATIC = -lruby1.8-static

RUBY_EXTCONF_H = 
CFLAGS   =  -fPIC -fno-strict-aliasing -g -g -O2  -fPIC $(cflags) -O3 
INCFLAGS = -I. -I. -I/usr/lib/ruby/1.8/x86_64-linux -I.
DEFS     = 
CPPFLAGS =  -I/bla/external/libraries/CMA-ESpp -I/bla//external/libraries/alglib/cpp/src -I//bla/src -I/bla/external/libraries/eigen-eigen-3.0.1     -I/var/lib/gems/1.8/gems/rice-1.4.3/ruby/lib/include
CXXFLAGS = $(CFLAGS)  -Wall -g
ldflags  = -L. -Wl,-Bsymbolic-functions -rdynamic -Wl,-export-dynamic  -L/var/lib/gems/1.8/gems/rice-1.4.3/ruby/lib/lib
dldflags = 
archflag = 
DLDFLAGS = $(ldflags) $(dldflags) $(archflag)
LDSHARED = g++ -shared
AR = ar
EXEEXT = 

RUBY_INSTALL_NAME = ruby1.8
RUBY_SO_NAME = ruby1.8
arch = x86_64-linux
sitearch = x86_64-linux
ruby_version = 1.8
ruby = /usr/bin/ruby1.8
RUBY = $(ruby)
RM = rm -f
MAKEDIRS = mkdir -p
INSTALL = /usr/bin/install -c
INSTALL_PROG = $(INSTALL) -m 0755
INSTALL_DATA = $(INSTALL) -m 644
COPY = cp

#### End of system configuration section. ####

preload = 

CXX = g++
libpath = . $(libdir) /bla/external/libraries/CMA-ESpp/cma-es /bla/build/external/libraries/alglib/cpp/src /bla/build/src/tools
LIBPATH =  -L. -L$(libdir) -L/bla/build/external/libraries/CMA-ESpp/cma-es -L/bla/build/external/libraries/alglib/cpp/src -L/bla/build/src/tools
DEFFILE = 

CLEANFILES = mkmf.log
DISTCLEANFILES = 

extout = 
extout_prefix = 
target_prefix = 
LOCAL_LIBS = 
LIBS = -lcmaes -lalglib -ltools -lrice -lruby1.8 -lpthread -lrt -ldl -lcrypt -lm   -lc
SRCS = cmlp.cpp
OBJS = cmlp.o
TARGET = cmlp
DLLIB = $(TARGET).so
EXTSTATIC = 
STATIC_LIB = 

BINDIR        = $(bindir)
RUBYCOMMONDIR = $(sitedir)$(target_prefix)
RUBYLIBDIR    = $(sitelibdir)$(target_prefix)
RUBYARCHDIR   = $(sitearchdir)$(target_prefix)

TARGET_SO     = $(DLLIB)
CLEANLIBS     = $(TARGET).so $(TARGET).il? $(TARGET).tds $(TARGET).map
CLEANOBJS     = *.o *.a *.s[ol] *.pdb *.exp *.bak

all:        $(DLLIB)
static:     $(STATIC_LIB)

clean:
        @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)

distclean:  clean
        @-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
        @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)

realclean:  distclean
install: install-so install-rb

install-so: $(RUBYARCHDIR)
install-so: $(RUBYARCHDIR)/$(DLLIB)
$(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
    $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
install-rb: pre-install-rb install-rb-default
install-rb-default: pre-install-rb-default
pre-install-rb: Makefile
pre-install-rb-default: Makefile
$(RUBYARCHDIR):
    $(MAKEDIRS) $@

site-install: site-install-so site-install-rb
site-install-so: install-so
site-install-rb: install-rb

.SUFFIXES: .c .m .cc .cxx .cpp .C .o

.cc.o:
    $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<

.cxx.o:
    $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<

.cpp.o:
    $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<

.C.o:
    $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<

.c.o:
    $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) -c $<

$(DLLIB): $(OBJS) Makefile
    @-$(RM) $@
    $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)



$(OBJS): ruby.h defines.h
like image 845
alfa Avatar asked Oct 09 '22 17:10

alfa


1 Answers

../../build/src/tools/libtools.so
../../build/external/libraries/alglib/cpp/src/libalglib.so    
../../build/external/libraries/CMA-ESpp/cma-es/libcmaes.so

could be the problem. I would try absolute paths here, I could imagine that the pwd is a different one than you expected while running ruby extconf.rb. Also, I assume you need a dir_config entry for each library you'd like to link in. So

dir_config('libs', HEADER_DIRS, LIB_DIRS)

should be replaced by

dir_config('tools', '<Path to include dir>', '<Path to lib dir>')
dir_config('alglib', '<Path to include dir>', '<Path to lib dir>')

Note that the leading 'lib' should be omitted, just like you would in the -l linker option. Next, if you want to be absolutely sure that a library is found, replace

have_library('libtools')

by

have_library('tools') or raise

Again, 'lib' is omitted.

If you still can't solve the problem, please post the Makefile generated by `ruby extconf.rb'.

like image 156
emboss Avatar answered Oct 12 '22 12:10

emboss