Lua'ı genişletmek için temel imkanlardan biri Lua içinde yeni C fonksiyonları kaydetme uygulamasıdır.
Lua'nın C fonksiyonlarını çağırabildiğini söylediğimizde bu Lua'nın herhangi C fonksiyonunu çağırabileceği anlamına gelmez(bunu sağlayan paketler var ama ne güvenli ne de taşınabilirler). Önceki bölümde gördüğümüz üzere C bir Lua fonksiyonunu çağırdığında argümanların geçilmesi ve sonuçların alınması için temel bir protokol izlemeliydi. Benzer şekilde Lua'dan bir C fonksiyonunun çağrılması için argümanlarının alınması ve sonuçlarının döndürülmesi için bir protokol izlenmeli. Dahası Lua'dan bir C fonksiyonunun çağrılması için onu kaydetmeliyiz yani adresini Lua'a uygun bir şekilde vermeliyiz.
Lua bir C fonksiyonunu çağırırken C'nin Lua'ı çağırmak için kullandığı aynı tür yığını kullanır. C fonksiyonu argümanlarını yığından alır ve sonuçları yığına koyar. Sonuçları yığındaki diğer değerlerden ayırmak için fonksiyon(C'de) yığına bırakılan sonuçların sayısını döndürür.
Buradaki önemli kavram, yığının global bir yapı olmadığıdır; Her fonksiyon kendi özel yerel yığınına sahiptir. Lua bir C fonksiyonu çağırdığında ilk argüman daima bu yerel yığının indeks 1'inde olacaktır. Bir C fonksiyonu aynı (veya başka) C fonksiyonu tekrar çağıran Lua kodunu çağırdığında bile bu çağrıların her biri yalnızca kendi özel yığınını görür, indeks 1'de ilk argümanıyla.
26.1 C Fonksiyonları
İlk örnek olarak verilen bir sayının sinüsünü döndüren bir fonksiyonun basitleştirilmiş bir sürümünü nasıl uygulayacağımızı görelim:
static int l_sin(lua_State *L) { double d = lua_tonumber(L, 1); /* argümanı al */ lua_pushnumber(L, sin(d)); /* sonucu push et */ return 1; /* sonuç sayısı */ }
Lua ile kaydedilen herhangi bir fonksiyon lua.h'daki lua_CFunction olarak tanımlananla aynı prototipe sahip olmalıdır:
typedef int (*lua_CFunction) (lua_State *L);
C bakış açısından, bir C fonksiyonu tek argümanı olarak Lua durumunu alır ve yığında döndürülen değer sayısıyla bir tamsayı döndürür. Bu şekilde fonksiyon sonuçlarını push etmeden önce yığını temizlemesine gerek yoktur. Döndükten sonra, Lua, sonuçların altında yığında ne varsa otomatik olarak kaldırır.
Lua'dan bu fonksiyonu kullanmadan önce kaydetmeliyiz. lua_pushcfunction ile bu küçük sihri yapıyoruz: bir C fonksiyonuna işaretçi alır ve Lua içindeki bu fonksiyonu temsil eden “function” türünde bir değer oluşturur. Bir kayıt işlemiyle C fonksiyonu Lua 'daki diğer fonksiyonlar gibi olur.
l_sin 'i test için hızlı ve kirli bir yol olarak, kodunu doğrudan lua.c dosyasına koyar ve luaL_openlibs çağrısından hemen sonra aşağıdaki satırlar eklenir:
lua_pushcfunction(L, l_sin);
lua_setglobal(L, "mysin");
İlk satır yazılan fonksiyonu bir değer olarak push eder. İkinci satır onu global değişken mysin'e atar.
Bu değişikliklerden sonra Lua işletilebilirlerinizi yeniden build edersiniz; sonra Lua programlarınızda yeni fonksiyon mysin'i kullanabilirsiniz. (sonraki bölümde Lua ile yeni C fonksiyonlarını linklemenin daha iyi yollarını tartışacağız.)
Daha profesyonel bir sinüs fonksiyonu için argümanının tipini kontrol etmeliyiz. Burada yardımcı kütüphane bize yardım eder. luaL_checknumber fonksiyonu verilen argümanın bir sayı olup olmadığını denetler: hata durumunda bilgilendirici bir hata iletisi fırlatır; aksi halde sayıyı döndürür. Fonksiyonumuzun modifikasyonu minimal:
static int l_sin(lua_State *L) { double d = luaL_checknumber(L, 1); lua_pushnumber(L, sin(d)); return 1; /* sonuçların sayısı */ }
Yukarıdaki tanımlamayla mysin('a') 'i çağırırsanız şu mesajı alırsınız
bad argument #1 to ’mysin’ (number expected, got string)
--mysin için 1. argüman hatalı (sayı beklenirken string alındı)
lual_checknumber'ın, argüman numarası(1.), fonksiyon ismi (“mysin”) , beklenen parametre türü (sayı) ve gelen parametre türü (string) ile iletiyi otomatik olarak nasıl doldurduğuna dikkat.
Liste 26.1. dizin okuyan fonksiyon:
#include <dirent.h> #include <errno.h> static int l_dir(lua_State *L) { DIR *dir; struct dirent *entry; int i; const char *path = luaL_checkstring(L, 1); /* dizini aç */ dir = opendir(path); if (dir == NULL) { /* dizini açmada hata mı? */ lua_pushnil(L); /* nil döndür*/ lua_pushstring(L, strerror(errno)); /* ve hata mesajı */ return 2; /* sonuç miktarı */ } /* sonuç tablosu oluştur */ lua_newtable(L); i = 1; while ((entry = readdir(dir)) != NULL) { lua_pushnumber(L, i++); /* anahtar push et */ lua_pushstring(L, entry->d_name); /* değer push et */ lua_settable(L, -3); } closedir(dir); return 1; /* tablo şimdi üstte */ }
Daha karmaşık bir örnek olarak, verilen bir dizinin içeriğini döndüren bir fonksiyon yazalım. Lua bu fonksiyonu standart kütüphanelerinde sunmaz çünkü ANSI C bu iş için fonksiyonlara sahip değildir.
Burada, POSIX uyumlu bir işletim sistemine sahip olduğumuzu varsayıyoruz. fonksiyonumuz (Lua'da dir , C'de l_dir olarak adlandırılık) dizin yolunu bir string argüman olarak alır ve dizin içerikleriyle bir dizi döndürür. Örneğin dir("/home/lua") çağrısı {".","..","src","bin","lib"} tablosunu gibi birşey döndürebilir. Hata durumunda fonksiyon nil artı hata mesajlı bir string döndürür. Bu fonksiyonun tüm kodları 26.1 listesindedir. yardımcı kütüphaneden, stringler için olan lua_checkstring fonksiyonunun lual_checknumber ile eşdeğer kullanımına dikkat.
(ekstrem koşullarda, l_dır 'in bu uygulaması küçük bir bellek sızıntısına neden olabilir. yetersiz bellek nedeniyle çağrıların başarısız olabileceği üç Lua fonksiyonu: lua_newtable, lua_pushstring ve lua_settable. Bu fonksiyonlardan herhangi biri başarısız olursa bir hata yükseltecek ve l_dir kesilecek bu nedenle closedir çağrılamayacak. Daha önce ele aldığımız üzere, birçok program için bu büyük bir sorun değil: program bellek aşımı çalışırsa yapılabilecek en iyi şey kapanmaktır. Bununla birlikte 29. bölümde bu sorundan kaçındıran bir dizin fonksiyonu için alternatif bir uyarlama göreceğiz.)
26.2 C Modülleri
Bir Lua modülü çeşitli Lua fonksiyonlarını tanımlayan ve bunları uygun yerlerde, tipik olarak bir tabloda girdiler olarak, depolayan bir öbektir. Lua için bir C modülü bu davranışı taklit eder. C fonksiyonlarının tanımlanmasının yanı sıra aynı zamanda bir Lua kütüphanesinin ana öbeğine karşılık gelen özel bir fonksiyon da tanımlanmalıdır. Bu fonksiyon modülün tüm C fonksiyonlarını kaydetmeli ve uygun yerlerde depolamalıdır. Bir Lua ana öbeği gibi, modülde ilklenmesi gereken diğer şeyleri de yine ilklemelidir.
Lua bu kayıt işlemiyle C fonksiyonlarını algılar. Bir C fonksiyonu sunulup Lua'da depolandığından itibaren Lua, adresine(bir fonksiyonu kayıt ettiğimizde Lua'a verdiğimiz budur) direkt başvuruyla onu çağırır. Diğer bir deyişle bir kayıt işlemiyle Lua, fonksiyon ismine, paket konumuna ya da bir fonksiyonu çağırmak için olan diğer görünürlük kurallarına bağımlı kalmaz. Tipik olarak bir C modülü kütüphaneyi açan bir fonksiyon olan tek bir public-umumi(extern) fonksiyona sahiptir. Diğer tüm fonksiyonlar C'de static olarak bildirilmiş, private-özel olabilirler.
Lua'ı C fonksiyonlarıyla genişlettiğinizde sadece bir C fonksiyonunu kaydetmek istediğinizde bile kodunuzu bir C modülü olarak tasarlamak iyi bir fikirdir: er ya da geç (genellikle er) diğer fonksiyonlara ihtiyacınız olacaktır. Her zamanki gibi yardımcı kütüphane bu iş için yardımcı bir fonksiyon sunar. luaL_register fonksiyonu ilgili adlarıyla C fonksiyonlarının bir listesini alır ve hepsini kütüphane adıyla bir tablonun içine kaydeder. Örnek olarak daha önce tanımladığımız l_dir fonksiyonuyla bir kütüphane oluşturmak istediğimizi varsayalım. İlk olarak kütüphane fonksiyonlarını tanımlamalıyız:
static int l_dir (lua_State *L) { <önceki gibi> }
Ardından, temsil edildikleri adlarıyla modüldeki tüm fonksiyonlarla bir dizi deklare ederiz. Bu dizi bir string ve bir fonksiyon pointer'ı olan iki alana sahip bir yapı olan luaL_Reg türünde öğelere sahiptir:
static const struct luaL_Reg mylib[] = { {"dir", l_dir}, {NULL, NULL} /* bitiş simgesi*/ };
Örneğimizde deklare edilecek sadece bir fonksiyon(l_dir) var. Dizideki son çift sonu işaret etmek için daima {NULL, NULL} ' dir. Son olarak, luaL_register kullanılarak bir ana fonksiyon beyan ederiz:
int luaopen_mylib(lua_State *L) { luaL_register(L, "mylib", mylib); return 1; }
luaL_register çağrısı verilen adla (“mylib”) bir tablo oluşturur (veya yeniden kullanır) ve mylib dizisi ile belirtilen isim-fonksiyon çiftleriyle onu doldurur. Döndüğünde, luaL_register yığına kütüphaneyi açtığı tabloyu bırakır. luaopen_mylib fonksiyonu daha sonra Lua'a bu değeri döndürmek için 1 döndürür.
Kütüphane bittikten sonra yorumlayıcıya onu linklemeliyiz. Bunu yapmanın en uygun yolu, Lua yorumlayıcınız bu imkanı destekliyorsa, dinamik linkleme tesisidir. Bu durumda, kodunuzla dinamik kütüphane(windows'da mylib.lib, diğer işletim sistemlerinde mylib.so) yaratmalısınız ve C dizini altına bir yere onu koymalısınız. Bu adımlardan sonra kütüphanenizi Lua'dan direkt require ile yükleyebilirsiniz:
require "mylib"
Bu çağrı, Lua ile dinamik kütüphane mylib'i linkler, luaopen_mylib fonksiyonunu bulur, bir C fonksiyonu olarak onu kaydeder ve onu çağırır, modül açılır. (Bu davranış luaopen_mylib'in neden diğer C fonksiyonlarıyla aynı prototipte olması gerektiğini açıklar.)
Yorumlayıcınız dinamik linklemeyi desteklemiyorsa Lua'ı yeni kütüphanenizle yeniden derlemeniz gerekir. Bu yeniden derlemenin yanı sıra bağımsız yorumlayıcıda yeni bir durum açıldığında bu kütüphaneyi de açması gerektiğini söylemenin bir yolunu bulmanız gerekir. Bunu yapmanın basit yolu, linit.c dosyasında luaL_openlibs ile açılacak standart kütüphaneler listesine luaopen_mylib'i eklemektir.
Hiç yorum yok:
Yorum Gönder