Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

การสร้างส่วนขยาย Ruby C ตั้งแต่เริ่มต้น

ใน Ruby Magic ฉบับนี้ เราจะแสดงวิธีใช้โค้ดที่เขียนด้วยภาษา C จาก Ruby สามารถใช้เพื่อเพิ่มประสิทธิภาพส่วนที่ละเอียดอ่อนของโค้ดของคุณ หรือเพื่อสร้างอินเทอร์เฟซระหว่างไลบรารี C และ Ruby ทำได้โดยการสร้างส่วนขยายที่ห่อไลบรารีที่เขียนด้วย C.

มีไลบรารีที่เติบโตเต็มที่และมีประสิทธิภาพจำนวนมากที่เขียนด้วยภาษาซี แทนที่จะสร้างวงล้อขึ้นมาใหม่โดยย้ายพวกมัน เรายังสามารถใช้ประโยชน์จากไลบรารีเหล่านี้จาก Ruby ได้อีกด้วย ด้วยวิธีนี้ เราจึงได้เขียนโค้ดในภาษาที่เราชื่นชอบ ในขณะที่ใช้ไลบรารี C ในพื้นที่ที่ Ruby ไม่ค่อยแข็งแรง ที่ AppSignal เราได้ใช้วิธีนี้ในการพัฒนา rdkafka gem

ลองดูว่าใครจะเข้าใกล้สิ่งนี้ได้อย่างไร หากคุณต้องการติดตามและทดลองด้วยตัวเอง ลองดูโค้ดตัวอย่าง ในการเริ่มต้น ให้นำรหัส Ruby ชิ้นนี้ที่มีสตริง ตัวเลข และบูลีน (คุณจะเป็น C เพราะอะไร ตั้งใจเล่น) และย้ายไปยังไลบรารี 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

ตามลำดับ วิธีการที่แสดงเชื่อมสตริง เพิ่มจำนวนขึ้นหนึ่งและส่งกลับค่าตรงข้ามของบูลีนตามลำดับ

ห้องสมุดของเราถูกย้ายไปที่ C

ด้านล่าง คุณสามารถดูรหัสที่พอร์ตไปยัง C ไลบรารีมาตรฐาน C และไลบรารี IO รวมอยู่ด้วย เพื่อให้เราสามารถใช้การจัดรูปแบบสตริงได้ เราใช้ char* แทนที่จะเป็นทับทิม 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);

ไฟล์นี้อธิบาย API สาธารณะของไลบรารี C ของเรา โปรแกรมอื่นใช้เพื่อให้ทราบว่าสามารถเรียกใช้ฟังก์ชันใดในไลบรารีได้

วิถีแห่งปี 2018:ใช้ ffi พลอย

ตอนนี้เรามีไลบรารี่ C ที่เราต้องการใช้จาก Ruby มีสองวิธีในการห่อโค้ด C นี้ในอัญมณี วิธีที่ทันสมัยเกี่ยวข้องกับการใช้ 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

ตอนนี้เรามีไลบรารี่ C ที่เราต้องการใช้จาก Ruby ขั้นตอนต่อไปคือการสร้างอัญมณีที่รวบรวมและห่อหุ้มไว้ หลังจากสร้างอัญมณีแล้ว ขั้นแรกให้เพิ่ม 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 ซึ่งย่อมาจาก "Make Makefile" เป็นชุดผู้ช่วยที่มาพร้อมกับ Ruby ที่ช่วยขจัดความยุ่งยากในการตั้งค่าบิลด์ C เราเรียก create_makefile และตั้งชื่อนามสกุล สิ่งนี้จะสร้าง Makefile ซึ่งมีการกำหนดค่าและคำสั่งทั้งหมดในการสร้างโค้ด C

ต่อไป เราต้องเขียนโค้ด C เพื่อเชื่อมต่อไลบรารี่กับ Ruby เราจะสร้างฟังก์ชันบางอย่างที่แปลงประเภท C เช่น char* ถึงประเภท Ruby เช่น String . จากนั้นเราจะสร้างคลาส Ruby ด้วยรหัส C

ก่อนอื่น เรารวมไฟล์ส่วนหัวบางส่วนจาก 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 ที่เข้ามานั้นเป็นสตริงหรือไม่ เนื่องจากการประมวลผลค่าที่ไม่ใช่สตริงอาจทำให้เกิดบั๊กทุกประเภท จากนั้นเราก็แปลงทับทิม 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);
}

ใช่ คุณอ่านถูกต้องแล้ว เราสามารถสร้างโมดูล คลาส และเมธอดของ Ruby ใน C ได้ เราตั้งค่าคลาสของเราที่นี่ จากนั้นเราเพิ่มวิธี Ruby ให้กับคลาส เราต้องระบุชื่อของวิธี Ruby ชื่อของฟังก์ชัน C wrapper ที่จะเรียกและระบุจำนวนอาร์กิวเมนต์

หลังจากทำงานเสร็จแล้ว เราก็สามารถเรียกรหัส C ของเราได้:

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

บทสรุป

เรากระโดดข้ามห่วง ไม่ชน และขยาย C ของเราให้ทำงาน การเขียนนามสกุล C นั้นไม่เหมาะสำหรับคนใจเสาะ แม้ในขณะที่ใช้ ffi อัญมณีที่คุณยังคงสามารถทำลายกระบวนการ Ruby ของคุณได้อย่างง่ายดาย แต่มันทำได้และสามารถเปิดโลกแห่งไลบรารี C ที่มีประสิทธิภาพและเสถียรให้กับคุณได้!