教程之2、第2部分:SDK構建系統和Chrome應用程序

原文連接:https://developer.chrome.com/native-client/devguide/tutorial/tutorial-part2javascript

C ++教程:入門(第2部分)

概觀

本教程介紹如何將完成的PNaCl Web應用程序從第1部分轉換 爲使用Native Client SDK構建系統和經常使用JavaScript文件。它還演示了一些使您的Web應用程序符合內容安全策略(CSP)的技術,這是Chrome應用程序所必需的。html

使用Native Client SDK構建系統能夠輕鬆地使用全部SDK工具鏈進行構建,並在Debug和Release配置之間切換。它還簡化了項目的makefile,咱們將在下一節中看到。最後,它添加了一些用於運行調試 應用程序的有用命令。java

能夠pepper_$(VERSION)/getting_started/part2在Native Client SDK下載的目錄中找到此示例的完成代碼 。chrome

使用Native Client SDK構建系統

本節介紹如何使用SDK構建系統。爲此,咱們將在makefile中進行更改。由於part1和part2中的makefile是如此不一樣,因此從頭開始更容易。這是新makefile的內容。如下部分將更詳細地描述它。api

簡化Makefile

part1的makefile只支持一個工具鏈(PNaCl)和一個配置(Release)。它也只支持一個源文件。它相對簡單,但若是咱們想要添加對多個工具鏈,配置,源文件或構建步驟的支持,它將變得愈來愈複雜。SDK構建系統使用一組變量和宏來實現這一點,而不會顯着增長makefile的複雜性。瀏覽器

這是新的makefile,支持三個工具鏈(PNaCl,Newlib NaCl,Glibc NaCl)和兩個配置(Debug,Release)。安全

VALID_TOOLCHAINS := pnacl clang-newlib glibc

NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../..)
include $(NACL_SDK_ROOT)/tools/common.mk

TARGET = part2
LIBS = ppapi_cpp ppapi

CFLAGS = -Wall
SOURCES = hello_tutorial.cc

# Build rules generated by macros from common.mk:

$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))

# The PNaCl workflow uses both an unstripped and finalized/stripped binary.
# On NaCl, only produce a stripped binary for Release configs (not Debug).
ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG))))
$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))
$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))
else
$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))
endif

$(eval $(call NMF_RULE,$(TARGET),))

選擇有效的工具鏈,包括common.mk

makefile首先指定對此項目有效的工具鏈。Native Client SDK構建系統支持其示例和庫的多工具鏈項目,但一般您在開始項目時選擇一個工具鏈,而不會更改它。有關詳細信息,請參閱Native Client概述的 工具鏈部分服務器

在這個例子中,咱們支持pnaclclang-newlib以及glibc 工具鏈。網絡

VALID_TOOLCHAINS := pnacl clang-newlib glibc

接下來,爲方便起見,咱們指定要查找的位置NACL_SDK_ROOT。因爲此示例位於pepper_$(VERSION)/getting_started/part2,所以SDK的根目錄是兩個目錄。app

NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../..)

在您本身的項目中,您能夠在此處使用已安裝SDK的絕對路徑。您還能夠經過設置NACL_SDK_ROOT 環境變量來覆蓋此默認值。有關更多詳細信息,請參閱本教程第1部分的步驟5

接下來,咱們包含該文件tools/common.mk。此文件提供Native Client SDK構建系統的功能,包括用於編譯和連接項目的新構建規則,咱們將在下面使用。

include $(NACL_SDK_ROOT)/tools/common.mk

配置項目

包含以後tools/common.mk,咱們經過指定項目名稱,它使用的源和庫來配置項目:

TARGET = part2
LIBS = ppapi_cpp ppapi

CFLAGS = -Wall
SOURCES = hello_tutorial.cc

這些變量名稱不是必需的,也不是SDK構建系統使用的; 它們僅用於下述規則。按照慣例,全部SDK makefile都使用如下變量:

目標

要構建的項目的名稱。此變量肯定將生成的庫或可執行文件的名稱。在上面的例子中,咱們調用目標part2,它將生成一個名爲part2.pexePNaCl 的可執行文件 。對於NaCl工具鏈,可執行文件的文件名將爲其體系結構提供後綴。例如,調用ARM可執行文件part2_arm.nexe

LIBS

此可執行文件須要連接的庫列表。庫搜索路徑已設置爲僅查看當前工具鏈和體系結構的目錄。在這個例子中,咱們連接ppapi_cpp 和ppapippapi_cpp須要使用Pepper C ++接口ppapi須要與瀏覽器通訊。

CFLAGS

要傳遞給編譯器的額外標誌列表。在這個例子中,咱們傳遞 -Wall,它打開全部警告。

LDFLAGS

要傳遞給連接器的其餘標誌的列表。此示例不須要任何特殊的連接器標誌,所以省略此變量。

來源

要編譯的C或C ++源列表,用空格分隔。若是您有很長的源列表,若是您將每一個文件放在它本身的行上,而且\用做行繼續符,則可能更容易閱讀。這是一個例子:

SOURCES = foo.cc \
          bar.cc \
          baz.cc \
          quux.cc

構建宏

對於許多項目,不須要更改如下構建宏; 他們將使用咱們上面定義的變量。

$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))

ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG))))
$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))
$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))
else
$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))
endif

$(eval $(call NMF_RULE,$(TARGET),))

第一行定義了SOURCES使用如下標誌編譯每一個源的規則CFLAGS

$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))

接下來的六行定義了將目標文件連接到一個或多個可執行文件的規則。當TOOLCHAINpnacl,僅存在一個生成的可執行:在上面的例子中,part2.pexe。當使用NaCl工具鏈時,將生成三個可執行文件,每一個體繫結構對應一個體繫結構:在上面的示例中part2_arm.nexepart2_x86_32.nexe和 part2_x86_64.nexe

若是CONFIGRelease,每一個可執行文件也被脫除調試信息,減小文件大小。不然,當TOOLCHAIN 是pnacl,工做流程涉及建立一個未經剝離的二進制文件以進行調試,而後完成它並剝離它以進行發佈。

ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG))))
$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))
$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))
else
$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))
endif

最後,NMF規則生成一個NaCl清單文件(.nmf),它引用上一步中生成的每一個可執行文件:

$(eval $(call NMF_RULE,$(TARGET),))

使index.html適用於Chrome應用

本節介紹了使part1與CSP兼容的HTML和JavaScript所需的更改。若是您要構建Chrome應用程序,則須要這樣作,但若是您想在開放網絡上使用PNaCl,則不須要這樣作。

CSP規則

Chrome Apps CSP限制您執行如下操做:

  • 您沒法在Chrome應用頁面中使用內聯腳本。限制禁止<script>塊和事件處理程序(<button onclick="...">)。
  • 您沒法在任何應用文件中引用任何外部資源(視頻和音頻資源除外)。您沒法在iframe中嵌入外部資源。
  • 你不能使用像eval()和那樣的字符串到JavaScript的方法new Function()

使index.html符合CSP標準

爲了使咱們的應用程序符合CSP,咱們必須刪除內聯腳本。如上所述,咱們不能使用內聯<script>塊或事件處理程序。這很容易 - 咱們只會從腳本標記中引用一些新文件,並刪除全部內聯腳本:

<head>
  ...
  <script type="text/javascript" src="common.js"></script>
  <script type="text/javascript" src="example.js"></script>
</head>

common.js具備全部SDK示例使用的共享代碼,稍後將在本文檔中進行介紹。example.js是一個腳本,具備特定於此示例的代碼。

咱們還須要刪除body標籤上的內聯事件處理程序:

<body onload="pageDidLoad()">
...

這個邏輯如今由common.js。處理。

使index.html支持不一樣的工具鏈和配置

最後,index.html對於CSP合規性而言,有一些更改是沒必要要的,但有助於使SDK示例更通用。

首先,咱們 向body元素添加一些數據屬性,以指定名稱,支持的工具鏈,支持的配置和.nmf文件路徑:

<body data-name="part2"
    data-tools="clang-newlib glibc pnacl"
    data-configs="Debug Release"
    data-path="{tc}/{config}">
...

common.js將讀取這些數據屬性,以容許您經過更改URL的查詢字符串來加載具備不一樣工具鏈的相同示例。例如,您能夠經過導航到加載此示例的glibc Debug版本 index.html?tc=glibc&config=Debug。例如../,路徑URI 不適用於data-path參數或其對應的查詢字符串。

接下來,咱們刪除embedHTML中描述的元素。這將common.js根據當前的工具鏈/配置組合自動添加:

<!--
Just as in part1, the <embed> element will be wrapped inside the <div>
element with the id "listener". In part1, the embed was specified in HTML,
here the common.js module creates a new <embed> element and adds it to the
<div> for us.
-->
<div id="listener"></div>

與common.js共享公共代碼

common.js包含JavaScript代碼,每一個示例用於建立NaCl模塊,處理來自該模塊的消息以及其餘常見任務,如顯示模塊加載狀態和記錄消息。解釋全部 common.js內容超出了本文檔的範圍,但請查看該文件中的文檔以獲取更多信息。

加載頁面並建立模塊

既然咱們已經添加<script>標籤common.js,並example.js給 head元素,他們將被載入和文檔的其他部分被解析以前執行。所以,在嘗試建立embed元素並將其添加到頁面以前,咱們必須等待頁面完成加載。

咱們能夠經過呼叫addEventListener和收聽 DOMContentLoaded事件來作到這一點:

// Listen for the DOM content to be loaded. This event is fired when parsing of
// the page's document has finished.
document.addEventListener('DOMContentLoaded', function() {
  ...
});

在此函數中,咱們解析URL查詢字符串,並將其與數據屬性進行比較:

// From https://developer.mozilla.org/en-US/docs/DOM/window.location
var searchVars = {};
if (window.location.search.length > 1) {
  var pairs = window.location.search.substr(1).split('&');
  for (var key_ix = 0; key_ix < pairs.length; key_ix++) {
    var keyValue = pairs[key_ix].split('=');
    searchVars[unescape(keyValue[0])] =
        keyValue.length > 1 ? unescape(keyValue[1]) : '';
  }
}

...

var toolchains = body.dataset.tools.split(' ');
var configs = body.dataset.configs.split(' ');

...

var tc = toolchains.indexOf(searchVars.tc) !== -1 ?
    searchVars.tc : toolchains[0];

// If the config value is included in the search vars, use that.
// Otherwise default to Release if it is valid, or the first value if
// Release is not valid.
if (configs.indexOf(searchVars.config) !== -1)
  var config = searchVars.config;
else if (configs.indexOf('Release') !== -1)
  var config = 'Release';
else
  var config = configs[0];

而後domContentLoaded調用,執行一些檢查以查看瀏覽器是否支持Native Client,而後建立NaCl模塊。

function domContentLoaded(name, tool, path, width, height, attrs) {
  updateStatus('Page loaded.');
  if (!browserSupportsNaCl(tool)) {
    updateStatus(
        'Browser does not support NaCl (' + tool + '), or NaCl is disabled');
  } else if (common.naclModule == null) {
    updateStatus('Creating embed: ' + tool);

    // We use a non-zero sized embed to give Chrome space to place the bad
    // plug-in graphic, if there is a problem.
    width = typeof width !== 'undefined' ? width : 200;
    height = typeof height !== 'undefined' ? height : 200;
    attachDefaultListeners();
    createNaClModule(name, tool, path, width, height, attrs);
  } 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('Waiting.');
  }
}

attachDefaultListeners在建立模塊以前添加,以確保沒有消息丟失。注意,window.attachListeners也稱爲; 這是common.js容許每一個示例以不一樣方式配置自身的方式。若是一個例子定義了該attachListeners函數,它將被調用common.js

function attachDefaultListeners() {
  var listenerDiv = document.getElementById('listener');
  listenerDiv.addEventListener('load', moduleDidLoad, true);
  listenerDiv.addEventListener('message', handleMessage, true);
  listenerDiv.addEventListener('crash', handleCrash, true);
  if (typeof window.attachListeners !== 'undefined') {
    window.attachListeners();
  }
}

最後,createNaClModule實際上建立了embed,並將其做爲元素的子項追加到id listener

function createNaClModule(name, tool, path, width, height, attrs) {
  var moduleEl = document.createElement('embed');
  moduleEl.setAttribute('name', 'nacl_module');
  moduleEl.setAttribute('id', 'nacl_module');
  moduleEl.setAttribute('width', width);
  moduleEl.setAttribute('height', height);
  moduleEl.setAttribute('path', path);
  moduleEl.setAttribute('src', path + '/' + name + '.nmf');

  ...

  var mimetype = mimeTypeForTool(tool);
  moduleEl.setAttribute('type', mimetype);

  var listenerDiv = document.getElementById('listener');
  listenerDiv.appendChild(moduleEl);
  ...
}

當模塊完成加載時,它將調度一個load事件,並moduleDidLoad調用上面()註冊的事件監聽器函數。請注意,common.js容許每一個示例定義一個 window.moduleDidLoad函數,該函數也將在此處調用。

function moduleDidLoad() {
  common.naclModule = document.getElementById('nacl_module');
  updateStatus('RUNNING');

  if (typeof window.moduleDidLoad !== 'undefined') {
    window.moduleDidLoad();
  }
}

example.js的特定於示例的行爲

如上一節所述,common.js將在模塊加載過程當中調用某些函數。這個例子只須要響應兩個: moduleDidLoadhandleMessage

// This function is called by common.js when the NaCl module is
// loaded.
function moduleDidLoad() {
  // Once we load, hide the plugin. In this example, we don't display anything
  // in the plugin, so it is fine to hide it.
  common.hideModule();

  // After the NaCl module has loaded, common.naclModule is a reference to the
  // NaCl module's <embed> element.
  //
  // postMessage sends a message to it.
  common.naclModule.postMessage('hello');
}

// This function is called by common.js when a message is received from the
// NaCl module.
function handleMessage(message) {
  var logEl = document.getElementById('log');
  logEl.textContent += message.data;
}

編譯Native Client模塊並再次運行該應用程序

  1. 經過make再次運行該命令來編譯Native Client模塊。

  2. 經過運行啓動SDK Web服務器make server

  3. 經過http://localhost:5103/part2在Chrome中從新加載來從新運行該應用程序。

    Chrome加載Native Client模塊後,您應該會看到從模塊發送的消息。

CC-By 3.0許可下提供的內容

相關文章
相關標籤/搜索