Dış dünyayla iletişim kurmak için WebAssembly kitaplığınıza JavaScript kodunu nasıl yerleştireceğinizi öğrenin.
WebAssembly'i web ile entegre ederken web API'leri ve üçüncü taraf kitaplıkları gibi harici API'leri çağırmanın bir yoluna ihtiyacınız vardır. Ardından, bu API'lerin döndürdüğü değerleri ve nesne örneklerini depolayacak ve daha sonra bu depolanan değerleri diğer API'lere iletecek bir yönteme ihtiyacınız vardır. Eşzamansız API'ler için Asyncify ile eşzamanlı C/C++ kodunuzda vaatleri beklemeniz ve işlem tamamlandıktan sonra sonucu okumanız da gerekebilir.
Emscripten, bu tür etkileşimler için çeşitli araçlar sunar:
- C++'ta JavaScript değerlerini depolamak ve bunlar üzerinde çalıştırmak için
emscripten::val
. EM_JS
JavaScript snippet'lerini yerleştirmek ve bunları C/C++ işlevi olarak bağlamak için.EM_JS
'ye benzer ancak eşzamansız JavaScript snippet'lerini yerleştirmeyi kolaylaştıranEM_ASYNC_JS
.EM_ASM
kısa snippet'leri yerleştirmek ve işlev tanımlamadan satır içi olarak yürütmek için kullanılır.- Çok sayıda JavaScript işlevini birlikte tek bir kitaplık olarak tanımlamak istediğiniz gelişmiş senaryolar için
--js-library
.
Bu gönderide, benzer görevler için bu özelliklerin hepsini nasıl kullanacağınızı öğreneceksiniz.
emscripten::val sınıfı
emcripten::val
sınıfı Embind tarafından sağlanır. Genel API'leri çağırabilir, JavaScript değerlerini C++ örneklerine bağlayabilir ve değerleri C++ ile JavaScript türleri arasında dönüştürebilir.
Bazı JSON'ları getirmek ve ayrıştırmak için bunu, Asyncify'ın .await()
özelliğiyle nasıl kullanacağınız aşağıda açıklanmıştır:
#include <emscripten/val.h>
using namespace emscripten;
val fetch_json(const char *url) {
// Get and cache a binding to the global `fetch` API in each thread.
thread_local const val fetch = val::global("fetch");
// Invoke fetch and await the returned `Promise<Response>`.
val response = fetch(url).await();
// Ask to read the response body as JSON and await the returned `Promise<any>`.
val json = response.call<val>("json").await();
// Return the JSON object.
return json;
}
// Example URL.
val example_json = fetch_json("https://httpbin.org/json");
// Now we can extract fields, e.g.
std::string author = json["slideshow"]["author"].as<std::string>();
Bu kod iyi çalışıyor ancak birçok ara adım gerçekleştiriyor. val
üzerindeki her işlemin aşağıdaki adımları gerçekleştirmesi gerekir:
- Bağımsız değişken olarak iletilen C++ değerlerini ara bir biçime dönüştürün.
- JavaScript'e gidin, bağımsız değişkenleri okuyup JavaScript değerlerine dönüştürün.
- İşlevi yürütme
- Sonucu JavaScript'ten ara biçime dönüştürün.
- Dönüştürülen sonucu C++'ya döndürün ve C++ sonunda sonucu geri okur.
Her await()
'ün, WebAssembly modülünün çağrı yığınının tamamını çözerek, JavaScript'e dönerek, bekleyerek ve işlem tamamlandığında WebAssembly yığınını geri yükleyerek C++ tarafını duraklatması da gerekir.
Bu tür kodlar için C++'dan herhangi bir şeye ihtiyaç yoktur. C++ kodu yalnızca bir dizi JavaScript işlemi için sürücü görevi görür. fetch_json
'ü JavaScript'e taşıyıp aynı zamanda ara adımların yükünü azaltabilseydiniz ne olurdu?
EM_JS makrosu
EM_JS macro
, fetch_json
öğesini JavaScript'e taşımanıza olanak tanır. Emscripten'deki EM_JS
, JavaScript snippet'i tarafından uygulanan bir C/C++ işlevi tanımlamanıza olanak tanır.
WebAssembly'in kendisi gibi, yalnızca sayısal bağımsız değişkenleri ve döndürülen değerleri destekleme sınırlamasına sahiptir. Diğer değerleri iletmek için bunları ilgili API'ler aracılığıyla manuel olarak dönüştürmeniz gerekir. Aşağıda birkaç örnek verilmiştir.
Geçilen sayıların dönüştürülmesi gerekmez:
// Passing numbers, doesn't need any conversion.
EM_JS(int, add_one, (int x), {
return x + 1;
});
int x = add_one(41);
JavaScript'e ve JavaScript'ten dize aktarırken preamble.js'deki ilgili dönüşüm ve tahsis işlevlerini kullanmanız gerekir:
EM_JS(void, log_string, (const char *msg), {
console.log(UTF8ToString(msg));
});
EM_JS(const char *, get_input, (), {
let str = document.getElementById('myinput').value;
// Returns heap-allocated string.
// C/C++ code is responsible for calling `free` once unused.
return allocate(intArrayFromString(str), 'i8', ALLOC_NORMAL);
});
Son olarak, daha karmaşık, rastgele değer türleri için daha önce bahsedilen val
sınıfı için JavaScript API'sini kullanabilirsiniz. Bu aracı kullanarak, JavaScript değerlerini ve C++ sınıflarını ara herkese açık kullanıcı adlarına ve geri dönüşlere dönüştürebilirsiniz:
EM_JS(void, log_value, (EM_VAL val_handle), {
let value = Emval.toValue(val_handle);
console.log(value);
});
EM_JS(EM_VAL, find_myinput, (), {
let input = document.getElementById('myinput');
return Emval.toHandle(input);
});
val obj = val::object();
obj.set("x", 1);
obj.set("y", 2);
log_value(obj.as_handle()); // logs { x: 1, y: 2 }
val myinput = val::take_ownership(find_input());
// Now you can store the `find_myinput` DOM element for as long as you like, and access it later like:
std::string value = input["value"].as<std::string>();
Bu API'ler göz önünde bulundurularak fetch_json
örneği, çoğu işi JavaScript'den ayrılmadan yapacak şekilde yeniden yazılabilir:
EM_JS(EM_VAL, fetch_json, (const char *url), {
return Asyncify.handleAsync(async () => {
url = UTF8ToString(url);
// Invoke fetch and await the returned `Promise<Response>`.
let response = await fetch(url);
// Ask to read the response body as JSON and await the returned `Promise<any>`.
let json = await response.json();
// Convert JSON into a handle and return it.
return Emval.toHandle(json);
});
});
// Example URL.
val example_json = val::take_ownership(fetch_json("https://httpbin.org/json"));
// Now we can extract fields, e.g.
std::string author = json["slideshow"]["author"].as<std::string>();
İşlevin giriş ve çıkış noktalarında birkaç açık dönüşümümüz hâlâ var ancak geri kalanı artık normal JavaScript kodu. val
eşdeğerinin aksine, artık JavaScript motoru tarafından optimize edilebilir ve tüm eşzamansız işlemler için C++ tarafının yalnızca bir kez duraklatılması gerekir.
EM_ASYNC_JS makrosu
Geriye kalan ve pek hoş görünmeyen tek şey Asyncify.handleAsync
sarmalayıcısıdır. Tek amacı, async
JavaScript işlevlerinin Asyncify ile yürütülmesine izin vermektir. Bu kullanım alanı o kadar yaygındır ki artık bunları bir araya getiren özel bir EM_ASYNC_JS
makrosu vardır.
fetch
örneğinin nihai sürümünü oluşturmak için bu işlevi nasıl kullanabileceğinizi aşağıda görebilirsiniz:
EM_ASYNC_JS(EM_VAL, fetch_json, (const char *url), {
url = UTF8ToString(url);
// Invoke fetch and await the returned `Promise<Response>`.
let response = await fetch(url);
// Ask to read the response body as JSON and await the returned `Promise<any>`.
let json = await response.json();
// Convert JSON into a handle and return it.
return Emval.toHandle(json);
});
// Example URL.
val example_json = val::take_ownership(fetch_json("https://httpbin.org/json"));
// Now we can extract fields, e.g.
std::string author = json["slideshow"]["author"].as<std::string>();
EM_ASM
EM_JS
, JavaScript snippet'lerini tanımlamak için önerilen yöntemdir. Tanımlanmış snippet'leri diğer tüm JavaScript işlevi içe aktarma işlemleri gibi doğrudan bağladığı için verimlidir. Ayrıca tüm parametre türlerini ve adlarını açık bir şekilde tanımlamanıza olanak tanıyarak iyi ergonomik bilgiler sağlar.
Ancak bazı durumlarda, console.log
çağrısı, debugger;
ifadesi veya benzer bir ifade için hızlı bir snippet eklemek ve tamamen ayrı bir işlev tanımlamak istemiyorsanız bu işlemi yapabilirsiniz. Bu nadir durumlarda, EM_ASM macros family
(EM_ASM
, EM_ASM_INT
ve EM_ASM_DOUBLE
) daha basit bir seçenek olabilir. Bu makrolar EM_JS
makrosuna benzer ancak bir işlev tanımlamak yerine, kodları eklendikleri satırda yürütürler.
İşlev prototipi beyan etmedikleri için dönüş türünü belirtmek ve bağımsız değişkenlere erişmek için farklı bir yönteme ihtiyaçları vardır.
Döndürülen türün seçilmesi için doğru makro adını kullanmanız gerekir. EM_ASM
bloklarının void
işlevleri gibi davranması beklenir. EM_ASM_INT
blokları tam sayı değeri döndürebilir ve EM_ASM_DOUBLE
blokları da buna göre kayan noktalı sayılar döndürür.
İletilen tüm bağımsız değişkenler, JavaScript gövdesinde $0
, $1
vb. adlar altında kullanılabilir. Genel olarak EM_JS
veya WebAssembly'de olduğu gibi, bağımsız değişkenler yalnızca sayısal değerlerle (tam sayılar, kayan noktalı sayılar, işaretçiler ve tutamaçlar) sınırlıdır.
Aşağıda, rastgele bir JS değerini konsola kaydetmek için EM_ASM
makrosunu nasıl kullanabileceğinize dair bir örnek verilmiştir:
val obj = val::object();
obj.set("x", 1);
obj.set("y", 2);
// executes inline immediately
EM_ASM({
// convert handle passed under $0 into a JavaScript value
let obj = Emval.fromHandle($0);
console.log(obj); // logs { x: 1, y: 2 }
}, obj.as_handle());
--js-library
Son olarak Emscripten, JavaScript kodunu kendi özel kitaplık biçiminde ayrı bir dosyada tanımlamayı destekler:
mergeInto(LibraryManager.library, {
log_value: function (val_handle) {
let value = Emval.toValue(val_handle);
console.log(value);
}
});
Daha sonra, karşılık gelen prototipleri C++ tarafında manuel olarak bildirmeniz gerekir:
extern "C" void log_value(EM_VAL val_handle);
Her iki tarafta da tanımlandıktan sonra JavaScript kitaplığı, prototipleri ilgili JavaScript uygulamalarıyla bağlayarak --js-library option
aracılığıyla ana kodla bağlanabilir.
Ancak bu modül biçimi standart değildir ve dikkatli bağımlılık ek açıklamaları gerektirir. Bu nedenle, çoğunlukla gelişmiş senaryolar için ayrılmıştır.
Sonuç
Bu yayında, WebAssembly ile çalışırken JavaScript kodunu C++'a entegre etmenin çeşitli yollarını inceledik.
Bu tür snippet'leri dahil etmek, uzun işlem dizilerini daha temiz ve verimli bir şekilde ifade etmenize, üçüncü taraf kitaplıklarına, yeni JavaScript API'lerine ve hatta henüz C++ veya Embind ile ifade edilememiş JavaScript söz dizimi özelliklerine erişmenize olanak tanır.