Son wasm yazımda, web üzerinde kullanabilmeniz için bir C kütüphanesini wasm’e nasıl derleyeceğinizden bahsetmiştim. Bana (ve birçok okuyucuya) dikkat çeken bir şey, wasm modülünüzün hangi işlevlerini kullandığınızı manuel olarak bildirmeniz gereken kaba ve biraz tuhaf yol. Fikrinizi tazelemek için, bahsettiğim kod parçacığı bu:
const api = {
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
};
Burada işaretlediğimiz fonksiyonların isimlerini bildiriyoruz. EMSCRIPTEN_KEEPALIVE
, dönüş türlerinin ne olduğu ve bağımsız değişkenlerinin türlerinin neler olduğu. Daha sonra, üzerindeki yöntemleri kullanabiliriz. api
Bu işlevleri çağırmak için nesne. Ancak, wasm’ı bu şekilde kullanmak dizeleri desteklemez ve bellek yığınlarını manuel olarak taşımanızı gerektirir, bu da birçok kitaplık API’sinin kullanımını çok sıkıcı hale getirir. Daha iyi bir yol yok mu? Neden evet var, yoksa bu makale ne hakkında olurdu?
C++ ad yönetimi #
Geliştirici deneyimi, bu bağlamalara yardımcı olan bir araç oluşturmak için yeterli bir neden olsa da, aslında daha acil bir neden var: C veya C++ kodunu derlediğinizde, her dosya ayrı ayrı derlenir. Ardından, bir linker, tüm bu sözde nesne dosyalarını bir araya getirme ve onları bir wasm dosyasına dönüştürme işini üstlenir. Bağlayıcının kullanması için C ile, işlevlerin adları hala nesne dosyasında mevcuttur. Bir C işlevini çağırabilmeniz için ihtiyacınız olan tek şey, dizi olarak sağladığımız addır. cwrap()
.
Öte yandan C++, işlev aşırı yüklemesini destekler, yani imza farklı olduğu sürece (örneğin, farklı türde parametreler) aynı işlevi birden çok kez uygulayabilirsiniz. Derleyici düzeyinde, güzel bir isim gibi add
alacaktı ezilmiş bağlayıcı için işlev adındaki imzayı kodlayan bir şeye. Sonuç olarak, fonksiyonumuzun adını artık arayamayız.
gömmek girin #
ortada Emscripten araç zincirinin bir parçasıdır ve size C++ koduna açıklama eklemenize izin veren bir dizi C++ makrosu sağlar. JavaScript’ten hangi işlevleri, numaralandırmaları, sınıfları veya değer türlerini kullanmayı planladığınızı bildirebilirsiniz. Bazı basit işlevlerle basit başlayalım:
#include <emscripten/bind.h>using namespace emscripten;
double add(double a, double b) {
return a + b;
}
std::string exclaim(std::string message) {
return message + "!";
}
EMSCRIPTEN_BINDINGS(my_module) {
function("add", &add);
function("exclaim", &exclaim);
}
Önceki makalemle karşılaştırıldığında, dahil etmiyoruz emscripten.h
artık, işlevlerimize açıklama eklemek zorunda olmadığımız için EMSCRIPTEN_KEEPALIVE
artık değil. Bunun yerine, elimizde bir EMSCRIPTEN_BINDINGS
İşlevlerimizi JavaScript’e maruz bırakmak istediğimiz adları listelediğimiz bölüm.
Bu dosyayı derlemek için, önceki makaledekiyle aynı kurulumu (veya isterseniz aynı Docker görüntüsünü) kullanabiliriz. Embind’i kullanmak için şunu ekliyoruz: --bind
bayrak:
$ emcc --bind -O3 add.cpp
Şimdi geriye kalan tek şey, yeni oluşturulmuş wasm modülümüzü yükleyen bir HTML dosyasını hazırlamak:
script src="/a.out.js"></script>
script>
Module.onRuntimeInitialized = _ => {
console.log(Module.add(1, 2.3));
console.log(Module.exclaim("hello world"));
};
</script>
Gördüğünüz gibi kullanmıyoruz. cwrap()
artık değil. Bu sadece kutudan çıkar çıkmaz çalışır. Ama daha da önemlisi, dizelerin çalışması için bellek yığınlarını manuel olarak kopyalama konusunda endişelenmemize gerek yok! embind, tip kontrollerinin yanı sıra bunu size ücretsiz olarak verir:
Zaman zaman oldukça hantal olan wasm hatalarıyla uğraşmak yerine bazı hataları erken yakalayabildiğimiz için bu oldukça harika.
nesneler #
Birçok JavaScript oluşturucusu ve işlevi, options nesnelerini kullanır. JavaScript’te güzel bir model, ancak wasm’de manuel olarak gerçekleştirilmesi son derece sıkıcı. embind burada da yardımcı olabilir!
Örneğin, bunu buldum inanılmaz şekilde dizelerimi işleyen kullanışlı C++ işlevi ve ben bunu acilen web’de kullanmak istiyorum. İşte bunu nasıl yaptım:
#include <emscripten/bind.h>
#include <algorithm>using namespace emscripten;
struct ProcessMessageOpts {
bool reverse;
bool exclaim;
int repeat;
};
std::string processMessage(std::string message, ProcessMessageOpts opts) {
std::string copy = std::string(message);
if(opts.reverse) {
std::reverse(copy.begin(), copy.end());
}
if(opts.exclaim) {
copy += "!";
}
std::string acc = std::string("");
for(int i = 0; i opts.repeat; i++) {
acc += copy;
}
return acc;
}
EMSCRIPTEN_BINDINGS(my_module) {
value_objectProcessMessageOpts>("ProcessMessageOpts")
.field("reverse", &ProcessMessageOpts::reverse)
.field("exclaim", &ProcessMessageOpts::exclaim)
.field("repeat", &ProcessMessageOpts::repeat);
function("processMessage", &processMessage);
}
Seçeneklerim için bir yapı tanımlıyorum processMessage()
işlev. İçinde EMSCRIPTEN_BINDINGS
engelle, kullanabilirim value_object
JavaScript’in bu C++ değerini bir nesne olarak görmesini sağlamak için. ben de kullanabilirim value_array
bu C++ değerini bir dizi olarak kullanmayı tercih edersem. ben de bağlarım processMessage()
işlev ve geri kalanı embind büyü. şimdi arayabilirim processMessage()
herhangi bir kalıp kodu olmadan JavaScript’ten işlev:
console.log(Module.processMessage(
"hello world",
{
reverse: false,
exclaim: true,
repeat: 3
}
)); // Prints "hello world!hello world!hello world!"
sınıflar #
Tamlık uğruna, embind’in tüm sınıfları açığa çıkarmanıza nasıl izin verdiğini de göstermeliyim, bu da ES6 sınıflarıyla çok fazla sinerji getiriyor. Muhtemelen şimdiye kadar bir model görmeye başlayabilirsiniz:
#include <emscripten/bind.h>
#include <algorithm>using namespace emscripten;
class Counter {
public:
int counter;
Counter(int init) :
counter(init) {
}
void increase() {
counter++;
}
int squareCounter() {
return counter * counter;
}
};
EMSCRIPTEN_BINDINGS(my_module) {
class_Counter>("Counter")
.constructorint>()
.function("increase", &Counter::increase)
.function("squareCounter", &Counter::squareCounter)
.property("counter", &Counter::counter);
}
JavaScript tarafında, bu neredeyse yerel bir sınıf gibi hissettiriyor:
script src="/a.out.js">/script>
script>
Module.onRuntimeInitialized = _ => {
const c = new Module.Counter(22);
console.log(c.counter); // prints 22
c.increase();
console.log(c.counter); // prints 23
console.log(c.squareCounter()); // prints 529
};
/script>
Peki ya C? #
embind, C++ için yazılmıştır ve yalnızca C++ dosyalarında kullanılabilir, ancak bu, C dosyalarına bağlanamayacağınız anlamına gelmez! C ve C++’yı karıştırmak için girdi dosyalarınızı yalnızca iki gruba ayırmanız gerekir: Biri C için, diğeri C++ dosyaları için ve CLI bayraklarını emcc
aşağıdaki gibi:
$ emcc --bind -O3 --std=c++11 a_c_file.c another_c_file.c -x c++ your_cpp_file.cpp
Çözüm #
embind, wasm ve C/C++ ile çalışırken size geliştirici deneyiminde büyük gelişmeler sağlar. Bu makale, teklifleri içeren tüm seçenekleri kapsamaz. Eğer ilgileniyorsanız, devam etmenizi öneririm embind belgeleri. Embind kullanmanın, gzip’lendiğinde hem wasm modülünüzü hem de JavaScript birleştirme kodunuzu 11k’ye kadar büyütebileceğini unutmayın; özellikle de küçük modüllerde. Yalnızca çok küçük bir wasm yüzeyiniz varsa, embind bir üretim ortamında değerinden daha pahalıya mal olabilir! Yine de kesinlikle denemelisiniz.