Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

ゼロからRubyC拡張機能を構築する

今回のRubyMagicでは、RubyからCで記述されたコードの使用方法を紹介します。これを使用して、コードのパフォーマンスに敏感な部分を最適化したり、CライブラリとRuby間のインターフェイスを作成したりできます。これは、Cで記述されたライブラリをラップする拡張機能を作成することによって行われます。

Cで書かれた成熟したパフォーマンスの高いライブラリがたくさんあります。それらを移植して車輪の再発明を行う代わりに、Rubyからこれらのライブラリを活用することもできます。このようにして、Rubyが従来は強力ではなかった領域でCライブラリを使用しながら、お気に入りの言語でコーディングすることができます。 AppSignalでは、このアプローチを使用してrdkafkagemを開発しました。

では、これにどのように取り組むことができるか見てみましょう。フォローして自分で実験したい場合は、サンプルコードを確認してください。まず、文字列、数値、ブール値(Cの理由、しゃれを意図したもの)を含むこのRubyコードを取得し、Cライブラリに移植します。

module CFromRubyExample
  class Helpers
    def self.string(value)
      "String: '#{value}'"
    end
 
    def self.number(value)
      value + 1
    end
 
    def self.boolean(value)
      !value
    end
  end
end

順番に、示されているメソッドは文字列を連結し、数値を1つ増やし、ブール値の反対を返します。

Cに移植されたライブラリ

以下に、Cに移植されたコードを示します。文字列フォーマットを使用できるように、C標準ライブラリとIOライブラリが含まれています。 char*を使用します RubyのStringの代わりに 。 char* メモリ内のどこかにある文字のバッファの場所を指します。

# include <stdlib.h>
# include <stdio.h>
 
char* string_from_library(char* value) {
  char* out = (char*)malloc(256 * sizeof(char));
  sprintf(out, "String: '%s'", value);
  return out;
}
 
int number_from_library(int value) {
  return value + 1;
}
 
int boolean_from_library(int value) {
  if (value == 0) {
    return 1;
  } else {
    return 0;
  }
}

ご覧のとおり、単純な文字列フォーマットを行うには、いくつかのフープをジャンプする必要があります。文字列を連結するには、最初にバッファを割り当てる必要があります。これが完了すると、sprintf 次に、関数はフォーマットされた結果をそれに書き込むことができます。最後に、バッファを返すことができます。

上記のコードでは、クラッシュまたはセキュリティの問題が発生する可能性があります。着信文字列が245バイトより長い場合、恐ろしいバッファオーバーフローが発生します。 Cを書くときは、絶対に注意する必要があります。足を撃ちやすいのです。

次はヘッダーファイルです:

char* string_from_library(char*);
int number_from_library(int);
int boolean_from_library(int);

このファイルは、CライブラリのパブリックAPIについて説明しています。他のプログラムは、ライブラリ内のどの関数を呼び出すことができるかを知るためにそれを使用します。

2018年の方法:ffiを使用する 宝石

これで、Rubyから使用したいCライブラリができました。このCコードをgemでラップする方法は2つあります。最新の方法では、ffiを使用します 宝石。それは私たちが飛び越えなければならないフープの多くを自動化します。 ffiの使用 今書いたCコードは次のようになります:

module CFromRubyExample
  class Helpers
    extend FFI::Library
 
    ffi_lib File.join(File.dirname(__FILE__), "../../ext/library.so")
 
    attach_function :string, [:string], :string
    attach_function :number, [:int], :int
    attach_function :boolean, [:int], :int
  end
end

この記事では、CコードをC拡張子でラップする方法についても説明します。これにより、Rubyの内部ですべてがどのように機能するかについてより多くの洞察が得られます。

ライブラリをC拡張機能でラップする

これで、Rubyから使用したいCライブラリができました。次のステップは、コンパイルしてラップするgemを作成することです。宝石を作成した後、最初にextを追加します require_pathsへ gemspec内:

Gem::Specification.new do |spec|
  spec.name          = "c_from_ruby_example"
  # ...
  spec.require_paths = ["lib", "ext"]
end

これは、ビルドする必要のあるネイティブ拡張があることをRubygemsに通知します。 extconf.rbというファイルを探します またはRakefile 。この場合、extconf.rbを追加しました :

require "mkmf"
 
create_makefile "extension"

mkmfが必要です 、「MakeMakefile」の略です。これは、Rubyに含まれている一連のヘルパーであり、Cビルドをセットアップする際の厄介な部分を排除します。 create_makefileと呼びます 拡張子の名前を設定します。これにより、Makefileが作成されます これには、Cコードをビルドするためのすべての構成とコマンドが含まれています。

次に、ライブラリをRubyに接続するためのCコードを作成する必要があります。 char*などのC型を変換するいくつかの関数を作成します StringなどのRuby型に 。次に、Cコードを使用してRubyクラスを作成します。

まず、Rubyのヘッダーファイルをいくつかインクルードします。これらは、型変換を行うために必要な関数をインポートします。 library.hも含まれています ライブラリを呼び出すことができるように、以前に作成したヘッダーファイル。

#include "ruby/ruby.h"
#include "ruby/encoding.h"
#include "library.h"

次に、ライブラリ内の各関数をラップする関数を作成します。これは文字列用のものです:

static VALUE string(VALUE self, VALUE value) {
  Check_Type(value, T_STRING);
 
  char* pointer_in = RSTRING_PTR(value);
  char* pointer_out = string_from_library(pointer_in);
  return rb_str_new2(pointer_out);
}

文字列以外の値を処理するとあらゆる種類のバグが発生する可能性があるため、最初に、入力されるRuby値が文字列であるかどうかを確認します。次に、RubyのStringを変換します char*RSTRING_PTRを使用 Rubyが提供するヘルパーマクロ。これで、Cライブラリを呼び出すことができます。返されたchar*を変換するには 、インクルードrb_str_new2を使用します 関数。数値とブール値に同様のラッピング関数を追加します。

数値については、NUM2INTを使用して同様のことを行います およびINT2NUM ヘルパー:

static VALUE number(VALUE self, VALUE value) {
  Check_Type(value, T_FIXNUM);
 
  int number_in = NUM2INT(value);
  int number_out = number_from_library(number_in);
  return INT2NUM(number_out);
}

ブールバージョンも同様です。 Cには実際にはブール型がないことに注意してください。慣例では、代わりに0と1を使用します。

static VALUE boolean(VALUE self, VALUE value) {
  int boolean_in = RTEST(value);
  int boolean_out = boolean_from_library(boolean_in);
  if (boolean_out == 1) {
    return Qtrue;
  } else {
    return Qfalse;
  }
}

最後に、Rubyから呼び出すことができるようにすべてを接続できます:

void Init_extension(void) {
  VALUE CFromRubyExample = rb_define_module("CFromRubyExample");
  VALUE NativeHelpers = rb_define_class_under(CFromRubyExample, "NativeHelpers", rb_cObject);
 
  rb_define_singleton_method(NativeHelpers, "string", string, 1);
  rb_define_singleton_method(NativeHelpers, "number", number, 1);
  rb_define_singleton_method(NativeHelpers, "boolean", boolean, 1);
}

はい、その通りです。CでRubyモジュール、クラス、メソッドを作成できます。ここでクラスを設定します。次に、Rubyメソッドをクラスに追加します。 Rubyメソッドの名前、呼び出されるCラッパー関数の名前、および引数の数を指定する必要があります。

すべての作業が終わったら、最終的にCコードを呼び出すことができます:

CFromRubyExample::NativeHelpers.string("a string")

結論

私たちはフープを飛び越え、クラッシュせず、C拡張機能を動作させました。 C拡張機能を書くことは、気の弱い人向けではありません。 ffiを使用している場合でも それでも、Rubyプロセスを非常に簡単にクラッシュさせることができます。しかし、それは実行可能であり、パフォーマンスが高く安定したCライブラリの世界を開くことができます!


  1. Rubyでの新しいプログラミング言語の構築:インタープリター

    Githubのフルソース Stoffleプログラミング言語の完全な実装は、GitHubで入手できます。バグを見つけたり質問がある場合は、遠慮なく問題を開いてください。 このブログ投稿では、完全にRubyで構築されたおもちゃのプログラミング言語であるStoffleのインタープリターの実装を開始します。このプロジェクトの詳細については、このシリーズの最初の部分をご覧ください。 これから作成するインタプリタは、一般にツリーウォークインタプリタと呼ばれます。このシリーズの前回の投稿では、トークンのフラットシーケンスをツリーデータ構造(抽象構文木、または略してAST)に変換するパーサーを構築しま

  2. Windows 11 を最初からインストールする方法 (USB を使用してインストール)

    Microsoft は、世界で最も使用されているコンピューター オペレーティング システムの新しいバージョンである Windows 11 をリリースしました。 Windows 11 オペレーティング システムの最新バージョンには、ゲーム特典、簡素化されたレイアウト、直感的なショートカットなど、多くの新機能が搭載されています。 Windows 11 の最小ハードウェア要件を満たす Windows 10 コンピューターをお持ちの場合は、無料で Windows 11 にアップグレードできます。 Windows 11 をインストールする方法もいくつかあります すでにお伝えしたとおりですが、あなたの場合