Use another language to call a function: Difference between revisions

Add Ruby using Fiddle and MRI.
(Add an easy C++ answer.)
(Add Ruby using Fiddle and MRI.)
Line 1,027:
 
The output is the expected “<tt>Here I am</tt>” line.
 
=={{header|Ruby}}==
We have four files. First, ''main.c'' has the <code>main()</code> from the top of this page. Second, ''query.rb'' uses Fiddle to create a C function at run time. It sets the C variable <code>QueryPointer</code> to this function. It needs at least Ruby 2.3 for <code>Array#unpack('J')</code>. Our <code>main()</code> can't call <code>QueryPointer()</code>, because it expects <code>Query()</code> to exist at link time, and it would crash if Ruby raised an error. The third file ''query-rb.c'' provides <code>Query()</code> as a wrapper around <code>QueryPointer()</code>. The wrapper embeds the Ruby interpreter and protects against Ruby errors. The fourth file ''Rakefile'' builds the program.
 
{{works with|MRI|2.3+}}
 
<lang ruby># query.rb
require 'fiddle'
 
# Look for a C variable named QueryPointer.
# Raise an error if it is missing.
c_var = Fiddle.dlopen(nil)['QueryPointer']
 
int = Fiddle::TYPE_INT
voidp = Fiddle::TYPE_VOIDP
sz_voidp = Fiddle::SIZEOF_VOIDP
 
# Implement the C function
# int Query(void *data, size_t *length)
# in Ruby code. Store it in a global constant in Ruby (named Query)
# to protect it from Ruby's garbage collector.
#
Query = Fiddle::Closure::BlockCaller
.new(int, [voidp, voidp]) do |datap, lengthp|
message = "Here am I"
 
# We got datap and lengthp as Fiddle::Pointer objects.
# Read length, assuming sizeof(size_t) == sizeof(void *).
length = lengthp[0, sz_voidp].unpack('J').first
 
# Does the message fit in length bytes?
if length < message.bytesize
0 # failure
else
length = message.bytesize
datap[0, length] = message # Copy the message.
lengthp[0, sz_voidp] = [length].pack('J') # Update the length.
1 # success
end
end
 
# Set the C variable to our Query.
Fiddle::Pointer.new(c_var)[0, sz_voidp] = [Query.to_i].pack('J')</lang>
 
<lang c>/* query-rb.c */
#include <stdlib.h>
#include <ruby.h>
 
/*
* QueryPointer() uses Ruby and may raise a Ruby error. Query() is a
* C wrapper around QueryPointer() that loads Ruby, sets QueryPointer,
* and protects against Ruby errors.
*/
int (*QueryPointer)(char *, size_t *) = NULL;
 
static int in_bad_exit = 0;
 
static void
do_at_exit(void)
{
RUBY_INIT_STACK;
 
if (!in_bad_exit)
ruby_cleanup(0);
}
 
static void
bad_exit(int state)
{
in_bad_exit = 1;
ruby_stop(state); /* Clean up Ruby and exit the process. */
}
 
static void
require_query(void)
{
static int done = 0;
int state;
 
if (done)
return;
done = 1;
 
ruby_init();
atexit(do_at_exit);
ruby_init_loadpath(); /* needed to require 'fiddle' */
 
/* Require query.rb in current directory. */
rb_eval_string_protect("require_relative 'query'", &state);
if (!state && !QueryPointer)
rb_eval_string_protect("fail 'missing QueryPointer'", &state);
if (state)
bad_exit(state); /* Ruby will report the error. */
}
 
struct args {
char *data;
size_t *length;
int result;
};
 
static VALUE
Query1(VALUE v) {
struct args *a = (struct args *)v;
a->result = QueryPointer(a->data, a->length);
return Qnil;
}
 
int
Query(char *data, size_t *length)
{
struct args a;
int state;
RUBY_INIT_STACK;
 
require_query();
 
/* Call QueryPointer(), protect against errors. */
a.data = data;
a.length = length;
rb_protect(Query1, (VALUE)&a, &state);
if (state)
bad_exit(state);
return a.result;
}</lang>
 
<lang ruby># Rakefile
 
# To build and run:
# $ rake
# $ ./callruby
 
# Must link with cc -Wl,-E so query.c exports QueryPointer.
CC = ENV.fetch('CC', 'cc')
LDFLAGS = '-Wl,-E'
CPPFLAGS = RbConfig.expand('-I$(rubyarchhdrdir) -I$(rubyhdrdir)')
LIBS = RbConfig.expand('$(LIBRUBYARG) $(LIBS)')
 
task 'default' => 'callruby'
 
desc 'compiles callruby'
file 'callruby' => %w[main.o query-rb.o] do |t|
sh "#{CC} #{LDFLAGS} -o #{t.name} #{t.sources.join(' ')} #{LIBS}"
end
 
rule '.o' => %w[.c] do |t|
sh "#{CC} #{CPPFLAGS} -o #{t.name} -c #{t.source}"
end
 
desc 'removes callruby and .o files'
task 'clean' do
rm_f %w[callruby main.o query-rb.o]
end</lang>
 
=={{header|Tcl}}==
Anonymous user