12 Temmuz 2019 Cuma

Paylaşımlı bir C++ kütüphanesi yazıp onu LuaJIT'de yükleme

Bu öğreticide C ve C++ dilinde paylaşımlı bir kütüphanenin nasıl oluşturulacağını ve LuaJIT'de dış fonksiyon arayüzünü (FFI) kullanarak onun nasıl yükleneceğini ele alacağız .
GitHub'daki şu linkte(https://github.com/cslarsen/luajit-cpp) örnek kodlar mevcut.



LuaJIT'i kullanmanın belirgin sonuçları: Normal Lua'dan çok daha hızlı - yüz kat daha hızlı - ve FFI üzerinden paylaşımlı kütüphaneleri yüklemek gibi gerçekten güzel bir imkan sağlamasıdır.


Öncelikle, hibrid bir C ve C++ paylaşımlı kütüphane oluşturarak başlayalım . Bu, hem C hem de C++ derleyicisi kullanabileceğiniz anlamına gelir.

Aşağıdaki kodları first.cpp veya first.c adlı bir dosyaya yerleştirin:

#ifdef __cplusplus
extern "C"
#endif
int add(int a, int b)
{
  return a + b;
}

Derleme yaparken pozisyondan bağımsız çalıştırılabilir kod(birincil adresin herhangi bir yerine yerleştirilebilen, mutlak adresinden bağımsız olarak sorunsuz şekilde çalışan kod) üretmek için -fPIC , paylaşımlı kütüphane oluşturmak için -shared  ve çıktı dosyasını belirtmek için -o libfirst.so(ya da OS X için libfirst.dylib) parametreleri geçilir.

$ g++ -W -Wall -g -fPIC -shared -o libfirst.so first.cpp

Programın C versiyonu için şöyle olacak:

$ gcc -W -Wall -g -fPIC -shared -o libfirst.so first.c

Artık libfoo.so oluşturduk onu inceleyebiliriz.

$ file libfirst.so
libfirst.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),
dynamically linked, not stripped

Sembollerini listeleyebiliriz:

$ nm --defined-only libfirst.so | grep add
000000000000052a T add

Eğer libfirst.so 'dan hata ayıklama sembollerini kaldırırsak nm kullanamayız ama objdump kullanabiliriz.

$ strip libfirst.so
$ objdump -T libfirst.so

libfirst.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
00000000000004b8 l    d  .init  0000000000000000              .init
...
00000000000005ba g    DF .text  0000000000000015  Base        add
...

add fonksiyonunun imzasını(prototipini) da biliyoruz zaten:

int add(int, int)

LuaJIT'i kullanarak paylaşımlı bir kütüphaneyi yüklememiz de gereken tüm şey bu. İlkin luajit.exe'de deneyelim.

$luajit.exe
> ffi = require("ffi")
> first = ffi.load("libfirst.so")

ffi.cdef 'e add fonksiyonu için girdi argümanlarını bilmeye gerek olmadan fonksiyon prototipini(imzasını) geçiyoruz basitçe:

> ffi.cdef("int add(int, int);")

Artık add 'ı çağırabiliriz:

> io.write(first.add(11, 22) .. "\n")
33

İşte bu kadar kolay. Python kullanıcıları için benzer işlevsellik sunan ( ctypes olmayan C imzalarını otomatik olarak ayrıştıramaması hariç) pypy ile gelen cffi modülü var . Diğer birçok dilde de var. Chicken Scheme bile C kodunu ayrıştırır. Aradaki fark, C ayrıştırıcılarının gelişmişlik derecesidir.

Peki niye C ile etkileşime geçiyorsunuz? Bence çok açık. Platforma özgü en keskin fonksiyonlar C'de mevcut olmasıdır. İyi bir FFI kütüphanesine sahip olmak LuaJIT'den bunlara kolayca erişebileceğiniz anlamına gelir. SDL,Zlib vb gibi kütüphanelerle bağlantı sağlamayı da kolaylaştırır.

LuaJIT'de C++ nesnelerini kullanma

Bir sonraki adım C ++ nesnelerini kullanmaktır. Bunu yapmanın birçok yolu var, sadece birine odaklanacağım. İlkin foo.cpp dosyasında bir C ++ sınıfı oluşturacağız sonra LuaJIT'den kullanacağımız bir C arayüzü aracılığıyla ona atıf da bulunacağız. Nihayetinde LuaJIT'de nesne-benzer bir şeye onu evireceğiz.

foo.cpp şöyle:

class Person {
public:
  Person(const std::string& name_,
         const int age_):
    name(name_),
    age(age_)
  {
  }

  const std::string name;
  const int age;
};

C için şunun gibi olur du:

extern "C" void* new_person(const char* name, int age)
{
  assert(name != NULL);
  Person *p = new Person(name, age);
  return reinterpret_cast<void*>(p);
}

extern "C" void delete_person(Person* p)
{
  delete p;
}

extern "C" int age(const Person* p)
{
  assert(p != NULL);
  return p->age;
}

extern "C" char* name(const Person* p)
{
  assert(p != NULL);
  return strdup(p->name.c_str());
}

name'deki strdup fonksiyonunun sisteminizde bulunma garantisi olmadığını unutmayın. Devamdaki gibi bir şey ile kendiniz halledebilirsiniz:

static char* strdup(const char* in)
{
  assert(s != NULL);
  char *out = (char*) malloc(strlen(in)+1);
  return strcpy(out, in);
}

LuaJIT'in FFI kütüphanesine fonksiyon imzalarının ne olduğunu söylememiz gerekir:

ffi.cdef[[
  /* kendi kütüphanemizden */
  typedef struct Person Person;
  Person* new_person(const char* name, const int age);
  char* name(const Person* p);
  int age(const Person* p);

  /* C kütüphanesinden */
  void free(void*);
]]

Bunu bir numara çevirerek nesne benzeri bir yapıya bürüyeceğiz:

local PersonWrapper = {}
PersonWrapper.__index = PersonWrapper

local function Person(...)
  local self = {super = foo.new_person(...)}
  ffi.gc(self.super, foo.delete_person)
  return setmetatable(self, PersonWrapper)
end

ffi.gc 'e pointer'ı ve bu pointer'ı iade edecek foo.delete_person 'i geçtiğimize emin olun.

name fonksiyon için malloc kullanarak yeni ayrılan string'i elde ederiz bu yüzden heap'den tekrar onu kaldırmak için c.free 'i kullanmaya gerek duymayız. C kütüphanesini yüklemek için:

C = ffi.C

name ve age için şimdi paketleme uygulayabiliriz:

function PersonWrapper.name(self)
  local name = foo.name(self.super)
  ffi.gc(name, C.free)
  return ffi.string(name)
end

function PersonWrapper.age(self)
  return foo.age(self.super)
end


Nihayetinde:

local ffi = ffi.require("ffi")
local foo = ffi.load("libfoo.so")

-- Yukardaki kodu buraya ekle

local person = Person("Mark Twain", 74)
io.write(string.format("'%s' is %d years old\n",
                       person:name(),
                       person:age()))

Çalıştıralım:

$luajit.exe foo.lua
'Mark Twain' is 74 years old

Hiç yorum yok:

Yorum Gönder