WebAssembly nedir ve nereden çıktı? başlıklı makalede, Günümüzdeki WebAssembly'nin nasıl ortaya çıktığını açıkladım. Bu makalede, mevcut bir C programını (mkbitmap
) WebAssembly'ye derleme yaklaşımımı göstereceğim. Dosyalarla çalışma, WebAssembly ve JavaScript arasında iletişim kurma ve tuval çizme gibi işlemler içerdiğinden hello world örneğinden daha karmaşıktır ancak sizi bunaltmayacak kadar yönetilebilir.
Bu makale, WebAssembly'yi öğrenmek isteyen web geliştiriciler için yazılmıştır ve mkbitmap
gibi bir şeyi WebAssembly'ye derlemek isterseniz adım adım nasıl ilerleyebileceğinizi gösterir. İlk çalıştırmada bir uygulamanın veya kitaplığın derlenmemesi tamamen normaldir. Bu nedenle, aşağıda açıklanan adımların bazıları çalışmadı ve geri dönüp farklı bir şekilde tekrar denemem gerekti. Makalede, nihai derleme komutu gökten düşmüş gibi gösterilmiyor. Bunun yerine, bazı hayal kırıklıkları da dahil olmak üzere gerçek ilerleme durumum açıklanıyor.
mkbitmap
hakkında
mkbitmap
C programı bir resmi okur ve sırasıyla şu işlemlerden birini veya daha fazlasını uygular: ters çevirme, yüksek geçiren filtreleme, ölçeklendirme ve eşikleme. Her işlem ayrı ayrı kontrol edilebilir ve etkinleştirilip devre dışı bırakılabilir. mkbitmap
'nın temel kullanım amacı, renkli veya gri tonlamalı görüntüleri diğer programlar için giriş olarak uygun bir biçime, özellikle de SVGcode'un temelini oluşturan izleme programı potrace
için uygun bir biçime dönüştürmektir. Bir ön işleme aracı olarak mkbitmap
, özellikle karikatürler veya el yazısı metinler gibi taranmış çizimleri yüksek çözünürlüklü iki seviyeli görüntülere dönüştürmek için kullanışlıdır.
mkbitmap
komutunu kullanmak için bu komuta bir dizi seçenek ve bir veya daha fazla dosya adı iletmeniz gerekir. Tüm ayrıntılar için aracın man sayfasına bakın:
$ mkbitmap [options] [filename...]


mkbitmap -f 2 -s 2 -t 0.48
(Kaynak).Kodu alın
İlk adım, mkbitmap
kaynak kodunu almaktır. Bu bilgiyi projenin web sitesinde bulabilirsiniz. Bu yazı yazıldığı sırada en son sürüm potrace-1.16.tar.gz'dir.
Yerel olarak derleme ve yükleme
Bir sonraki adım, nasıl davrandığını anlamak için aracı yerel olarak derleyip yüklemektir. INSTALL
dosyası aşağıdaki talimatları içerir:
cd
komutunu kullanarak paketin kaynak kodunu içeren dizine gidin ve./configure
komutunu yazarak paketi sisteminiz için yapılandırın.configure
komutunun çalıştırılması biraz zaman alabilir. Çalışırken hangi özellikleri kontrol ettiğini belirten bazı mesajlar yazdırır.Paketi derlemek için
make
yazın.İsteğe bağlı olarak, pakette bulunan tüm kendi kendine testleri çalıştırmak için
make check
yazın. Bu testler genellikle yeni oluşturulmuş, yüklenmemiş ikili dosyaları kullanır.Programları, veri dosyalarını ve belgeleri yüklemek için
make install
yazın. Kök kullanıcının sahip olduğu bir öneke yükleme yaparken paketin normal bir kullanıcı olarak yapılandırılması ve oluşturulması, yalnızcamake install
aşamasının kök kullanıcı ayrıcalıklarıyla yürütülmesi önerilir.
Bu adımları uyguladığınızda potrace
ve mkbitmap
olmak üzere iki yürütülebilir dosya elde edersiniz. Bu makalenin odak noktası mkbitmap
dosyasıdır. mkbitmap --version
komutunu çalıştırarak doğru çalıştığını doğrulayabilirsiniz. Kısa olması için önemli ölçüde kırpılmış olan, makinemdeki dört adımın çıktısı aşağıda verilmiştir:
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'.
Çalışıp çalışmadığı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
'yı başarıyla derleyip yüklemişsinizdir. Ardından, bu adımların WebAssembly ile çalışmasını sağlayın.
mkbitmap
dilini WebAssembly'ye derleme
Emscripten, C/C++ programlarını WebAssembly'ye derlemek için kullanılan bir araçtır. Emscripten'in Building Projects (Projeleri Oluşturma) dokümanında şunlar belirtilmektedir:
Emscripten ile büyük projeler oluşturmak çok kolaydır. Emscripten, makefile'larınızı
gcc
yerineemcc
kullanacak şekilde yapılandıran iki basit komut dosyası sağlar. Çoğu durumda, projenizin mevcut derleme sisteminin geri kalanı değişmeden kalır.
Belgelerde daha sonra (kısa olması için biraz düzenlenerek) şu bilgiler verilir:
Normalde aşağıdaki komutlarla derleme yaptığınızı düşünün:
./configure
make
Emscripten ile derleme yapmak için bunun yerine aşağıdaki komutları kullanırsınız:
emconfigure ./configure
emmake make
Yani ./configure
, emconfigure ./configure
'ye, make
ise emmake make
'e dönüşür. Aşağıda, bu işlemin mkbitmap
ile nasıl yapılacağı gösterilmektedir.
Adım 0, 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 yolunda gittiyse dizinde artık .wasm
dosya olmalıdır. Bu dosyaları find . -name "*.wasm"
komutunu çalıştırarak bulabilirsiniz:
$ find . -name "*.wasm"
./a.wasm
./src/mkbitmap.wasm
./src/potrace.wasm
Son iki dosya umut verici görünüyor. Bu nedenle, cd
dizinine src/
. Ayrıca, mkbitmap
ve potrace
olmak üzere iki yeni ilgili dosya da eklendi. Bu makale için yalnızca mkbitmap
geçerlidir. .js
uzantısının olmaması biraz kafa karıştırıcı olsa da bunlar aslında JavaScript dosyalarıdır ve hızlı 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
işlevini çağırarak JavaScript dosyasını mkbitmap.js
olarak yeniden adlandırın (isterseniz mv potrace potrace.js
işlevini de çağırabilirsiniz).
Şimdi, node mkbitmap.js --version
komutunu çalıştırarak dosyayı komut satırında Node.js ile yürütüp çalışıp çalışmadığını görmek için ilk testi yapma zamanı:
$ node mkbitmap.js --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.
mkbitmap
, WebAssembly'ye başarıyla derlendi. Şimdi bir sonraki adım, uygulamanın tarayıcıda çalışmasını sağlamaktır.
mkbitmap
Tarayıcıda WebAssembly ile
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 standart dosya 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
dizinine hizmet veren 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 kılavuz sayfasına göre "dosya adı bağımsız değişkeni verilmezse mkbitmap, standart girişten okuyarak filtre görevi görür". Emscripten'de standart giriş varsayılan olarak prompt()
olduğundan bu durum beklenir.
Otomatik yürütmeyi önleme
mkbitmap
işlevinin hemen yürütülmesini durdurup bunun yerine kullanıcı girişini beklemesini sağlamak için Emscripten'in Module
nesnesini anlamanız gerekir. Module
, Emscripten tarafından oluşturulan kodun yürütülmesinin çeşitli noktalarında çağırdığı özelliklere sahip genel bir JavaScript nesnesidir.
Kodun yürütülmesini kontrol etmek için Module
uygulamasını sağlayabilirsiniz.
Bir Emscripten uygulaması başlatıldığında Module
nesnesindeki değerlere bakar ve bunları uygular.
mkbitmap
durumunda, istemin görünmesine neden olan ilk çalıştırmayı önlemek için Module.noInitialRun
değerini true
olarak ayarlayın. script.js
adlı bir komut dosyası oluşturun, index.html
içindeki <script src="mkbitmap.js"></script>
'den önce ekleyin ve script.js
'ya aşağıdaki kodu ekleyin. Uygulamayı yeniden yüklediğinizde istem artık görünmez.
var Module = {
// Don't run main() at page load
noInitialRun: true,
};
Birkaç derleme işareti daha içeren modüler bir derleme oluşturma
Uygulamaya giriş sağlamak için Module.FS
içinde Emscripten'in dosya sistemi desteğini kullanabilirsiniz. Belgelerin Dosya Sistemi Desteği Dahil bölümünde şunlar belirtilir:
Emscripten, dosya sistemi desteğinin otomatik olarak dahil edilip edilmeyeceğine karar verir. Birçok programın dosyalara ihtiyacı yoktur ve dosya sistemi desteği boyut olarak önemsiz değildir. Bu nedenle Emscripten, bir neden görmediğinde bunu dahil etmekten kaçınır. Bu, C/C++ kodunuz dosyalara erişmiyorsa
FS
nesnesinin ve diğer dosya sistemi API'lerinin çıkışa dahil edilmeyeceği anlamına gelir. Diğer yandan, C/C++ kodunuzda dosya kullanılıyorsa dosya sistemi desteği otomatik olarak eklenir.
Maalesef mkbitmap
, Emscripten'in dosya sistemi desteğini otomatik olarak dahil etmediği durumlardan biridir. Bu nedenle, bunu yapmasını 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 ayarlanan birkaç ek işaretle birlikte uygulamanız gerektiği anlamına gelir. Aşağıdaki işaretler diğer projelerde de işe yarayabilir.
- Dosya sistemi desteğinin dahil edilmesi için
-sFILESYSTEM=1
'ı ayarlayın. Module.FS
veModule.callMain
dışa aktarılacak şekilde-sEXPORTED_RUNTIME_METHODS=FS,callMain
değerini ayarlayın.- Modern bir ES6 modülü oluşturmak için
-sMODULARIZE=1
ve-sEXPORT_ES6
değerlerini ayarlayın. - İstemin görünmesine neden olan ilk çalıştırmayı önlemek için
-sINVOKE_RUN=0
'ı ayarlayın.
Ayrıca, bu özel durumda, configure
komut dosyasına WebAssembly için derleme yaptığınızı bildirmek üzere --host
işaretini wasm32
olarak ayarlamanız gerekir.
Son emconfigure
komutu şu şekilde 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
komutunu tekrar çalıştırmayı ve yeni oluşturulan dosyaları mkbitmap
klasörüne kopyalamayı unutmayın.
index.html
dosyasını yalnızca ES modülü script.js
'nü yükleyecek şekilde değiştirin. Ardından, mkbitmap.js
modülünü bu dosyadan 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();
Uygulamayı artık tarayıcıda açtığınızda Module
nesnesinin DevTools konsoluna kaydedildiğini görmeniz gerekir. mkbitmap
işlevinin main()
işlevi artık başlangıçta çağrılmadığından istem kaybolur.
Ana işlevi manuel olarak yürütme
Sonraki adım, Module.callMain()
komutunu çalıştırarak mkbitmap
'nın main()
işlevini manuel olarak çağırmaktır. callMain()
işlevi, komut satırında ileteceğiniz değerlerle bire bir eşleşen bir bağımsız değişken dizisi alır. Komut satırında mkbitmap -v
komutunu çalıştırıyorsanız tarayıcıda Module.callMain(['-v'])
komutunu çağırırsınız. Bu işlem, mkbitmap
sürüm numarasını DevTools 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önlendirme
Varsayılan olarak standart çıkış (stdout
) konsoldur. Ancak bunu başka bir şeye yönlendirebilirsiniz. Örneğin, çıkışı bir değişkende depolayan bir işleve. 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 aktarma
Giriş dosyasını bellek dosya sistemine almak için komut satırında mkbitmap filename
komutuna eşdeğer bir komut kullanmanız gerekir. Bu konuya nasıl yaklaştığımı anlamak için öncelikle mkbitmap
'ın girişini nasıl beklediği ve çıkışını nasıl oluşturduğu hakkında biraz 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, gri eşlemler için ise PGM'dir. filename
bağımsız değişkeni verilirse mkbitmap
, varsayılan olarak son eki .pbm
olarak değiştirerek giriş dosyasının adından elde edilen bir çıkış dosyası oluşturur. Örneğin, example.bmp
giriş dosyasının adı example.pbm
olur.
Emscripten, yerel dosya sistemini simüle eden bir sanal dosya sistemi sağlar. Böylece, senkron dosya API'lerini kullanan yerel kod, çok az değişiklikle veya hiç değişiklik yapılmadan derlenip çalıştırılabilir.
mkbitmap
'nın bir giriş dosyasını filename
komut satırı bağımsız değişkeni olarak iletilmiş 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 dosyaları sanal dosya sistemine yazmak için kullandığınız bir writeFile()
işlevine sahiptir. Aşağıdaki kod örneğinde gösterildiği gibi writeFile()
kullanırsınız.
Dosya yazma işleminin çalıştığını doğrulamak için FS
nesnesinin readdir()
işlevini '/'
parametresiyle çalıştırın. example.bmp
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'])
'a 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ı gereken 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 mkbitmap
komutunu Module.callMain(['example.bmp'])
çalıştırarak yürütün. MEMFS' '/'
klasörünün içeriğini günlüğe kaydedin. Yeni oluşturulan example.pbm
çıkış dosyasını example.bmp
giriş dosyasının yanında görürsünüz.
// 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 çıkarma
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 PBM dosyalarını doğrudan tarayıcıda görüntülemeyi desteklemediğinden işlev, Uint8Array
döndürür. Bu dosyayı File
nesnesine dönüştürüp diske kaydedersiniz.
(Dosya kaydetmenin daha zarif yolları vardır ancak dinamik olarak oluşturulan <a download>
en yaygın olarak desteklenen yöntemdir.) Dosya kaydedildikten sonra, en sevdiğiniz resim görüntüleyicide 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ı sabit kodlanmıştır ve mkbitmap
, varsayılan parametrelerle çalışır. Son adımda, kullanıcının giriş dosyasını dinamik olarak seçmesine, mkbitmap
parametrelerini ayarlamasına ve ardından aracı seçilen seçeneklerle çalıştırmasına izin verilir.
// 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, biraz JavaScript koduyla çıkış resminin önizlemesini bile gösterebilirsiniz. Bunu yapmanın bir yolu için aşağıdaki yerleştirilmiş demoyu kaynak kodu ile inceleyin.
Sonuç
Tebrikler! mkbitmap
öğesini WebAssembly'ye başarıyla derlediniz ve tarayıcıda çalışmasını sağladınız. Bazı çıkmaz sokaklar vardı ve çalışana kadar aracı birden fazla kez derlemeniz gerekti. Ancak yukarıda da yazdığım gibi bu, deneyimin bir parçası. Takılırsanız StackOverflow'un webassembly
etiketini de kullanabilirsiniz. İyi derlemeler!
Teşekkür
Bu makale Sam Clegg ve Rachel Andrew tarafından incelenmiştir.