不知不覺,Google已經正式推出其Native Client (NaCl)過去約7個月之久。而目前國內似乎還沒有多少關於NaCl的資料,所以在這裡面向Web開發者做一下簡單的介紹,希望能夠起到一個拋磚引玉的效果。
本文的所有代碼均來自於https://developers.google.com/native-client/devguide/tutorial,如果您對其中的任何技術細節存在疑問,請以原文為準。
何謂NaCl
NaCl是一項能讓C/C++代碼運行在瀏覽器當中的技術。這是一個最通俗的說法,但不夠精確。嚴格來說,NaCl技術在理論上能夠實現任何編譯型語言都在其之上運行。
但是目前由於技術上的原因,NaCl還做不到任何語言任何平台的編譯型語言支持。
因為NaCl所憧憬的實際是LLVM技術,LLVM技術的要點即在於能將編譯型語言轉化為一個統一的中間語言,NaCl通過對這個中間語言的執行,即可達成任何編譯型語言的運行。換句話說,NaCl實際上希望搭建一個虛擬機。
不過LLVM現在還不夠成熟,NaCl不得不先使用GNU的編譯套件,使用LLVM技術的版本被稱為了PNaCl,目前還沒有正式推出。同時也因為這個原因,ARM架構沒能正式支持。
為什麼NaCl
在開發層面上,NaCl希望解決一個問題:JavaScript的低效率。當然,從經營策略上來說,Google可能還希望籍此將桌面領域的成熟軟件快速移植到其Chrome OS當中,不過這不是我們討論的重點。
JavaScript畢竟是一門解釋型語言,只有當瀏覽器執行到代碼的時刻才能夠看到代碼,因此在執行優化上力度非常小。
但是Web應用發展至今,效率已經必須拿到桌面上考慮,如果你還想在瀏覽器裡面看到更多優質的遊戲的話。
有關限制
- 缺乏能夠切合的IDE
- 不支持硬件異常
- 不支持創建子進程
- 不支持原生TCP/UDP操作(但已提供了websocket支持)
- 不支持同步I/O
- 不支持內存剩餘查詢
- 內聯彙編代碼必須通過NaCl驗證
- NaCl的Pepper API必須通過主線程使用
如何使用
上面那些對於行動主義來說其實都是P,真正需要聚焦的還是如何使用。
NaCl的典型項目由三個部分組成:
網頁(*.html)。這裡所指的網頁是一個泛指,它包括JS代碼、CSS樣式表已經HTML代碼。
NaCl模塊(*.c;*.cc)。這是C/C++代碼的文件。
清單(*.nmf)。這份清單類似於Chrome Extension的清單,主要用於指明在不同架構的機器上調用什麼模塊。
在真正開始之前,我們還需要安裝一個NaCl的SDK。這個SDK當中主要包含了NaCl的模塊編譯工具鏈。大家可以從這裡下載:https://developers.google.com/native-client/sdk/download
安裝之前,確保一下機器上有一個可用的Python 2.7,並加入到環境變量當中。
而SDK的安裝則相當簡單,只需要使用naclsdk update命令即可。
下面,創建一個名為hello_tutorial的目錄,我們來搭建一個簡單的demo。
網頁
<script type="text/javascript"> hello_tutorialModule = null; // Global application object. statusText = 'NO-STATUS'; // Indicate load success. function moduleDidLoad() { hello_tutorialModule = document.getElementById('hello_tutorial'); updateStatus('SUCCESS'); } // The 'message' event handler. This handler is fired when the NaCl module // posts a message to the browser by calling PPB_Messaging.PostMessage() // (in C) or pp::Instance.PostMessage() (in C++). This implementation // simply displays the content of the message in an alert panel. function handleMessage(message_event) { alert(message_event.data); } // If the page loads before the Native Client module loads, then set the // status message indicating that the module is still loading. Otherwise, // do not change the status message. function pageDidLoad() { if (hello_tutorialModule == null) { updateStatus('LOADING...'); } else { // It's possible that the Native Client module onload event fired // before the page's onload event. In this case, the status message // will reflect 'SUCCESS', but won't be displayed. This call will // display the current message. updateStatus(); } } // Set the global status message. If the element with id 'statusField' // exists, then set its HTML to the status message as well. // opt_message The message test. If this is null or undefined, then // attempt to set the element with id 'statusField' to the value of // |statusText|. function updateStatus(opt_message) { if (opt_message) statusText = opt_message; var statusField = document.getElementById('status_field'); if (statusField) { statusField.innerHTML = statusText; } } </script> </head> <body onload="pageDidLoad()"> <h1>Native Client Module hello_tutorial</h1> <p> <!-- Load the published .nexe. This includes the 'nacl' attribute which shows how to load multi-architecture modules. Each entry in the "nexes" object in the .nmf manifest file is a key-value pair: the key is the instruction set architecture ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. To load the debug versions of your .nexes, set the 'nacl' attribute to the _dbg.nmf version of the manifest file. Note: Since this NaCl module does not use any real-estate in the browser, it's width and height are set to 0. Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' and a 'message' event listener attached. This wrapping method is used instead of attaching the event listeners directly to the <EMBED> element to ensure that the listeners are active before the NaCl module 'load' event fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or pp::Instance.PostMessage() (in C++) from within the initialization code in your NaCl module. --> <div id="listener"> <script type="text/javascript"> var listener = document.getElementById('listener'); listener.addEventListener('load', moduleDidLoad, true); listener.addEventListener('message', handleMessage, true); </script> <embed name="nacl_module" id="hello_tutorial" width=0 height=0 src="hello_tutorial.nmf" type="application/x-nacl" /> </div> </p> <h2>Status</h2> <div id="status_field">NO-STATUS</div> </body> </html>
NaCl模塊
/// @file hello_tutorial.cc /// This example demonstrates loading, running and scripting a very simple NaCl /// module. To load the NaCl module, the browser first looks for the /// CreateModule() factory method (at the end of this file). It calls /// CreateModule() once to load the module code from your .nexe. After the /// .nexe code is loaded, CreateModule() is not called again. /// /// Once the .nexe code is loaded, the browser than calls the CreateInstance() /// method on the object returned by CreateModule(). It calls CreateInstance() /// each time it encounters an <embed> tag that references your NaCl module. /// /// The browser can talk to your NaCl module via the postMessage() Javascript /// function. When you call postMessage() on your NaCl module from the browser, /// this becomes a call to the HandleMessage() method of your pp::Instance /// subclass. You can send messages back to the browser by calling the /// PostMessage() method on your pp::Instance. Note that these two methods /// (postMessage() in Javascript and PostMessage() in C++) are asynchronous. /// This means they return immediately - there is no waiting for the message /// to be handled. This has implications in your program design, particularly /// when mutating property values that are exposed to both the browser and the /// NaCl module. #include <cstdio> #include <string> #include "ppapi/cpp/instance.h" #include "ppapi/cpp/module.h" #include "ppapi/cpp/var.h" /// The Instance class. One of these exists for each instance of your NaCl /// module on the web page. The browser will ask the Module object to create /// a new Instance for each occurence of the <embed> tag that has these /// attributes: /// type="application/x-nacl" /// src="hello_tutorial.nmf" /// To communicate with the browser, you must override HandleMessage() for /// receiving messages from the borwser, and use PostMessage() to send messages /// back to the browser. Note that this interface is entirely asynchronous. class hello_tutorialInstance : public pp::Instance { public: /// The constructor creates the plugin-side instance. /// @param[in] instance the handle to the browser-side plugin instance. explicit hello_tutorialInstance(PP_Instance instance) : pp::Instance(instance) {} virtual ~hello_tutorialInstance() {} /// Handler for messages coming in from the browser via postMessage(). The /// @a var_message can contain anything: a JSON string; a string that encodes /// method names and arguments; etc. For example, you could use /// JSON.stringify in the browser to create a message that contains a method /// name and some parameters, something like this: /// var json_message = JSON.stringify({ "myMethod" : "3.14159" }); /// nacl_module.postMessage(json_message); /// On receipt of this message in @a var_message, you could parse the JSON to /// retrieve the method name, match it to a function call, and then call it /// with the parameter. /// @param[in] var_message The message posted by the browser. virtual void HandleMessage(const pp::Var& var_message) { // TODO(sdk_user): 1. Make this function handle the incoming message. } }; /// The Module class. The browser calls the CreateInstance() method to create /// an instance of your NaCl module on the web page. The browser creates a new /// instance for each <embed> tag with type="application/x-nacl". class hello_tutorialModule : public pp::Module { public: hello_tutorialModule() : pp::Module() {} virtual ~hello_tutorialModule() {} /// Create and return a hello_tutorialInstance object. /// @param[in] instance The browser-side instance. /// @return the plugin-side instance. virtual pp::Instance* CreateInstance(PP_Instance instance) { return new hello_tutorialInstance(instance); } }; namespace pp { /// Factory function called by the browser when the module is first loaded. /// The browser keeps a singleton of this module. It calls the /// CreateInstance() method on the object you return to make instances. There /// is one instance per <embed> tag on the page. This is the main binding /// point for your NaCl module with the browser. Module* CreateModule() { return new hello_tutorialModule(); } } // namespace pp
Makefile
Makefile是C\C++編譯指令的存放文件,這份文件將指引編譯器、鏈接器如何工作。因為我們現在所處情況特殊,所以Makefile需要自己編寫。
# Copyright (c) 2012 The Native Client Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # # GNU Make based build file. For details on GNU Make see: # http://www.gnu.org/software/make/manual/make.html # # # Project information # # These variables store project specific settings for the project name # build flags, files to copy or install. In the examples it is typically # only the list of sources and project name that will actually change and # the rest of the makefile is boilerplate for defining build rules. # PROJECT:=hello_tutorial LDFLAGS:=-lppapi_cpp -lppapi CXX_SOURCES:=$(PROJECT).cc # # Get pepper directory for toolchain and includes. # # If NACL_SDK_ROOT is not set, then assume it can be found a two directories up, # from the default example directory location. # THIS_MAKEFILE:=$(abspath $(lastword $(MAKEFILE_LIST))) NACL_SDK_ROOT?=$(abspath $(dir $(THIS_MAKEFILE))../..) # Project Build flags WARNINGS:=-Wno-long-long -Wall -Wswitch-enum -pedantic -Werror CXXFLAGS:=-pthread -std=gnu++98 $(WARNINGS) # # Compute tool paths # # OSNAME:=$(shell python $(NACL_SDK_ROOT)/tools/getos.py) TC_PATH:=$(abspath $(NACL_SDK_ROOT)/toolchain/$(OSNAME)_x86_newlib) CXX:=$(TC_PATH)/bin/i686-nacl-g++ # # Disable DOS PATH warning when using Cygwin based tools Windows # CYGWIN ?= nodosfilewarningexport CYGWIN # Declare the ALL target first, to make the 'all' target the default build all: $(PROJECT)_x86_32.nexe $(PROJECT)_x86_64.nexe # Define 32 bit compile and link rules for main application x86_32_OBJS:=$(patsubst %.cc,%_32.o,$(CXX_SOURCES)) $(x86_32_OBJS) : %_32.o : %.cc $(THIS_MAKE) $(CXX) -o $@ -c $< -m32 -O0 -g $(CXXFLAGS) $(PROJECT)_x86_32.nexe : $(x86_32_OBJS) $(CXX) -o $@ $^ -m32 -O0 -g $(CXXFLAGS) $(LDFLAGS) # Define 64 bit compile and link rules for C++ sources x86_64_OBJS:=$(patsubst %.cc,%_64.o,$(CXX_SOURCES)) $(x86_64_OBJS) : %_64.o : %.cc $(THIS_MAKE) $(CXX) -o $@ -c $< -m64 -O0 -g $(CXXFLAGS) $(PROJECT)_x86_64.nexe : $(x86_64_OBJS) $(CXX) -o $@ $^ -m64 -O0 -g $(CXXFLAGS) $(LDFLAGS) # Define a phony rule so it always runs, to build nexe and start up server. .PHONY: RUN RUN: all python ../httpd.py
清單文件
清單中指出了不同架構所使用的模塊。
{ "program": { "x86-64": {"url": "hello_tutorial_x86_64.nexe"}, "x86-32": {"url": "hello_tutorial_x86_32.nexe"} } }
所有這些文件齊全之後,只需要make即可完成自動編譯。
如何運行
在編譯之後,雙擊html頁面打開其實並不會載入模塊運行,這是由於瀏覽器的訪問域規則不允許直接在用戶的本地讀取文件。因此,我們需要讓本機成為一個服務器,以遠程服務器的身份來讀取模塊最終傳送給瀏覽器。
這很容易,SDK當中已經包含了提供http服務的python腳本:
cd pepper_18/examples/hello_tutorial make
這樣,http://localhost:5103就成為了我們的服務器地址。注意,http服務的根目錄的位置位於examples目錄。
現在,我們在瀏覽器的chrome://flags和chrome://plugins頁面中打開NaCl的幾個相關選項,就可以成功運行了。
Via NewHTML
沒有留言:
張貼留言