讓C代碼在瀏覽器中運行——JavaScript慘遭拋棄?

2020-12-27 前端小劉

轉載自:www.blog.csdn.net/sinat_32582203/article/details/73355211

WebAssembly作為一種新興的Web技術,相關的資料和社區還不夠豐富,但其為web開發提供了一種嶄新的思路和工作方式,未來是很有可能大放光彩的。

使用WebAssembly,我們可以在瀏覽器中運行一些高性能、低級別的程式語言,可用它將大型的C和C++代碼庫比如遊戲、物理引擎甚至是桌面應用程式導入Web平臺。

截至目前為止,我們已經可以在Chrome、Firefox中使用WebAssembly,Edge和Safari對它的支持也基本完成。這意味著很快,就能在所有流行的瀏覽器中運行wasm了。

在這篇文章中,我們將會演示如何將簡單的C代碼編譯為wasm,並將其包含在網頁中。在此之前,我們先來直觀的了解下WebAssembly是如何工作的。

WebAssembly是如何工作的?

這裡不涉及過多技術性的問題。我們知道,在今天的瀏覽器中,JavaScript是在虛擬機(VM)中執行的,該虛擬機能夠最大化地優化代碼並壓榨每一絲的性能,這也使得JavaScript稱為速度最快的動態語言之一。但儘管如此,它還是無法與原生的C/C++代碼相媲美。所以,WebAssembly就出現了。

Wasm同樣在JavaScript虛擬機中運行,但是它表現得更好。兩者可以自由交互、互不排斥,這樣你就同時擁有了兩者最大的優勢——JavaScript巨大的生態系統和有好的語法,WebAssembly接近原生的表現性能。

大多數程式設計師會選擇使用C語言來編寫WebAssembly模塊,並將其編譯成.wasm文件。這些.wasm文件並不能直接被瀏覽器識別,所以它們需要一種稱為JavaScript膠接代碼(glue code,用於連接相互不兼容的軟體組件,詳見:http://whatis.techtarget.com/definition/glue-code)的東西來加載。

隨著未來WebAssembly框架和本地wasm模塊支持的發展,這一過程可能會有所縮短。

開發前準備

編寫WebAssembly需要不少的工具,但作為一個程式設計師,下面的工具你應該大部分都已經有了。

1、支持WebAssembly的瀏覽器,新版的Chrome或者Firefox均可(可以在此查看各個瀏覽器對某項內容的支持情況:http://caniuse.com/#feat=wasm)。

2、C到WebAssembly的編譯器,推薦使用Emscripten(https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html),安裝這個工具費時費力費空間,但沒辦法,這是目前為止最好的選擇,請仔細閱讀安裝說明,需佔用約1GB的硬碟空間。

3、一個C編譯器/開發環境,比如Linux下的GCC,OS X下的Xcode,Windows下的Visual Studio。

4、一個簡單的本地web伺服器,Linux/OS X下使用

python -m SimpleHTTPServer 9000

命令即可,Windows下可安裝IIS服務。

一、編寫C代碼

下面我們編寫一個非常簡單的C語言例子,它將會返回1-6的隨機數,在你所使用的工作目錄下,創建一個dice-roll.c文件。

#include<stdio.h>#include<stdlib.h>#include<time.h>#include<emscripten/emscripten.h>// 一旦WASM模塊被加載,main()中的代碼就會執行intmain(int argc, char ** argv){printf("WebAssembly module loaded\n");}// 返回1-6之間的一隨機數int EMSCRIPTEN_KEEPALIVE roll_dice(){srand ( time(NULL) );return rand() % 6 + 1;}

當我們將其編譯為wasm並且在瀏覽器中加載時,

main

函數會自動執行,其中的

printf

將會被翻譯成

console.log

我們想要

roll_dice

函數能夠在JavaScript中隨時調用,為此,我們需要在函數名前添加

EMSCRIPTEN_KEEPALIVE

標記以告訴Emscripten我們的意圖。

二、將C編譯為WebAssembly

現在我們已經有了C代碼,接下來需要將它編譯成wasm,不僅如此,我們還需要生成相應的JavaScript膠接代碼以便能夠真正運行起來。

這裡我們必須使用Emscripten編譯器,你會發現有大量的命令行參數和編譯方法可選,經過實踐,我們找到了下面這個最友好最實用的組合:

emcc dice-roll.c -s WASM=1 -O3 -o index.js

各個參數含義如下:

emcc——代表Emscripten編譯器;dice-roll.c——包含C代碼的文件;-s WASM=1——指定使用WebAssembly;-O3——代碼優化級別,3已經是很高的級別了;-o index.js——指定生成包含wasm模塊所需的全部膠接代碼的JS文件;

需要注意的是,儘管上面的emcc選項能夠很好地應對我們這個例子,但在更複雜的情況下,好需要使用不同的方法,可查看官方文檔了解更多內容:http://kripken.github.io/emscripten-site/docs/tools_reference/emcc.html#emccdoc。

三、在瀏覽器中加載WebAssembly代碼

現在我們將回到熟悉的web開發領域,在當前文件夾創建index.html文件,引入相關的js文件與CSS文件。

<!DOCTYPE html><head><metacharset="utf-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><title>WebAssembly 示例</title><metaname="viewport"content="width=device-width, initial-scale=1"><linkrel="stylesheet"href="CSS/styles.css"><linkrel="stylesheet"href="CSS/dice-1.0.min.css"></head><body><divclass="dice dice-6"></div><span>點擊以搖動篩子</span><!-- 引入JavaScript膠節文件 --><!-- 這將會加載WebAssembly模塊並運行其main函數 --><scriptsrc="index.js"></script></body></html>

至此,項目結構已經完整,如下:

style.css簡單設置一下頁面樣式:

body {padding: 40px;font: normal 16px sans-serif;display: flex;flex-direction: column;}.dice {display: block !important;font-size: 80px;margin: 0 auto 20px;cursor: pointer;}span {display: block;margin: 0 auto;color: #333;}

dice-1.0.min.js是來自Github(https://github.com/diafygi/dice-css)的一個微型CSS骰子樣式庫,包括了1-6的SVG矢量圖,可作為內聯圖標使用,用法與font-awesome和glyphicons相同。

由於跨源問題的存在,我們需要一個本地伺服器才能運行這個項目。在Linux/OS X系統中,可以在項目目錄下運行如下命令:

python -m SimpleHTTPServer 9000

然後到瀏覽器中,打開localhost:9000以查看這個小應用。按F12打開控制臺,即可看到我們在C代碼中使用printf輸出的問候語:

四、調用WebAssembly函數

最後一步是連接JavaScript與WebAssembly,由於膠接代碼的存在(index.js),這項任務變得非常簡單,它已經為我們處理好了所有的接線任務。

在瀏覽器中處理WebAssembly有一個非常強大的API可以使用,在此我們不會進行深入探討因為這已經超出了入門的範疇,我們只需要Module接口及其ccall方法這部分即可。該方法允許我們通過函數名從C代碼中調用一個函數,然後就向一般的JS函數一樣使用就行了。

var result = Module.ccall('funcName', // 函數名'number', // 返回類型['number'], // 參數類型[42]); // 參數調用此方法之後,result就將擁有對應C函數的所有功能,除函數名以外的所有參數都是可選的。我們也可以使用縮寫版:// 通過在函數名前添加下劃線來調用C函數var result = _funcName();

roll_dice

函數無需任何參數,在JavaScript代碼中調用十分簡單:

<script>// 當HTML dice元素被點擊時,其值將會被改變var dice = document.querySelector('.dice');dice.addEventListener('click', function(){// 調用C代碼中的roll_dice函數var result = _roll_dice();dice.className = "dice dice-" + result; });</script>

將上面這段代碼添加到index.html末尾,即

之前即可。

此時運行項目,即可看到結果:

總結

雖然現在WebAssembly還在發展的初期,但從公布的新標準來看,潛力巨大。在瀏覽器中運行低級語言的能力,將會帶來全新的應用程式與web體驗,而這,是僅僅通過JavaScript無法使用的。

誠然,使用WebAssembly在當前階段還十分繁瑣,文檔需要分為多個部分,相應的工具也不容易使用,並且還需要JavaScript膠接代碼才能使用wasm模塊。但隨著越來越多的人進入這個平臺,所有這些問題都將會被解決。

令附一些參考資料:

WebAssembly官網(www.webassembly.org/)WebAssembly on MDN(www.developer.mozilla.org/en-US/docs/WebAssembly)Awesome WASM(www.github.com/mbasso/awesome-wasm)Emscripten官網(www.kripken.github.io/emscripten-site/index.html)

相關焦點

  • JavaScript引擎實現JVM 支持運行Java
    一般的高級語言如果要在不同的平臺上運行,至少需要編譯成不同的目標代碼。而引入JVM後,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用JVM屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。JVM在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。
  • JavaScript中使用bind()方法讓代碼更乾淨
    在我最近的編碼中,我發現了一個更簡單的方法。在這個過程中,它消除了匿名函數並刪除了linting錯誤,「不要在循環中寫函數」。你看,我一直在使用JavaScript中的bind()試驗。事實證明,我們可以在多個場合,包括處理我幾周前提到的閉包問題中,使用bind。什麼是bind()?
  • JavaScript為什麼這麼難?
    javascript中的this不是靜態綁定的,而是動態綁定的。而且可以人為改變指向。看看下面這個例子理解在對象構造器中this.getName,A.prototype.getName,和A.getName的區別。
  • 如何安全的運行第三方javascript代碼(中)
    在主線程上運行的好處是插件可以:1.直接編輯文檔而不是副本,避免加載時間問題。2.可以運行複雜的組件更新和約束邏輯,而無需為代碼置辦兩個副本。3.在需要同步API時,可以使用同步API調用。這樣的話,更新的加載或刷新就不會發生混淆。
  • 探秘JavaScript中的六個字符
    -1370.html原文:http://jazcash.com/a-javascript-journey-with-only-six-characters/JavaScript 是一個奇怪而有趣的語言,我們可以寫一些瘋狂卻仍然有效的代碼。
  • 第一篇:JavaScript基本語法
    第一個JavaScript程序JavaScript是腳本語言,它可以直接嵌入到HTML網頁,由瀏覽器一邊解釋一邊執行。JavaScript程序一般是在客戶端運行,當然也可以在伺服器端運行。例如:Node.js就可以讓JavaScript運行在服務端,使用JavaScript技術構建WEB伺服器。
  • 0基礎學習JavaScript一定要知道如何使用VS2019去編寫代碼
    VS2019就會為我們創建出一個具有Html代碼的頁面。JavaScript代碼就是在Html頁面上使用的,在瀏覽器中解釋和執行。現在我們編寫第一行JavaScript代碼:<!JavaScript代碼的基本使用如下說明:一、代碼編寫位置<script type="text/javascript">----JavaScript代碼段</script&
  • 在HTML中使用JavaScript實例代碼「言午」
    此文章主要為大家介紹了在HTML中使用JavaScript實例代碼,具有一定的參考價值,學習覺得挺不錯的,分享給大家。 type(可選):關鍵詞:MIME(腳本語言的內容類型)為保證最大限度的瀏覽器兼容,type的屬性值主要時候用的依舊是text/javascript,如果沒有寫這個屬性,其默認值仍然為text/javascript。
  • 從零開始學代碼,五分鐘了解一下JavaScript!
    JavaScript是嵌入到瀏覽器軟體當中去的,只要你的電腦有瀏覽器就可以執行JS程序了。JavaScript是一種面向對象的程序語言。在程序中,對象是由「屬性」和「方法」構成在現實中,男女朋友就是一個「對象」。
  • JavaScript入門教程
    JavaScript可插入 HTML 頁面的編程代碼,由所有的瀏覽器執行,從服務端被下載到客戶端由瀏覽器執行。BOM結構圖3) 內容簡介windowBOM使javascript有能力與瀏覽器進行溝通,這個溝通從window對象開始,所有的window對象的屬性和方法自動成為javascript的全局變量和全局函數
  • JavaScript 基礎語法
    代碼分析:滑鼠點擊div標籤的時候,會出現彈窗並且裡面顯示的內容是「利利、堡堡」;可以把標籤裡的onclick進行拆開理解,on是「在什麼上」的意思,click是點擊的意思,結合起來的意思是在標籤上進行點擊;onclick中引號的內容就是JavaScript代碼;整個代碼結合起來意思是在標籤上進行點擊的時候,會運行onclick
  • JavaScript 中 Eval 函數的前世今生,執行代碼字符串
    代碼壓縮工具(在把 JS 投入生產環境前對其進行壓縮的工具)將局部變量重命名為更短的變量(例如 a 和 b 等),以使代碼體積更小。這通常是安全的,但在使用了 eval 的情況下就不一樣了,因為局部變量可能會被 eval 中的代碼訪問到。因此壓縮工具不會對所有可能會被從 eval 中訪問的變量進行重命名。這樣會導致代碼壓縮率降低。
  • 開發者必備的Javascript單元測試工具
    jsTestDriver以客戶服務端的方式運行,在客戶端發送測試請求到服務端,整個運行是在可以捕捉的瀏覽器中進行的,其優點為它能很容易地與代碼編輯器整合,以及能成為自動構建的一部分。接下來,我們配置jsTestDriver的服務端,以讓其監控chrome瀏覽器,讓其運行Javascript測試用例。在命令行輸入如下代碼,具體路徑請根據實際情況修改。
  • 在JavaScript中使用Window對象的警告框、確認框、窗口尺寸和操作
    常用方法和屬性在瀏覽器中,除了我們用戶以可視化的方式操作瀏覽器之外,各大瀏覽器還提供了以對象模型的方式讓開發者也能操作瀏覽器。如瀏覽器的「後退」功能,除了可以使用滑鼠點擊實現之外,還可以使用BOM實現。對於學習JavaScript開發者來說,最常用的與瀏覽器交互的就是彈出警告框和確認框。
  • javaScript入門—函數和document使用方法,新手必看!
    javaScript是世界上流行一種客戶端語言,是一種弱類型語言, javaScript語言解釋器為瀏覽器的一部分,javaScript這些優勢更好適應了網站頁面的特點。javaScript代碼只需要嵌入到html頁面中(頁面頭部、body,javaScript代碼大部分放在頁面頭部)就可以執行,也不需要任何額外的插件,大多數瀏覽器(firefox、ie、oprea、safari等)都可以直接運行javaScript代碼。上面代碼在瀏覽器中打開會顯示「hello !」的alert的對話框,如果沒有檢查一下瀏覽器是否啟用了javascript。
  • 如何在javascript中創建一個對象?
    javascript是一門基於對象而不是面向對象的語言,由於它的這個缺陷,在javascript中實現面向對象時十分彆扭,就比如創建對象,由於在ES6之前沒有class關鍵字,想要創建對象必須依賴以下幾種間接方式。
  • 5種用於前端開發的JavaScript替代方案
    JavaScript仍然是在瀏覽器中運行的唯一語言,因此這些新語言主要是編譯或轉換為JavaScript。CoffeeScript是第一個類似的腳本,但很快就被眾多競爭對手所超越,每個競爭對手都對JavaScript進行了改進。  Dart  Dart是一種面向對象的c語言,由Google創建,作為JavaScript的替代品。
  • JavaScript是什麼
    面向對象的思想—java、 C#腳本—寫好的程序不需要中間轉換,就能立即在運行環境中運行。javaScript,SQL為html網頁提供動態效果【特效】。完成與後臺處理程序數據交互。【1.發請求{要} 2.處理數據】簡單的具體操作:1.直接向html文件中寫出標記和內容。
  • 在JavaScript中,使用replace()、test()和exec()方法匹配字符串
    JavaScript核心代碼如下:<script type="text/javascript">var str = "Hello Microsoft!"現在將JS代碼運行一下結果:圖1從圖1的運行結果上看,已經將Hello Microsoft替換為Hello Google字符串。在Replace()方法中,使用了正則表達式,則只要能在正則表達式上匹配到,則就會發生替換。
  • const 並不能加快 C 代碼的運行速度?
    簡單的測試讓我們思考一個最簡單的例子,曾經我以為這個例子中的const能夠加快C代碼運行速度。$ clang -S -Wall -O3 -emit-llvm test.c$ view test.ll生成的IR(中間代碼表示形式)如下所示,比彙編代碼更為緊湊,所以我把兩個函數的代碼都羅列出來了,你可以看到我說的一點都沒錯:「除了那個調用之外,二者毫無區別」。