Emscripten'ın evi

JS'yi wasm'nize bağlar.

Son Wasm makalemde, web'de kullanabilmeniz için C kitaplığını wasm olarak derleme konusunda bilgi verilmektedir. Bir şey Bu benim için (ve birçok okuyucu için) öne çıkan şey, Kaba ve biraz tuhaf bir wasm modülünüzün hangi işlevlerini kullandığınızı manuel olarak bildirmeniz gerekir. Konuştuğumuz kod snippet'i şöyledir:

const api = {
    version: Module.cwrap('version', 'number', []),
    create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
    destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
};

Burada, EMSCRIPTEN_KEEPALIVE, bunların dönüş türleri ve bunların türleri belirlemektir. Sonra, çağırmak için api nesnesindeki yöntemleri kullanabiliriz bu işlevlere göz atın. Ancak, wasm'ın bu şekilde kullanılması dizeleri ve bellek parçalarını manuel olarak taşımanızı gerektirir. Bu da, API'lerin kullanımı çok zahmetli. Daha iyi bir yol yok mu? Neden evet, aksi takdirde bu makalenin konusu neydi?

C++ ad birleştirme

Geliştirici deneyimi, kullanıcılara yardımcı olan bir araç geliştirmek için daha acil bir neden vardır: C veya C++ kodu varsa her dosya ayrı olarak derlenir. Ardından bağlayıcı, bütün bu nesne dosyalarını bir araya getirmek ve bunları bir wasm'a dönüştürmek dosyası olarak kaydedebilirsiniz. C ile, işlevlerin adları nesne dosyasında kullanılmaya devam eder kullanabilirsiniz. C işlevini çağırmak için ihtiyacınız olan tek şey adıdır. cwrap() öğesine bir dize olarak sağlıyoruz.

Öte yandan C++, işlev aşırı yüklemesini destekler. Bu da, imza farklı olduğu sürece aynı işlevi birden çok kez tekrarlamanız gerekir (ör. farklı şekilde yazılmış parametreler). Derleyici düzeyinde add gibi bir güzel ad işlevindeki imzayı kodlayan bir şeye parçalanır bağlayıcının adını girin. Bunun sonucunda, işlevimizi arama artık bir adı yok.

Embind girin

embind Emscripten araç zincirinin bir parçasıdır ve size birçok C++ makrosu sağlar Bunlar sayesinde C++ kodunu ekleyebilirsiniz. Dört hafta içinde hangi fonksiyonların, enumların, kullanmayı planladığınız sınıfları veya değer türlerini gösterir. Hadi başlayalım bazı basit işlevlerle daha basittir:

#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 emscripten.h artık dahil edilmiyor. artık işlevlerimize EMSCRIPTEN_KEEPALIVE ile ek açıklama eklememiz gerekmiyor. Bunun yerine, adları şunun altında listeleyen bir EMSCRIPTEN_BINDINGS bölümümüz vardır: açık hale getirmek isteriz.

Bu dosyayı derlemek için aynı kurulumu (veya isterseniz Docker görüntüsü) önceki örnekte makalesine bakın. Embind'i kullanmak için --bind işaretini ekliyoruz:

$ emcc --bind -O3 add.cpp

Şimdi tek yapmanız gereken yeni wasm modülü oluşturuldu:

<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 artık cwrap() kullanmıyoruz. Bu şekilde hemen kullanabilirsiniz. ekleyebilirsiniz. Ancak daha da önemlisi, yükleme işlemini manuel olarak parça parça hafızaya ayırabiliriz. embind, bunu size ücretsiz olarak sağlar. şu tür kontrolleri uygulayın:

Yanlış sayıda bağımsız değişkenle bir işlevi çağırdığınızda Geliştirici Araçları hataları
veya argümanların yanlış
tür

Bazı hataları ele almak yerine erkenden tespit edebildiğimiz için bu gerçekten ve zaman zaman elverişsiz olan wasm hatalarını düzeltmeye çalışın.

Nesneler

Birçok JavaScript oluşturucu ve işlevi, seçenek nesnelerini kullanır. Bu güzel bir ancak wasm'da manuel olarak gerçekleştirmek son derece yorucudur. Embind bu konuda da yardımcı olabilir!

Örneğin, dizelerim var ve bunu web'de acilen kullanmak istiyorum. Bunu şu şekilde 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_object<ProcessMessageOpts>("ProcessMessageOpts")
    .field("reverse", &ProcessMessageOpts::reverse)
    .field("exclaim", &ProcessMessageOpts::exclaim)
    .field("repeat", &ProcessMessageOpts::repeat);

    function("processMessage", &processMessage);
}

processMessage() işlevimin seçenekleri için bir struct tanımlıyorum. EMSCRIPTEN_BINDINGS blok, JavaScript'in görmesini sağlamak için value_object kullanabilirim bu C++ değerini nesne olarak değiştirir. İstersem value_array kullanabilirim bu C++ değerini bir dizi olarak kullanın. Ayrıca processMessage() işlevini de bağlıyorum ve geri kalan her şey sihir ile harikadır. Artık processMessage() işlevini şuradan çağırabilirim: Ortak metin kodu olmayan JavaScript:

console.log(Module.processMessage(
    "hello world",
    {
    reverse: false,
    exclaim: true,
    repeat: 3
    }
)); // Prints "hello world!hello world!hello world!"

Sınıflar

Bütünlük kazanmak amacıyla, embind'in içeriğinizi Google'ın bu da ES6 sınıflarıyla büyük bir sinerji yaratıyor. Muhtemelen bir kalıp görmeye başlayın:

#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")
    .constructor<int>()
    .function("increase", &Counter::increase)
    .function("squareCounter", &Counter::squareCounter)
    .property("counter", &Counter::counter);
}

JavaScript açısından, bu neredeyse yerel bir sınıf hissi verir:

<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 C dosyalarına bağlantı veremezsiniz! C ve C++ özelliklerini birlikte kullanmak için giriş dosyalarınızı iki gruba ayırın: C için biri, C++ dosyaları ve emcc için CLI işaretlerini şu şekilde genişletin:

$ emcc --bind -O3 --std=c++11 a_c_file.c another_c_file.c -x c++ your_cpp_file.cpp

Sonuç

embind, çalışırken geliştirici deneyiminde harika iyileştirmeler sunar. ile başladı. Bu makalede, embind tekliflerinin tüm seçenekleri ele alınmamaktadır. Bu konu ilginizi çekiyorsa, embind's dokümanlarına göz atın. Embind kullanarak hem wasm modülünüzü hem de gzip ile sıkıştırıldığında 11.000'e kadar daha büyük JavaScript yapışkan kodu (özellikle küçük boyutlarda) modüllerinde yer alır. Çok küçük bir wasm yüzeyiniz varsa embind'in maliyeti üretim ortamında buna değer. Yine de, abonelik seçeneklerinin deneyin.