Category Archives: C

Create ruby/jruby bindings of C++/C with ffi

Let’s start with the basic. What are bindings? It’s a way to call low level libraries (normally in C or C++) from another language (high level one, like ruby, java or whatever). This simple step requires two important things: 1) first, to know how we should call the method itself and 2) second, how to map the type from the high level language to C primitives types. To accomplish this, we have what is called foreign function interface which trivialize this task.

In ruby/jruby world we are gonna need ffi gem (jruby also has a compatible ffi gem), besides that the most important part is to have a clear interface in C. You can do bindings for a C++ library if you create a C interface first (because the bindings between C++ and C are free, the compiler knows how to do it by itself). So let’s cut the crap and write some code

First our C++ library which can be this few lines or thousands:

class awesome_object {
  public:
    awesome_object(int a, int b) : a_(a), b_(b) {}
 
    int awesome_method() {
      return a_ + b_;
    }
 
  protected:
    int a_, b_;
}

Now the C bindings which will be our façade for our c++ library:

extern "C" {
  typedef awesome_object_p void *;
 
  awesome_object_p create_awesome_object(int a, int b) {
    return static_cast<void *>(new awesome_object(a, b));
  }
 
  void delete_pointer_to_awesome_object(awesome_object_p p) {
    delete static_cast<awesome_object *>(p);
  }
 
  int awesome_method(awesome_object_p p) {
    awesome_object o = *static_cast<awesome_object *>(p);
    return o.awesome_method();
  }
}

Now ruby code that use ffi gem to be able to call the methods defined in C:

require 'ffi'
 
module AwesomeObject
  include FFI
  extend FFI::Library
  ffi_lib "libawesome.so" # .dll if you are in windows, it doesn't matter the OS
  attach_function "delete_pointer_to_awesome_object", "delete_pointer_to_awesome_object", [:pointer], :void
  attach_function "awesome_method", "awesome_method", [:pointer], :int
  attach_function "create_awesome_object", "create_awesome_object", [:int, :int], :pointer
end

With this you can use your C++ library from ruby code (I’d recommend package everything as a gem) just like this:

class MyTest
  include AwesomeObject
  def test!
    object = create_awesome_object(11, 31)
    awesome_method object
    delete_pointer_to_awesome_object object
  end
end
MyTest.new.test!

So, if you have read until now, probably you just want something working, say no more. Download this tar.gz, and see all this by yourself. In the tar you have the code split into c/h cpp/hpp files as it should be, in the post I’ve put all together to simplify things. Just execute make test and if you have ruby, ffi gem and g++ installed on your system, you’ll see something like:

$ make test
g++ -g -Wall -fPIC -c awesome_object.cpp
g++ -g -Wall -fPIC -c awesome.c
g++ -g -Wall -fPIC -shared -o libawesome.so awesome.o awesome_object.o
ruby test.rb
42

Convertir Subs

Bueno siempre estoy poniendo post de series o personales y esto parece casi un blog rosa xD. Vamos a poner algo interesante esta vez.

Me estoy bajando una serie (Gilmore Girls), y resulta que los subs que hay en español estan realizados para un video de 25fps (video en español vamos), diría que los que los hicieron no fueron muy agiles, pues el video que hay compartido en la mula es claramente los dvd’s en ingles y si quieres subs lo lógico es que sean para el video en ingles, el cual esta a diferente fps, por lo tanto habrá desincronización.

La solución es convertir los tiempos, hay un programa para windows que lo hace, pero como no quiero usar windows, me he hecho un programita simple, pero útil. Es en ANSI C y se compila con un tipico “gcc fichero.c -o NombreEjecutable” luego para usarlo simplemente “NombreEjecutable FICHERO_ENTRADA FICHERO_SALIDA”. Hay un par de Define’s que se pueden variar a necesidad. Yo ya he convertido todos los subtitulos y van ahora a la perfeción :). Aqui pego el código, no creo que alguien lo quiera pero ahi queda xD. Por supuesto permito copiarlo y tal, pero vamos que son cuatro lineas xD.


#include
#include
#include
/********************************************/
#define FPS_IN 25.0 // FPS ENTRADA
#define FPS_OUT 23.978 // FPS SALIDA
/********************************************/
int lookAhead(FILE *F, int n){
// PRE: F=
// POST: lookAhead=d_x AND F=
int c;
for(c=1;c c = fgetc(F);
fseek(F, -n, SEEK_CUR);
return c;
}
int end_of_file(FILE *F){
// PRE: F=
// POST: end_of_file = (x>n)
return (0xffffffff==lookAhead(F,1));
}
int end_of_line(FILE *F){
// indica si estamos final de linea, valido para formato WIN/UNIX/MAX
int c;
c = lookAhead(F,1);
if (c == 0x0D) c = lookAhead(F, 2);
return (c == 0x0A);
}
void skip_line(FILE *F){
// salta la linea actual, valido para formato WIN/UNIX/MAX
int c;
while (!end_of_line(F))
fgetc(F);
if (end_of_line(F)){
c = fgetc(F);
if (c == 0x0D) c = fgetc(F);
}
}
int esNum(int character){
return (character>=48 && character<=57);
}
int leeNum(FILE *F, int n){
int veces;
int ok=1;
for(veces=0; veces if (!esNum(fgetc(F)))
ok = 0;
return ok;
}
char linea_OK(FILE *F){
// comprueba si estamos en una linea que indica "tiempo"
// segun el formato de ficheros *.SRT
int i;
int leidos = 0;
for (i=1;i<=3;i++){
leidos += 2;
if (!leeNum(F, 2)){
fseek(F, -leidos, SEEK_CUR);
return 0;
}
if ((i=lookAhead(F,1)) == ':'){
leidos++;
fgetc(F);
}else if ((i == ',') && (i==3)){
leidos++;
fgetc(F);
}else{
fseek(F, -leidos, SEEK_CUR);
return 0;
}
}
fseek(F, -leidos, SEEK_CUR);
return 1;
}
void new_line(FILE *out){
fputc(0x0D, out); fputc(0x0A,out);
}
void clonaLinea(FILE *in, FILE *out){
while(!end_of_line(in))
fputc(fgetc(in), out);
skip_line(in);
new_line(out);
}
void pilla_tiempo(FILE *F, int *h, int *m, int *s, int *ms){
char num[3];
num[2]=0;
fread(num, sizeof(char), 2, F);
(*h)=atoi(num);
fgetc(F);
fread(num, sizeof(char), 2, F);
(*m)=atoi(num);
fgetc(F);
fread(num, sizeof(char), 2, F);
(*s)=atoi(num);
fgetc(F);
fread(num, sizeof(char), 3, F);
(*ms)=atoi(num);
}
void pon_tiempo(FILE *F, int h, int m, int s, float ms){
char cad[5];
cad[2]=0;
sprintf(cad, "%d",h);
if (!cad[1]){ cad[1]=cad[0]; cad[0]='0'; cad[2]=0;}
fwrite(cad, 2,sizeof(char), F);
fwrite(":", 1,sizeof(char), F);
sprintf(cad,"%d",m);
if (!cad[1]){ cad[1]=cad[0]; cad[0]='0'; cad[2]=0;}
fwrite(cad, 2,sizeof(char), F);
fwrite(":", 1,sizeof(char), F);
sprintf(cad,"%d",s);
if (!cad[1]){ cad[1]=cad[0]; cad[0]='0'; cad[2]=0;}
fwrite(cad, 2,sizeof(char), F);
fwrite(",", 1,sizeof(char), F);
sprintf(cad,"%.3f",ms);
fwrite(cad+2, 3,sizeof(char), F);
}
void convertir(int h, int m, int s, int ms, int *h2, int *m2, int *s2, float *ms2){
float segT=0.0;
segT = (float)(ms)/1000.0 + (float)(s)+60.0*m + 3600.0*h;
segT *= (float)(FPS_IN) / (float)(FPS_OUT);
(*h2) = (int)(segT / 3600.0);
segT -= *h2*3600.0;
(*m2) = (int)(segT / 60.0); segT -= *m2*60.0;
(*s2) = (int)(segT); segT -= *s2*1.0;
(*ms2) = segT;
}
void Convierte_Linea(FILE *F, FILE *Fout){
int h,h2,m2,s2,m,s,ms,i;
float ms2;
pilla_tiempo(F, &h, &m, &s, &ms);
convertir(h,m,s,ms, &h2, &m2, &s2, &ms2);
pon_tiempo(Fout, h2, m2, s2, ms2);
for(i=0;i<5;i++,fgetc(F));
fwrite(" --> ", 5, sizeof(char), Fout);
pilla_tiempo(F, &h, &m, &s, &ms);
convertir(h,m,s,ms, &h2, &m2, &s2, &ms2);
pon_tiempo(Fout, h2, m2, s2, ms2);
new_line(Fout); skip_line(F);
}
void abrirFicheros(FILE *in, FILE *out, char *f1, char *f2){
in=fopen(f1, "r");
out = fopen(f2, "w");
}
int main(int argc, char **argv){
FILE *F, *Fout;
if (argc != 3 && argc != 2){
printf("ERROR:\tUSO %s SUB_ENTRADA.srt SUB_SALIDA.srt\n", argv[0]);
exit(1);
}else
abrirFicheros( F,
Fout,
argv[1],
(argc==3 ? argv[2] : strcat(argv[1], ".srt"))
);
while (!end_of_file(F))
if (linea_OK(F))
Convierte_Linea(F, Fout);
else
clonaLinea(F, Fout);
fclose(F); fclose(Fout);
}

Codigo subs.c