Coding Ruby Extension with C++ on Win32

D

daniel åkerud

[Note: parts of this message were removed to make it a legal post.]

Here are some hurdles that I overcame when coding a Ruby Extension in C++ on
Win32. Feel free to add to this or to comment. I hope it will help someone
someday. (If you ask WHY?, my answer is: I'm very comfy with STL).

1. For Win32, mkmf generates makefiles for Visual Studio. In other words,
"make" won't work for you on Win32, you have to type "nmake". I used VC6.
And to use C++, the files you compile must be named ".cpp", not ".c" so that
they are compiled as c++ files instead of c files. Perhaps .cc extension
works, I didn't try.

2. Your static functions cannot be passed down to rb_define_method,
rb_define_global_function etc. The solution is not obvious. My first
thought, extern "C", __cdecl/__stdcall etc, does not do _anything_ to solve
it. You have to do the following (found somewhere on net):

// first a typedef
typedef VALUE (*HOOK)(...);
// then do a reinterpret_cast:
rb_define_method(rb_cYadaYada, "initialize",
reinterpret_cast<HOOK>(yadayada_initialize), 2);

3. Destructors in the functions are not called if ruby long jumps out! This
happens when you raise an exception. Here is an example using the classical
Lock-A-Mutex when the object is created, and Unlock-A-Mutex when the
destructor is called:

Lock my_lock(mutex);

connected = rb_iv_get(self, "@connected");

if(connected != Qtrue)
{
rb_raise(rb_eRuntimeError, YadaYada is not connected");
}

When the exception is raised, the destructor of my_lock WILL NOT BE CALLED
so the Mutex is not unlocked. Nasty!

4. You have to #include <string> before <ruby.h>. Otherwise you will get a
lot of compile errors when #include:ing <string>. This might apply to other
include files, but it seemed not to apply to <map> and <list>.

Cheers/D
 
K

Ken Bloom

[Note: parts of this message were removed to make it a legal post.]

Here are some hurdles that I overcame when coding a Ruby Extension in
C++ on Win32. Feel free to add to this or to comment. I hope it will
help someone someday. (If you ask WHY?, my answer is: I'm very comfy
with STL).

1. For Win32, mkmf generates makefiles for Visual Studio. In other
words, "make" won't work for you on Win32, you have to type "nmake". I
used VC6. And to use C++, the files you compile must be named ".cpp",
not ".c" so that they are compiled as c++ files instead of c files.
Perhaps .cc extension works, I didn't try.

2. Your static functions cannot be passed down to rb_define_method,
rb_define_global_function etc. The solution is not obvious. My first
thought, extern "C", __cdecl/__stdcall etc, does not do _anything_ to
solve it. You have to do the following (found somewhere on net):

// first a typedef
typedef VALUE (*HOOK)(...);
// then do a reinterpret_cast:
rb_define_method(rb_cYadaYada, "initialize",
reinterpret_cast<HOOK>(yadayada_initialize), 2);

3. Destructors in the functions are not called if ruby long jumps out!
This happens when you raise an exception. Here is an example using the
classical Lock-A-Mutex when the object is created, and Unlock-A-Mutex
when the destructor is called:

Lock my_lock(mutex);

connected = rb_iv_get(self, "@connected");

if(connected != Qtrue)
{
rb_raise(rb_eRuntimeError, YadaYada is not connected");
}

When the exception is raised, the destructor of my_lock WILL NOT BE
CALLED so the Mutex is not unlocked. Nasty!

4. You have to #include <string> before <ruby.h>. Otherwise you will get
a lot of compile errors when #include:ing <string>. This might apply to
other include files, but it seemed not to apply to <map> and <list>.

Cheers/D

Try writing the whole OO interface in C++, and letting SWIG do the
mapping into ruby for you. SWIG knows (with appropriate includes) about
std::vector (which maps to Array) and std::string (which maps to String).

--Ken
 
P

Paul Brannan

// first a typedef
typedef VALUE (*HOOK)(...);
// then do a reinterpret_cast:
rb_define_method(rb_cYadaYada, "initialize",
reinterpret_cast<HOOK>(yadayada_initialize), 2);

The problem is not that the methods are static; it's that they don't
have the same signature as the rb_define_method function expects. You
can cast to the right type using the RUBY_METHOD_FUNC() macro (this is
what Rice does - see http://rice.rubyforge.org - though it doesn't work
with a compiler as old as VC6).

Some compilers also check the calling convention (e.g. comeau).
Unfortunately, C++ provides no mechanism to specify both the calling
convention *and* the linkage, so if your compiler uses a different
calling convention for C and C++ code, you *have* to make your wrapper
functions extern "C" (which is a nuisance, since it means it's
impossible to have internal linkage and C calling convention on such
compilers).
3. Destructors in the functions are not called if ruby long jumps out! This
happens when you raise an exception.

IIRC the C++ standard does not specify what happens if you longjmp over
code with objects on the stack that have destructors with non-trivial
behavior. In other words, your destructor might get called, or it might
not.

The solution here is to use rb_protect whenever you call into Ruby code.
This is what Rice does under the hood.

There's a similar problem with allowing C++ exceptions to escape into
Ruby-land; on most compilers I've used, this causes std::terminate to
get called.
4. You have to #include <string> before <ruby.h>. Otherwise you will get a
lot of compile errors when #include:ing <string>. This might apply to other
include files, but it seemed not to apply to <map> and <list>.

I've not experienced this problem. What kinds of errors do you get?

Paul
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top