Extending Ruby using C++ and Rice

Hi,

I am trying to extend Ruby and my current implementation is using C++
and Rice (http://rice.rubyforge.org/main.html).
Mac OS X 10.5.8
Ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0]

I have completed the following steps as shown in Rice to create a cpp
class (my_test.cpp)

#include “rice/Class.hpp”
#include “rice/Data_Type.hpp”
#include “rice/Constructor.hpp”
#include “MyTest.h”

using namespace Rice;

extern “C”
void Init_my_test()
{
define_class(“MyTest”).
define_constructor(Constructor()).
define_method(“read”, &MyTest::read);
}

Here are my h and cpp files for MyTest

*** HEADER FILE ***
#ifndef __MyTest_h
#define __MyTest_h

#include
#include “XternalDebug.hpp”

class MyTest
{
public:
MyTest() {};
virtual ~MyTest(void){
SX::Terminate();
SXMP::Terminate();
}
std::string read(std::string filename);
};
#endif

*** CPP FILE ***
#include “MyTest.h”

extern “C” std::string MyTest::read(std::string filename)
{
if(SXMP::Initialize())
{
// Must initialize SX before we use it
if (SX::Initialize())
{
std::string status = “”;
bool ok;
SX myFile;
myFile.OpenFile(filename, UnknownFile, opts);
std::string buffer;
myFile.Get(&buffer);
return buffer;
}
}
return NULL;
}

SX and SXMP are taken from another library called libXternalDebug.a

My extconf.rb file looks as follows:
require ‘rubygems’
require ‘mkmf-rice’
$CPPFLAGS << ’ -DMAC_ENV=1’
have_library(“stdc++”)
dir_config(“xtoolkit”)
have_library(“XternalDebug”)
create_makefile(“my_test”)

I pass the following arguments to extconf.rb
ARCHFLAGS=“-arch i386” ruby extconf.rb
–with-xtoolkit-include=/Users//xtoolkit/public/include
–with-xtoolkit-lib=/Users//xtoolkit/public/libraries

$ ARCHFLAGS=“-arch i386” ruby extconf.rb
–with-xtoolkit-include=/Users//xtoolkit/public/include
–with-xtoolkit-lib=/Users//xtoolkit/public/libraries
checking for main() in -lrice… yes
checking for main() in -lstdc++… yes
checking for main() in -lX… yes
creating Makefile

I then run make:
$ makeg++ -I. -I.
-I/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin9.0
-I. -I/Users//xtoolkit/public/include
-I/Library/Ruby/Gems/1.8/gems/rice-1.3.1/ruby/lib/include -DMAC_ENV=1
-fno-common -arch i386 -Os -pipe -fno-common -Wall -g -c my_test.cpp
g++ -I. -I.
-I/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin9.0
-I. -I/Users//xtoolkit/public/include
-I/Library/Ruby/Gems/1.8/gems/rice-1.3.1/ruby/lib/include -DMAC_ENV=1
-fno-common -arch i386 -Os -pipe -fno-common -Wall -g -c MyTest.cpp
g++ -arch i386 -pipe -bundle -undefined dynamic_lookup -o my_test.bundle
my_test.o MyTest.o -L.
-L/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib
-L/Users//xtoolkit/public/libraries -L. -arch i386
-L/Library/Ruby/Gems/1.8/gems/rice-1.3.1/ruby/lib/lib -lXternalDebug
-lstdc++ -lrice -lruby -lpthread -ldl -lm

(please note i have removed my username intentionally from the above
paths)

I then have the following ruby test:
require ‘my_test’
require ‘test/unit’

class TestTest < Test::Unit::TestCase
def test_test
t = MyTest.new
assert_equal(Object, MyTest.superclass)
assert_equal(MyTest, t.class)
end
end

When I try to run this I get the following error:
$ ruby testtest.rb
Loaded suite testtest
Started
.
Finished in 0.000476 seconds.

1 tests, 2 assertions, 0 failures, 0 errors
dyld: lazy symbol binding failed: Symbol not found:
__ZN9TSXMISsE9TerminateEv
Referenced from: /Users//tests/rice_test2/my_test.bundle
Expected in: dynamic lookup

dyld: Symbol not found: __ZN9TSXISsE9TerminateEv
Referenced from: /Users//tests/rice_test2/my_test.bundle
Expected in: dynamic lookup

Trace/BPT trap

The above does show the 2 tests as passing but then I get the error
below when it is deconstructing the class at it uses the external
library.

Does anyone know what I am doing wrong or what I am missing to get these
errors? Do I need to include this external library in a particular
directory for ruby to discover?
Any help is much appreciated.

I have also heard of using a combination of FFI and SWIG but how does
this work when it is an external library and I do not have the source?

Lea

On Apr 16, 2010, at 12:05 PM, Lea Savage wrote:

#include “rice/Class.hpp”
define_constructor(Constructor()).
#include “XternalDebug.hpp”
};
// Must initialize SX before we use it
}
dir_config(“xtoolkit”)
–with-xtoolkit-lib=/Users//xtoolkit/public/libraries
-fno-common -arch i386 -Os -pipe -fno-common -Wall -g -c my_test.cpp
-lstdc++ -lrice -lruby -lpthread -ldl -lm
t = MyTest.new
Finished in 0.000476 seconds.

I have also heard of using a combination of FFI and SWIG but how does
this work when it is an external library and I do not have the source?

Lea

Posted via http://www.ruby-forum.com/.

For future reference, Rice has it’s own mailing list at
[email protected]. Send a blank email to that address to register as it
will be dropped, then you can send to the list.

As for your question here, the one thing that stands out is the “extern
‘C’” on MyTest::read. Rice expects normal C++ code and such a statement
isn’t required for Rice to properly use your code. I’m not sure this is
causing your error but it is something I would recommend removing unless
it’s there for other reasons.

Otherwise, as you noticed, the error says that it can’t find
SXM::Terminate during runtime. My guess is that the load path on your
machine (DYLD_LIBRARY_PATH) doesn’t include the path that includes the
SXM bundles. One way to quickly test this is to place the bundle right
next to your my_test.bundle.

As for your final statement about FFI and SWIG, these two libraries are
in effect for different purposes. FFI is about run-time linking to C
libraries (C++ doesn’t work because of non-portable name mangling) while
SWIG will parse your C++ and generate compliant C code that wraps the
C++ into Ruby. For completeness of the comparison, Rice takes wrapping a
third way, and is built to allow wrapping C++ libraries into Ruby using
a very simple C++ API, so you very rarely have to actually call a C-Ruby
API method.

I would prefer to continue this discussion on rice’s mailing list, to
avoid project-specific clutter in ruby-talk, but I’m fine to continue it
here as well.

Jason

Thanks for the suggestions.

I now have this working by doing the following:

  • Removed the extern “C” as Jason suggested
  • Moved my code into a dynamic library (using XCode) and included
    references to the xtoolkit library and include files.
  • Ensured that the static library was being included during compilation
    in my extconf.rb then checking that it is loaded correctly:

require ‘rubygems’
require ‘mkmf-rice’
$LIBS << ’ -lXternalDebug’
have_library(“stdc++”)
have_library(“XternalDebug”)
dir_config(“xtoolkit”)
dir_config(“mytest”)
have_library(“MyTest”)
have_header(“MyTest.h”)
create_makefile(“my_test”)

Added the XternalDebug to my LD_LIBRARY_PATH and my MyTest dynamic
library to DYLD_LIBRARY_PATH

I run the extconf.rb with the following arguments:
ARCHFLAGS="-arch i386" ruby extconf.rb
–with-mytest-include=/Users//mytest/MyTest
–with-mytest-lib=/Users//mytest/MyTest
–with-xtoolkit-include=/Users//xtoolkit/public/include

Kind regards,
Lea