रूबी मैजिक के इस संस्करण में, हम आपको दिखाएंगे कि रूबी से सी में लिखे गए कोड का उपयोग कैसे करें। इसका उपयोग आपके कोड के प्रदर्शन संवेदनशील भागों को अनुकूलित करने या C लाइब्रेरी और रूबी के बीच एक इंटरफ़ेस बनाने के लिए किया जा सकता है। यह एक्सटेंशन बनाकर किया जाता है जो सी में लिखे गए पुस्तकालयों को लपेटते हैं।
सी में बहुत सारे परिपक्व और प्रदर्शन करने वाले पुस्तकालय लिखे गए हैं। पहिए को फिर से पोर्ट करने के बजाय हम रूबी से इन पुस्तकालयों का भी लाभ उठा सकते हैं। इस तरह, हम अपनी पसंदीदा भाषा में कोड प्राप्त करते हैं, जबकि उन क्षेत्रों में सी पुस्तकालयों का उपयोग करते हैं जहां रूबी पारंपरिक रूप से मजबूत नहीं है। AppSignal में, हमने इस दृष्टिकोण का उपयोग rdkafka रत्न को विकसित करने में किया है।
तो आइए देखें कि कोई इस तक कैसे पहुंच सकता है। यदि आप साथ चलना चाहते हैं और स्वयं प्रयोग करना चाहते हैं, तो उदाहरण कोड देखें। शुरू करने के लिए, आइए रूबी कोड के इस टुकड़े को एक स्ट्रिंग, एक संख्या और एक बूलियन के साथ लें (आप सी क्यों, पन इरादा करेंगे) और इसे सी लाइब्रेरी में पोर्ट करें:
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 में पोर्ट किया गया
नीचे, आप सी में पोर्ट किए गए कोड को देख सकते हैं। सी मानक पुस्तकालय और आईओ पुस्तकालय शामिल हैं ताकि हम स्ट्रिंग स्वरूपण का उपयोग कर सकें। हम 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 बाइट्स से अधिक लंबी है, तो खतरनाक बफर ओवरफ्लो होगा। सी लिखते समय आपको निश्चित रूप से सावधान रहना चाहिए, अपने आप को पैर में गोली मारना आसान है।
आगे एक हेडर फ़ाइल है:
char* string_from_library(char*);
int number_from_library(int);
int boolean_from_library(int);
यह फ़ाइल हमारी सी लाइब्रेरी के सार्वजनिक एपीआई का वर्णन करती है। अन्य प्रोग्राम इसका उपयोग यह जानने के लिए करते हैं कि पुस्तकालय में किन कार्यों को बुलाया जा सकता है।
2018 का तरीका:ffi
. का इस्तेमाल करें रत्न
तो, अब हमारे पास एक सी लाइब्रेरी है जिसे हम रूबी से उपयोग करना चाहते हैं। इस सी कोड को मणि में लपेटने के दो तरीके हैं। आधुनिक तरीके में ffi
. का उपयोग करना शामिल है रत्न यह कई हुप्स को स्वचालित करता है जिनसे हमें कूदना है। ffi
का उपयोग करना सी कोड के साथ हमने अभी लिखा है:
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 एक्सटेंशन में लपेटना
तो अब हमारे पास एक सी लाइब्रेरी है जिसे हम रूबी से उपयोग करना चाहते हैं। अगला कदम एक रत्न बनाना है जो इसे संकलित और लपेटता है। रत्न बनाने के बाद, हम सबसे पहले ext
. जोड़ते हैं require_paths
. पर जेमस्पेक में:
Gem::Specification.new do |spec|
spec.name = "c_from_ruby_example"
# ...
spec.require_paths = ["lib", "ext"]
end
यह रूबीगेम्स को सूचित करता है कि एक मूल विस्तार है जिसे बनाने की आवश्यकता है। यह extconf.rb
नामक फ़ाइल की तलाश करेगा या एक Rakefile
. इस मामले में, हमने extconf.rb
. जोड़ा :
require "mkmf"
create_makefile "extension"
हमें mkmf
. की आवश्यकता है , जो "मेकफाइल" के लिए खड़ा है। यह रूबी के साथ शामिल सहायकों का एक सेट है जो सी बिल्ड सेट अप प्राप्त करने के जटिल हिस्से को समाप्त करता है। हम create_makefile
. कहते हैं और एक्सटेंशन के लिए एक नाम सेट करें। यह एक Makefile
बनाता है जिसमें सी कोड बनाने के लिए सभी कॉन्फ़िगरेशन और कमांड शामिल हैं।
इसके बाद, हमें लाइब्रेरी को रूबी से जोड़ने के लिए कुछ सी कोड लिखना होगा। हम कुछ ऐसे फंक्शन बनाएंगे जो C टाइप को कन्वर्ट करते हैं जैसे char*
रूबी प्रकारों जैसे String
. के लिए . फिर हम C कोड के साथ एक रूबी क्लास बनाएंगे।
सबसे पहले, हम रूबी से कुछ शीर्षलेख फ़ाइलें शामिल करते हैं। ये उन कार्यों को आयात करेंगे जिनकी हमें टाइप रूपांतरण करने की आवश्यकता है। हम 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);
}
हम पहले जांचते हैं कि रूबी मान एक स्ट्रिंग है या नहीं, क्योंकि गैर-स्ट्रिंग मान को संसाधित करने से सभी प्रकार की बग हो सकती है। फिर हम रूबी String
. को रूपांतरित करते हैं एक char*
. के लिए RSTRING_PTR
. के साथ रूबी प्रदान करता है कि सहायक मैक्रो। अब हम अपनी सी लाइब्रेरी को कॉल कर सकते हैं। लौटाए गए 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);
}
बूलियन संस्करण भी समान है। ध्यान दें कि सी में वास्तव में बूलियन प्रकार नहीं है। इसके बजाय सम्मेलन 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;
}
}
अंत में, हम सब कुछ तार-तार कर सकते हैं ताकि हम इसे रूबी से कॉल कर सकें:
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 कोड को कॉल कर सकते हैं:
CFromRubyExample::NativeHelpers.string("a string")
निष्कर्ष
हम हुप्स के माध्यम से कूद गए, दुर्घटनाग्रस्त नहीं हुए और हमारे सी एक्सटेंशन को काम करने के लिए मिला। सी एक्सटेंशन लिखना दिल के बेहोश होने के लिए नहीं है। ffi
. का उपयोग करते समय भी मणि आप अभी भी अपनी रूबी प्रक्रिया को आसानी से क्रैश कर सकते हैं। लेकिन यह संभव है और आपके लिए प्रदर्शनकारी और स्थिर सी पुस्तकालयों की दुनिया खोल सकता है!