WebAssembly nedir ve nereden geldi? bölümünde, Bugünün WebAssembly'sini nasıl ortaya koyduğumuzu açıkladım. Bu makalede, WebAssembly'de mevcut bir C programını (mkbitmap
) derleme yaklaşımımı göstereceğim. Bu, hello world örneğinden daha karmaşıktır. Dosyalarla çalışmayı, WebAssembly ile JavaScript alanları arasında iletişim kurmayı ve bir tuvalde çizim yapmayı kapsar ancak yine de sizi bunaltmayacak kadar yönetilebilirdir.
Bu makale, WebAssembly hakkında bilgi edinmek isteyen web geliştiricileri için yazılmış olup mkbitmap
gibi bir şeyi WebAssembly için derlemek istediğinizde ne yapmanız gerektiğini adım adım gösterir. Adil bir uyarı olarak, ilk çalıştırmada bir uygulamanın veya kitaplığın derlenmemesi tamamen normaldir. Bu nedenle, aşağıda açıklanan adımlardan bazıları çalışmadı. Dolayısıyla geri dönüp farklı bir şekilde denemem gerekti. Makalede, sihirli son derleme komutu sanki gökyüzünden düşmüş gibi gösterilmiyor, bunun yerine gerçek ilerleme durumum açıklanıyor, bazı hayal kırıklıkları da var.
Yaklaşık mkbitmap
mkbitmap
C programı bir görüntüyü okur ve resme şu işlemlerden birini veya daha fazlasını şu sırayla uygular: ters çevirme, üst geçiş filtreleme, ölçeklendirme ve eşik. Her işlem ayrı ayrı kontrol edilebilir ve açılabilir veya kapatılabilir. mkbitmap
işlevinin esas kullanımı, renkli veya gri tonlamalı resimleri diğer programlara, özellikle de SVGcode temelini oluşturan izleme programı potrace
için girdi olarak uygun bir biçime dönüştürmektir. Bir ön işleme aracı olarak mkbitmap
özellikle çizgi filmler veya el yazısı metinler gibi taranmış çizgi resimleri yüksek çözünürlüklü iki düzeyli resimlere dönüştürmek için kullanışlıdır.
mkbitmap
öğesine çeşitli seçenekler ve bir veya daha fazla dosya adı ileterek kullanırsınız. Tüm ayrıntılar için aracın Kılavuz sayfasına bakın:
$ mkbitmap [options] [filename...]
Kodu alın
İlk adım, mkbitmap
kaynak kodunu almaktır. Sertifikayı projenin web sitesinde bulabilirsiniz. Bu yazının yazıldığı tarihte potrace-1.16.tar.gz en son sürümdür.
Yerel olarak derleyip yükleyin
Bir sonraki adım, nasıl davrandığı hakkında fikir edinmek için aracı yerel olarak derlemek ve yüklemektir. INSTALL
dosyası aşağıdaki talimatları içerir:
Paketin kaynak kodunu ve türünü içeren dizine
cd
Paketi sisteminize göre yapılandırmak için./configure
.configure
özelliğinin çalışması biraz zaman alabilir. Çalışırken yazdırılır hangi özellikleri kontrol ettiğini belirten mesajlar oluşturabilir.Paketi derlemek için
make
yazın.İsteğe bağlı olarak,
make check
yardımcı olacak bir uygulamadır.Programları ve veri dosyalarını yüklemek için
make install
yazın ve belgelerinden faydalanabilirsiniz. Köke ait bir öneke yüklerken paketinin normal bir şekilde yapılandırılmasını ve derlenmesini önerilmiştir: ve yalnızcamake install
aşaması kök düzeyinde yürütülmüştür ayrıcalıkları.
Bu adımları uyguladığınızda, potrace
ve mkbitmap
olmak üzere iki yürütülebilir dosya ortaya çıkar. Bu ikinci dosya bu makalenin odak noktasıdır. mkbitmap --version
komutunu çalıştırarak düzgün şekilde çalıştığını doğrulayabilirsiniz. Makinemden elde edilen dört adımın tümünün çıktısı aşağıdaki gibidir, ancak kısa olması için yoğun bir şekilde kırpılmıştır:
1. Adım, ./configure
:
$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
[…]
config.status: executing libtool commands
2. Adım, make
:
$ make
/Applications/Xcode.app/Contents/Developer/usr/bin/make all-recursive
Making all in src
clang -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all-am'.
3. Adım, make check
:
$ make check
Making check in src
make[1]: Nothing to be done for `check'.
Making check in doc
make[1]: Nothing to be done for `check'.
[…]
============================================================================
Testsuite summary for potrace 1.16
============================================================================
# TOTAL: 8
# PASS: 8
# SKIP: 0
# XFAIL: 0
# FAIL: 0
# XPASS: 0
# ERROR: 0
============================================================================
make[1]: Nothing to be done for `check-am'.
4. Adım, sudo make install
:
$ sudo make install
Password:
Making install in src
.././install-sh -c -d '/usr/local/bin'
/bin/sh ../libtool --mode=install /usr/bin/install -c potrace mkbitmap '/usr/local/bin'
[…]
make[2]: Nothing to be done for `install-data-am'.
İşe yarayıp yaramadığını kontrol etmek için mkbitmap --version
komutunu çalıştırın:
$ mkbitmap --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.
Sürüm ayrıntılarını alırsanız mkbitmap
dosyasını başarıyla derleyip yüklemişsiniz demektir. Ardından, bu adımların eşdeğerinin WebAssembly ile çalışmasını sağlayın.
mkbitmap
öğesini WebAssembly'de derleyin
Emscripten, C/C++ programlarını WebAssembly'de derlemeye yarayan bir araçtır. Emscripten'in Building Projects (Projeler) belgelerinde şu bilgiler yer alır:
Emscripten ile büyük projeler oluşturmak çok kolaydır. Emscripten, oluşturma dosyalarınızı
gcc
yerineemcc
eklentisini kullanacak şekilde yapılandıran iki basit komut dosyası sağlar. Çoğu durumda, projenizin mevcut derleme sisteminin geri kalanı değiştirilmeden kalır.
Dokümanlar devam ediyor (kısa bir düzenleme için):
Normalde şu komutları kullanarak derleme yaptığınız örneği düşünün:
../configure
make
. Emscripten ile derleme yapmak için bunun yerine şu komutları kullanırsınız:
.emconfigure ./configure
emmake make
Dolayısıyla, ./configure
emconfigure ./configure
, make
ise emmake make
olur. Aşağıda, bu işlemi mkbitmap
ile nasıl yapabileceğiniz gösterilmektedir.
0. Adım, make clean
:
$ make clean
Making clean in src
rm -f potrace mkbitmap
test -z "" || rm -f
rm -rf .libs _libs
[…]
rm -f *.lo
1. Adım, emconfigure ./configure
:
$ emconfigure ./configure
configure: ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
[…]
config.status: executing libtool commands
2. Adım, emmake make
:
$ emmake make
make: make
/Applications/Xcode.app/Contents/Developer/usr/bin/make all-recursive
Making all in src
/opt/homebrew/Cellar/emscripten/3.1.36/libexec/emcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all'.
Her şey yolundaysa artık dizinde bir yerde .wasm
dosyası bulunuyor olmalıdır. Bu öğeleri find . -name "*.wasm"
komutunu çalıştırarak bulabilirsiniz:
$ find . -name "*.wasm"
./a.wasm
./src/mkbitmap.wasm
./src/potrace.wasm
Son ikisi de umut verici görünüyor. Bu nedenle, src/
dizinine cd
ekleyin. Ayrıca mkbitmap
ve potrace
adlı iki yeni dosya da mevcuttur. Bu makale için yalnızca mkbitmap
alakalıdır. .js
uzantısına sahip olmamaları biraz kafa karıştırıcı olsa da aslında bu JavaScript dosyalarıdır ve kısa bir head
çağrısıyla doğrulanabilir:
$ cd src/
$ head -n 20 mkbitmap
// include: shell.js
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(Module) { ..generated code.. }
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = typeof Module != 'undefined' ? Module : {};
// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)
mv mkbitmap mkbitmap.js
(ve isterseniz mv potrace potrace.js
) çağrısı yaparak JavaScript dosyasını mkbitmap.js
olarak yeniden adlandırın.
Şimdi sıra, komut satırında node mkbitmap.js --version
komutunu çalıştırarak dosyayı Node.js ile çalıştırarak çalışıp çalışmadığını görmek için ilk teste geldi:
$ node mkbitmap.js --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.
mkbitmap
dosyasını WebAssembly'de başarıyla derlediniz. Şimdi sıradaki adım, dosyanın tarayıcıda çalışmasını sağlamaktır.
Tarayıcıda WebAssembly ile mkbitmap
mkbitmap.js
ve mkbitmap.wasm
dosyalarını mkbitmap
adlı yeni bir dizine kopyalayın ve mkbitmap.js
JavaScript dosyasını yükleyen bir index.html
HTML ortak dosyası oluşturun.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>mkbitmap</title>
</head>
<body>
<script src="mkbitmap.js"></script>
</body>
</html>
mkbitmap
dizinini sunan bir yerel sunucu başlatın ve tarayıcınızda açın. Giriş yapmanızı isteyen bir istem görürsünüz. Aracın man sayfasına göre, "[i]f dosya adı bağımsız değişkeni belirtilmemişse mkbitmap standart girişten okuyor" (emscripten için varsayılan olarak prompt()
) olduğundan bu beklenen bir durumdur.
Otomatik yürütmeyi engelle
mkbitmap
öğesinin hemen çalışmasını durdurmak ve bunun yerine kullanıcı girişini beklemek için Emscripten'in Module
nesnesini anlamanız gerekir. Module
, Emscripten tarafından oluşturulan kodun yürütme sırasında çeşitli noktalarda çağırdığı özelliklere sahip genel bir JavaScript nesnesidir.
Kodun yürütülmesini kontrol etmek için bir Module
uygulaması sağlayabilirsiniz.
Bir Emscripten uygulaması başlatıldığında, Module
nesnesindeki değerlere bakar ve bunları uygular.
mkbitmap
söz konusu olduğunda, istemin görünmesine neden olan ilk çalıştırmayı önlemek için Module.noInitialRun
değerini true
olarak ayarlayın. script.js
adında bir komut dosyası oluşturun, bunu index.html
içinde <script src="mkbitmap.js"></script>
öğesinin öncesine ekleyin ve aşağıdaki kodu script.js
öğesine ekleyin. Şimdi uygulamayı yeniden yüklediğinizde istem kaybolacaktır.
var Module = {
// Don't run main() at page load
noInitialRun: true,
};
Biraz daha derleme bayrağı içeren modüler bir yapı oluşturun
Uygulamaya giriş sağlamak için Module.FS
uygulamasında Emscripten'in dosya sistemi desteğini kullanabilirsiniz. Belgenin Include File System Support (Dosya Sistemi Desteği Dahil Olma) bölümünde şunlar belirtiliyor:
Emscripten, dosya sistemi desteğini otomatik olarak dahil edip etmemeye karar verir. Birçok program dosyalara ihtiyaç duymaz ve dosya sistemi desteği de göz ardı edilebilir bir boyut değildir. Bu nedenle, Emscripten neden bulamadığında dosyayı yüklemekten kaçınır. Yani C/C++ kodunuz dosyalara erişmezse
FS
nesnesi ve diğer dosya sistemi API'leri çıkışa dahil edilmez. Diğer yandan, C/C++ kodunuzda dosya kullanılıyorsa dosya sistemi desteği de otomatik olarak dahil edilir.
Ne yazık ki mkbitmap
, Emscripten'in dosya sistemi desteğini otomatik olarak eklemediği durumlardan biri. Bu nedenle, bunu açıkça belirtmeniz gerekir. Bu, daha önce açıklanan emconfigure
ve emmake
adımlarını, CFLAGS
bağımsız değişkeni aracılığıyla birkaç işaret daha ayarlayarak uygulamanız gerektiği anlamına gelir. Aşağıdaki işaretler diğer projeler için de yararlı olabilir.
- Dosya sistemi desteğinin dahil edilmesi için
-sFILESYSTEM=1
değerini ayarlayın. -sEXPORTED_RUNTIME_METHODS=FS,callMain
alanını,Module.FS
veModule.callMain
dışa aktarılacak şekilde ayarlayın.- Modern bir ES6 modülü oluşturmak için
-sMODULARIZE=1
ve-sEXPORT_ES6
özelliklerini ayarlayın. - İstemin gösterilmesine neden olan ilk çalıştırmayı engellemek için
-sINVOKE_RUN=0
değerini ayarlayın.
Ayrıca bu örnekte, configure
komut dosyasına WebAssembly için derlediğinizi bildirmek için --host
işaretini wasm32
olarak ayarlamanız gerekir.
Son emconfigure
komutu şöyle görünür:
$ emconfigure ./configure --host=wasm32 CFLAGS='-sFILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=FS,callMain -sMODULARIZE=1 -sEXPORT_ES6 -sINVOKE_RUN=0'
emmake make
öğesini tekrar çalıştırmayı ve yeni oluşturulan dosyaları mkbitmap
klasörüne kopyalamayı unutmayın.
index.html
dosyasını, yalnızca script.js
ES modülünü yükleyecek şekilde değiştirin. Ardından, bu modülden mkbitmap.js
modülünü içe aktarın.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>mkbitmap</title>
</head>
<body>
<!-- No longer load `mkbitmap.js` here -->
<script src="script.js" type="module"></script>
</body>
</html>
// This is `script.js`.
import loadWASM from './mkbitmap.js';
const run = async () => {
const Module = await loadWASM();
console.log(Module);
};
run();
Şimdi uygulamayı tarayıcıda açtığınızda, Module
nesnesinin Geliştirici Araçları konsoluna kaydedildiğini görürsünüz. İstem kaybolur çünkü mkbitmap
main()
işlevi artık başlangıçta çağrılmaz.
Ana işlevi manuel olarak yürütme
Sonraki adım, Module.callMain()
komutunu çalıştırarak mkbitmap
öğesinin main()
işlevini manuel olarak çağırmaktır. callMain()
işlevi, komut satırında ileteceğiniz dizelerle tek tek eşleşen bir bağımsız değişken dizisi alır. Komut satırında mkbitmap -v
komutunu çalıştırırsanız tarayıcıda Module.callMain(['-v'])
yöntemini çağırırsınız. Bu işlem mkbitmap
sürüm numarasını Geliştirici Araçları konsoluna kaydeder.
// This is `script.js`.
import loadWASM from './mkbitmap.js';
const run = async () => {
const Module = await loadWASM();
Module.callMain(['-v']);
};
run();
Standart çıkışı yönlendir
Varsayılan olarak standart çıkış (stdout
) konsoldur. Ancak bunu başka bir şeye, örneğin çıkışı bir değişkene depolayan işleve yönlendirebilirsiniz. Bu, Module.print
özelliğini ayarlayarak çıkışı HTML'ye ekleyebileceğiniz anlamına gelir.
// This is `script.js`.
import loadWASM from './mkbitmap.js';
const run = async () => {
let consoleOutput = 'Powered by ';
const Module = await loadWASM({
print: (text) => (consoleOutput += text),
});
Module.callMain(['-v']);
document.body.textContent = consoleOutput;
};
run();
Giriş dosyasını bellek dosya sistemine al
Giriş dosyasını bellek dosya sistemine almak için komut satırında mkbitmap filename
eşdeğerinin olması gerekir. Bu konuya nasıl yaklaştığımı anlamak için önce mkbitmap
ürününün girdisini nasıl beklediği ve çıktısını nasıl oluşturduğuyla ilgili bilgi verelim.
mkbitmap
için desteklenen giriş biçimleri PNM (PBM, PGM, PPM) ve BMP'dir. Çıkış biçimleri, bit eşlemler için PBM ve gri haritalar için PGM'dir. Bir filename
bağımsız değişkeni verilirse mkbitmap
varsayılan olarak, son eki .pbm
olarak değiştirilerek giriş dosyasının adından alınan bir çıkış dosyası oluşturur. Örneğin, example.bmp
giriş dosyası adı için çıkış dosyasının adı example.pbm
olur.
Emscripten, yerel dosya sistemini simüle eden sanal bir dosya sistemi sağlar. Böylece, eşzamanlı dosya API'lerini kullanan yerel kod çok az değişiklikle veya hiç değişiklik yapılmadan derlenebilir ve çalışabilir.
mkbitmap
ürününün bir giriş dosyasını filename
komut satırı bağımsız değişkeni olarak aktarılmış gibi okuması için Emscripten'in sağladığı FS
nesnesini kullanmanız gerekir.
FS
nesnesi, bellek içi bir dosya sistemi (genellikle MEMFS olarak adlandırılır) tarafından desteklenir ve sanal dosya sistemine dosya yazmak için kullandığınız bir writeFile()
işlevine sahiptir. writeFile()
özelliğini aşağıdaki kod örneğinde gösterildiği gibi kullanıyorsunuz.
Dosya yazma işleminin çalıştığını doğrulamak için FS
nesnesinin readdir()
işlevini '/'
parametresiyle çalıştırın. example.bmp
dosyasını ve her zaman otomatik olarak oluşturulan bir dizi varsayılan dosya görürsünüz.
Sürüm numarasını yazdırmak için Module.callMain(['-v'])
için yapılan önceki çağrının kaldırıldığını unutmayın. Bunun nedeni, Module.callMain()
işlevinin genellikle yalnızca bir kez çalıştırılmasını bekleyen bir işlev olmasıdır.
// This is `script.js`.
import loadWASM from './mkbitmap.js';
const run = async () => {
const Module = await loadWASM();
const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
console.log(Module.FS.readdir('/'));
};
run();
İlk gerçek yürütme
Her şey hazır olduğunda Module.callMain(['example.bmp'])
komutunu çalıştırarak mkbitmap
yürütün. MEMFS'nin içeriğini günlüğe kaydet '/'
klasörünü tıkladığınızda, example.bmp
giriş dosyasının yanında yeni oluşturulan example.pbm
çıkış dosyasını göreceksiniz.
// This is `script.js`.
import loadWASM from './mkbitmap.js';
const run = async () => {
const Module = await loadWASM();
const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
Module.callMain(['example.bmp']);
console.log(Module.FS.readdir('/'));
};
run();
Çıkış dosyasını bellek dosya sisteminden alma
FS
nesnesinin readFile()
işlevi, son adımda oluşturulan example.pbm
öğesinin bellek dosya sisteminden alınmasını sağlar. Tarayıcılar genellikle doğrudan tarayıcı içinde görüntüleme için PBM dosyalarını desteklemediğinden işlev, File
nesnesine dönüştürdüğünüz ve diske kaydettiğiniz bir Uint8Array
değerini döndürür.
(Dosya kaydetmenin daha zarif yolları vardır, ancak dinamik olarak oluşturulmuş bir <a download>
kullanmak en yaygın olarak desteklenen yöntemdir.) Kaydedilen dosyayı favori resim görüntüleyicinizde açabilirsiniz.
// This is `script.js`.
import loadWASM from './mkbitmap.js';
const run = async () => {
const Module = await loadWASM();
const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
Module.callMain(['example.bmp']);
const output = Module.FS.readFile('example.pbm', { encoding: 'binary' });
const file = new File([output], 'example.pbm', {
type: 'image/x-portable-bitmap',
});
const a = document.createElement('a');
a.href = URL.createObjectURL(file);
a.download = file.name;
a.click();
};
run();
Etkileşimli kullanıcı arayüzü ekleme
Bu noktaya kadar, giriş dosyasına kod gömülür ve mkbitmap
, varsayılan parametrelerle çalışır. Son adım, kullanıcının dinamik olarak bir giriş dosyası seçmesine, mkbitmap
parametrelerini değiştirmesine ve ardından belirlenen seçeneklerle aracı çalıştırmasına izin vermektir.
// Corresponds to `mkbitmap -o output.pbm input.bmp -s 8 -3 -f 4 -t 0.45`.
Module.callMain(['-o', 'output.pbm', 'input.bmp', '-s', '8', '-3', '-f', '4', '-t', '0.45']);
PBM resim biçiminin ayrıştırılması özellikle zor değildir. Bu nedenle, bazı JavaScript kodlarıyla çıktı resminin bir önizlemesini bile gösterebilirsiniz. Bunu yapmanın bir yolunu öğrenmek için aşağıdaki yerleştirilmiş demonun kaynak koduna göz atın.
Sonuç
Tebrikler, mkbitmap
öğesini WebAssembly’de başarıyla derlediniz ve tarayıcıda çalışmasını sağladınız. Çıkmaz sokaklar vardı ve işe yarayana kadar aracı bir kereden fazla derlemeniz gerekti. Ancak yukarıda yazdığım gibi, bu, işin bir parçası. Takılırsanız StackOverflow'un webassembly
etiketini de unutmayın. İyi derlemeler!
Teşekkür
Bu makale Sam Clegg ve Rachel Andrew tarafından incelenmiştir.