NaCl: El Silverlight/Flash de Google?

En mi continuo transitar por las novedades tecnológicas (no sé a que hora visito tanto sitio…) Me encontré con que Google de una manera bastante discreta estaba lanzando una tecnología abreviada como la sal: NaCl. Acrónimo de Native Client.

NaCl = Sal

Native Client es una idea supernovedosa

Con la cual de acuerdo a lo que encontramos en el sitio del proyecto, tenemos un fabuloso SDK que permitirá al browser ejecutar código nativo para construir aplicaciones web de alta respuesta e interactividad… (no les suena esto familiar?)

Lo cual, me parece bastante curioso, dado todo lo que Google ha dicho acerca de su firme apoyo a HTML 5, que pretende solucionar el mismo tipo de problema.

Dijo el vice presidente de ingeniería Vic Gundotra en el Google I/O de 2009: “HTML 5, will be the future of the web”.

Y si es así? Para qué desgastarse tratando de implementar toda una tecnología para correr código nativo desde el browser? Sobretodo cuando se ha criticado al mismo tipo de tecnologías ya implementadas como Silverlight y Flash?

Será que en el fondo no creen mucho en HTML5? O será que en el fondo creen mucho en tecnologías Silverlifght/Flash?

NaCl tiene un SDK que funciona en Windows, Linux y Mac… Genial! Y lo mejor de todo, es que las aplicaciones las programaríamos en una suerte de sancocho (léase mescolanza o MIX para los más modernos) de CSS, HTML, Javascript y sí, el amigable C/C++! Todo para lograr que la aplicación no se vea como un frame embebido dentro del HTML, sino como puro HTML.

Este C/C++ nos permitirá crear de una forma bastante sencilla gráficos en 2D/3D, ejecutar audio y responder a eventos del mouse y teclado!

Pero…

Cuál es el AS bajo la manga de Google que aventajará a Silverlight y Flash?

Pues que no se requerirá plugin! No habrá que instalar nada!!!
Bueno, excepto el propio browser de Google (Chromium). Entonces ha de ser que para ellos no existen más browsers :

“all without requiring users to install a plugin. These web apps run in recent versions of Chromium with the --enable-nacl flag.”

Bueno; pero pongamos esas des-virtudes a un lado y démosle una oportunidad de prueba; observemos cómo sería la programación. Primero, veamos cómo sería la página web que hospedaría la sal; digo, NaCl:

    1:  <!DOCTYPE html>
    2:  <html>
    3:    <!--
    4:    Copyright (c) 2010 The Native Client Authors. All rights reserved.
    5:    Use of this source code is governed by a BSD-style license that can be
    6:    found in the LICENSE file.
    7:    -->
    8:  <head>
    9:    <title>Hello, World!</title>
   10:   
   11:    <script type="text/javascript">
   12:      hello_world = null;  // Global application object.
   13:      status_text = 'NO-STATUS';
   14:   
   15:      function moduleDidLoad() {
   16:        hello_world = document.getElementById('hello_world');
   17:        updateStatus('SUCCESS');
   18:      }
   19:   
   20:      // If the page loads before the Native Client module loads, then set the
   21:      // status message indicating that the module is still loading.  Otherwise,
   22:      // do not change the status message.
   23:      function pageDidLoad() {
   24:        if (hello_world == null) {
   25:          updateStatus('LOADING...');
   26:        } else {
   27:          // It's possible that the Native Client module onload event fired
   28:          // before the page's onload event.  In this case, the status message
   29:          // will reflect 'SUCCESS', but won't be displayed.  This call will
   30:          // display the current message.
   31:          updateStatus();
   32:        }
   33:      }
   34:   
   35:      function fortytwo() {
   36:        try {
   37:          alert(hello_world.fortytwo());
   38:        } catch(e) {
   39:          alert(e.message);
   40:        }
   41:      }
   42:   
   43:      function helloworld() {
   44:        try {
   45:          alert(hello_world.helloworld());
   46:        } catch(e) {
   47:          alert(e.message);
   48:        }
   49:      }
   50:   
   51:      // Set the global status message.  If the element with id 'status_field'
   52:      // exists, then set its HTML to the status message as well.
   53:      // opt_message The message test.  If this is null or undefined, then
   54:      //     attempt to set the element with id 'status_field' to the value of
   55:      //     |status_text|.
   56:      function updateStatus(opt_message) {
   57:        if (opt_message)
   58:          status_text = opt_message;
   59:        var status_field = document.getElementById('status_field');
   60:        if (status_field) {
   61:          status_field.innerHTML = status_text;
   62:        }
   63:      }
   64:    </script>
   65:  </head>
   66:  <body onload="pageDidLoad()">
   67:   
   68:  <h1>Native Client Simple Module</h1>
   69:  <p>
   70:    <button onclick="fortytwo()">Call fortytwo()</button>
   71:    <button onclick="helloworld()">Call helloworld()</button>
   72:   
   73:    <!-- For development, use a #develop location, which loads the develop
   74:    version of the module.
   75:    -->
   76:    <div id="nacl_helloworld_content"></div>
   77:    <script type="text/javascript">
   78:      contentDiv = document.getElementById('nacl_helloworld_content');
   79:      if (window.location.hash == '#develop') {
   80:        // Load the develop version of the module.
   81:        contentDiv.innerHTML = '<embed name="nacl_module" '
   82:                               + 'id="hello_world" '
   83:                               + 'width=0 height=0 '
   84:                               + 'type="pepper-application/hello_world" />';
   85:        moduleDidLoad();
   86:      } else {
   87:        // Load the published .nexe.  This includes the 'nexes' attribute which
   88:        // shows how to load multi-architecture modules.  The relative URLs in
   89:        // the following table should be matched with a similar directory
   90:        // structure on your server.
   91:        var nexes = 'x86-32: hello_world.nacl/x86_32/hello_world.nexe; '
   92:                    + 'x86-64: hello_world.nacl/x86_64/hello_world.nexe; '
   93:                    + 'ARM: hello_world.nacl/arm/hello_world.nexe; ';
   94:        contentDiv.innerHTML = '<embed name="nacl_module" '
   95:                               + 'id="hello_world" '
   96:                               + 'width=0 height=0 '
   97:                               + 'src="hello_world.nexe" '
   98:                               + nexes
   99:                               + 'type="application/x-nacl-srpc" '
  100:                               + 'onload=moduleDidLoad() />';
  101:      }
  102:    </script>
  103:  </p>
  104:   
  105:  <p>If the module is working correctly, a click on the "Call fortytwo" button
  106:    should open a popup dialog containing <b>42</b> as value.</p>
  107:   
  108:  <p> Clicking on the "Call helloworld" button
  109:    should open a popup dialog containing <b>hello, world</b> as value.</p>
  110:   
  111:  <h2>Status</h2>
  112:  <div id="status_field">NO-STATUS</div>
  113:  </body>
  114:  </html>

Bastante código para un Hello World no?

Independientemente de eso, observemos la estructura. Básicamente se trata de una página web convencional, cuyo DOM es manejado por un Javascript que accede a un objeto embebido (finalmente si se embeben objetos) que es precísamente el gestor de NaCl, el cual carga archivos binarios de tipo “.nexe” escritos en C++ (Entonces lo que uno hace es indicar la ruta de dicho .nexe; muy parecido a Flash o Silverlight, donde uno indica la ruta del .xap). Hasta aquí noto algunas desventajas con Silverlight… por ejemplo, no se sabe si la página carga antes o después del NaCl; así que hay que escribir lógica adicional para cuando esto sucede. También tenemos que estar mezclando en todo lado Javascript, para traer los datos desde el NaCl al DOM de la página. Finalmente siempre tienen que diferenciarse las versiones de desarrollo de las de producción. En Silverlight, no hay tal diferenciación. Sin embargo, he de decir, que no son diferencias tan graves y que en el fondo el funcionamiento es muy similar, en cuanto a la idea como tal.

Pero ahora veamos, cómo se programa la parte nativa de la aplicación.

Como ya les había comentado, usamos el poderoso y amigable C/C++ para dicha programación.

Luego de muchos tropiezos, por fin logré sacar el código que me daría un mensaje de texto que diría Hello World, el cual se transfiere a la página a través de Javascript y se muestra en un ‘alert´.

Veamos el código:

    1:  // Copyright 2008 The Native Client Authors. All rights reserved.
    2:  // Use of this source code is governed by a BSD-style license that can
    3:  // be found in the LICENSE file.
    4:   
    5:  #include <stdbool.h>
    6:  #include <stdio.h>
    7:  #include <stdlib.h>
    8:  #include <string.h>
    9:   
   10:  #if defined(__native_client__)
   11:  #include <nacl/nacl_npapi.h>
   12:  #else
   13:  // Building a develop version for debugging.
   14:  #include "third_party/npapi/bindings/npapi.h"
   15:  #include "third_party/npapi/bindings/nphostapi.h"
   16:  #endif
   17:   
   18:  // These are the method names as JavaScript sees them.
   19:  static const char* kHelloWorldMethodId = "helloworld";
   20:   
   21:  // This function creates a string in the browser's memory pool and then returns
   22:  // a variable containing a pointer to that string.  The variable is later
   23:  // returned back to the browser by the Invoke() function that called this.
   24:  static bool HelloWorld(NPVariant *result) {
   25:    if (result) {
   26:      const char *msg = "hello, world.";
   27:      const int msg_length = strlen(msg) + 1;
   28:      // Note: |msg_copy| will be freed later on by the browser, so it needs to
   29:      // be allocated here with NPN_MemAlloc().
   30:      char *msg_copy = reinterpret_cast<char*>(NPN_MemAlloc(msg_length));
   31:      strncpy(msg_copy, msg, msg_length);
   32:      STRINGN_TO_NPVARIANT(msg_copy, msg_length - 1, *result);
   33:    }
   34:    return true;
   35:  }
   36:   
   37:  static NPObject* Allocate(NPP npp, NPClass* npclass) {
   38:    return new NPObject;
   39:  }
   40:   
   41:  static void Deallocate(NPObject* object) {
   42:    delete object;
   43:  }
   44:   
   45:  // Return |true| if |method_name| is a recognized method.
   46:  static bool HasMethod(NPObject* obj, NPIdentifier method_name) {
   47:    char *name = NPN_UTF8FromIdentifier(method_name);
   48:    bool is_method = false;
   49:    if (!strcmp((const char *)name, kHelloWorldMethodId)) {
   50:      is_method = true;
   51:    } else if (!strcmp((const char*)name, kFortyTwoMethodId)) {
   52:      is_method = true;
   53:    }
   54:    NPN_MemFree(name);
   55:    return is_method;
   56:  }
   57:   
   58:  static bool InvokeDefault(NPObject *obj, const NPVariant *args,
   59:                            uint32_t argCount, NPVariant *result) {
   60:    if (result) {
   61:      NULL_TO_NPVARIANT(*result);
   62:    }
   63:    return true;
   64:  }
   65:   
   66:  // Invoke() is called by the browser to invoke a function object whose name
   67:  // is |method_name|.
   68:  static bool Invoke(NPObject* obj,
   69:                     NPIdentifier method_name,
   70:                     const NPVariant *args,
   71:                     uint32_t arg_count,
   72:                     NPVariant *result) {
   73:    NULL_TO_NPVARIANT(*result);
   74:    char *name = NPN_UTF8FromIdentifier(method_name);
   75:    if (name == NULL)
   76:      return false;
   77:    bool rval = false;
   78:   
   79:    // Map the method name to a function call.  |result| is filled in by the
   80:    // called function, then gets returned to the browser when Invoke() returns.
   81:    if (!strcmp((const char *)name, kHelloWorldMethodId)) {
   82:      rval = HelloWorld(result);
   83:    } else if (!strcmp((const char*)name, kFortyTwoMethodId)) {
   84:      rval = FortyTwo(result);
   85:    }
   86:    // Since name was allocated above by NPN_UTF8FromIdentifier,
   87:    // it needs to be freed here.
   88:    NPN_MemFree(name);
   89:    return rval;
   90:  }
   91:   
   92:  // The class structure that gets passed back to the browser.  This structure
   93:  // provides funtion pointers that the browser calls.
   94:  static NPClass kHelloWorldClass = {
   95:    NP_CLASS_STRUCT_VERSION,
   96:    Allocate,
   97:    Deallocate,
   98:    NULL,  // Invalidate is not implemented
   99:    HasMethod,
  100:    Invoke,
  101:    InvokeDefault,
  102:    NULL,  // HasProperty is not implemented
  103:    NULL,  // GetProperty is not implemented
  104:    NULL,  // SetProperty is not implemented
  105:  };
  106:   
  107:  NPClass *GetNPSimpleClass() {
  108:    return &kHelloWorldClass;
  109:  }

Cristalino no?

Y solo para un “Hello World!“


Ahora veamos lo mismo con Silverlight

    1:  using System;
    2:  using System.Windows;
    3:  using System.Windows.Controls;
    4:   
    5:   
    6:  namespace HelloSilverlight
    7:  {
    8:      public partial class MainPage : UserControl
    9:      {
   10:          public MainPage()
   11:          {
   12:              // Required to initialize variables
   13:              InitializeComponent();
   14:          }
   15:   
   16:          private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
   17:          {
   18:              MessageBox.Show("Hello World");
   19:          }
   20:      }
   21:  }

Bueno; el código habla por sí solo!!!

Por si fuera poco, observemos el tamaño del ejecutable que finalmente tiene que descargar el usuario:

NaCl:

image

Silverlight:

image

Hum… 1143 veces menor

Bueno; a favor de Google digamos que esta es la primera muestra en público de la plataforma y que todavía no está en producción. Pero que o recuerde, Silverlight en sus primeras apariciones no tenía esos inconvenientes.. yo creo que deberían refinar un poco más el producto a mostrar, antes de sacar su preview!

Por otro lado, también pienso que así evolucione mucho, (disminuir el tamaño final, simplificar la comunicación con el DOM, etc.) La programación como tal del cliente nativo no tendrá nada que hacer en cuanto a productividad si no se usa un lenguaje administrado.

Hoy en día los recursos de hardware que existen para la mayoría de las aplicaciones (yo diría un 96%) permiten que las ligeras ventajas en tiempo de ejecución logradas con lenguajes no administrados como C/C++ sean imperceptibles… así que vamos… cuál es el punto?