Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling C code from within Ruby -- How to use the returned pointer?

Problem:

I'd like to use in my Ruby program an algorithm that is coded in C and exposed through a DLL.

I would like to treat the algorithm as a black box that I can call from within Ruby -- simply pass in the required parameters and use the result.

Ruby (both 1.8.7 and 1.9.3) has the Win32API module which seems intended to make it quite easy to interface with dynamic libraries to do exactly what I'm after.

But the problem is that I can't seem to get the Win32API call to send back a string.

Details:

The third-party C function is CodeGen(). It take 6 parameters, including a source string, an arbitrary string to serve as encryption key, and, for simplicity, 4 numerical parameters, one signed int, one unsigned long, and two unsigned shorts. From these, CodeGen() implements a black-box algorithm to return a resulting string.

The C prototype for CodeGen() is:

const char *CodeGen(  int encryp_level, 
                     const char *source_str, const char *encryp_key,
                     unsigned long param_a, 
                     unsigned short param_b, unsigned short param_c
                  )

Note that both the input strings are constants, i.e. they are supplied to CodeGen() as strings -- so pointers to constant strings

The return value for CodeGen() is also a string, of fixed maximum length, so it will return a pointer.

My Question:

How do I go about setting up the call to CodeGen() and getting back the string it is supposed to generate?

My attempts:

The code below simply gives me integers as the return value, when I am expecting to obtain a string.

require 'Win32API'

codeGen = Win32API.new("encrypt.dll", "CodeGen", "ISSIII", "S")

ret_str = codeGen.Call(3, "foo", "bar", 0, 0, 0)

puts ret_str

However, instead of getting a string back, I get back an integer. Edit: Could this be a pointer?

Although I'm using Ruby 1.9.3 on Windows 7, 64-bit edition, I've also tested the above on Windows XP, 32-bits, and using Ruby 1.8.7, so I'm pretty sure it's something to do with my use of the Win32API itself.

Not sure whether the problem is any of these:

  • do the integers (3, 0, ...) need to be packed?
  • do I need to distinguish between short and long types?
  • am I not properly handling the return value?
  • if the return value is a pointer, how do I use this in Ruby?
  • something else?

Any insight would be much appreciated!

like image 735
Assad Ebrahim Avatar asked Jan 05 '13 05:01

Assad Ebrahim


People also ask

How do I pass a C object to a Ruby Object?

Instead, your C code will store and pass around pointers to Ruby objects (like how variables in Ruby contain pointers to objects). These pointers can be passed to various API functions and macros that will safely access and manipulate the Ruby objects. VALUE is the API-defined C type for these pointers.

Do I need to know C to use Ruby API?

The Ruby source uses some fairly sophisticated C, so you should at least feel comfortable reading it. You can think of the C API is being a big, clunky alternative to writing normal Ruby code. However the simple, elegant patterns of Ruby can be pretty unintuitive once translated into the language of the API.

What is the Ruby interpreter written in C?

The official Ruby interpreter is written in C. That means that everything you can do in Ruby, you can also do using function calls to Ruby’s C API. Why in the world would you do this? There are two good reasons: You’re writing some fancy application in C or C++ and you want some parts of your code to leverage the dynamic flexibility of Ruby.

How to return a pointer from a function in C?

Static Variables have a property of preserving their value even after they are out of their scope. So to execute the concept of returning a pointer from function in C you must define the local variable as a static variable. Want to learn from the best curated videos and practice problems, check out the C Foundation Course for Basic to Advanced C.


1 Answers

While I don't know why the Win32API approach did not work, I've found an easier solution using FFI to the problem of calling C functions from within Ruby or interfacing with DLLs.


Solution using FFI

Use FFI in Ruby for interfacing with DLLs, as follows:

  • (1) Install ffi (works with ruby 1.9.3, ymmv with previous versions)

    gem install ffi

  • (2) Create your own Ruby module to wrap the C function

require 'ffi'

module CodeGen                   # Ruby wrapper  (your choice)
  extend FFI::Library
  ffi_lib 'codegen'              # DLL name  (given)
  attach_function 
        :create_code,            # method name  (your choice)
        :CreateCodeShort3,       # DLL function name (given)
          [ :int, :string, :string, :uint, :ushort, :ushort], :string  
                          # specify C param / return value types 
end

ret_str = CodeGen.create_code(3, "foo", "bar", 0,0,0)

puts ret_str
  • (3) Run. Result gives a string as desired.

Done!

like image 189
Assad Ebrahim Avatar answered Oct 04 '22 07:10

Assad Ebrahim