ใน 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 ที่มีประสิทธิภาพและเสถียรให้กับคุณได้!