Modificação de quickjs para importar funções de ferrugem - uma nova forma de pensar sobre a extensão do Kraken
Repositório de códigos
A génese
Kraken é um motor de renderização web de alto desempenho baseado em Flutter, que utiliza quickjs, como motor de scripting.
Queria escrever algumas extensões ao Kraken usando rust.
Kraken apoia extensões de escrita usando dart .
Usando flutter_rust_bridge rust e dart.
Combinando estes dois pontos, não é difícil escrever extensões Kraken usando rust.
 No entanto, a sobrecarga de desempenho desta solução é elevada, pois há uma penalização de desempenho para dart chamando rust, e outra para quickjs chamando dart.
Por outro lado, enquanto a comunidade rust tem rquickjs tais chamadas para a biblioteca quickjs em rust.
 No entanto, chamam quickjs em vez de incorporarem quickjse não podem estar habituados a fazer magia quickjs.
Nesta base de códigos, implementei uma nova solução: modificar directamente o código fonte quickjs para suportar a extensão rust.
Esta é uma solução genérica que pode ser utilizada não só para modificar o Kraken, mas também para todas as estruturas e bibliotecas que incorporam quickjs.
Demonstração
O código test.js é o seguinte :
const try_run = (func, ...args) => {
  try {
    func(...args)
  } catch (err) {
    console.log('❌', err.message)
    console.log(err.stack)
  }
};
import * as rust from 'rust'
console.log("export from rust :", Object.keys(rust))
import {
  fib,
  sleep
} from 'rust'
(async () => {
  console.log('begin sleep 2s')
  await sleep(2000);
  console.log('sleep done')
  console.log('fib(3) =', fib(3));
  console.log("try catch example :")
  try_run(fib);
  try_run(fib, '*');
})()
Corre ./quickjs/qjs test.js, saída :
export from rust : fib,sleep
begin sleep 2s
sleep done
fib(3) = 6
try catch example :
❌ miss : args need 1 pass 0
    at fib (native)
    at try_run (test.js:8)
    at <anonymous> (test.js:27)
❌ not number : args position 0
    at fib (native)
    at try_run (test.js:6)
    at <anonymous> (test.js:28)
Aplicação de fibra na ferrugem
A função de fibra importada em js é de rust/src/export/fib.rs e o código é o seguinte :
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
  if n <= 1 {
    return if n == 1 { 1 } else { 0 };
  }
  n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => js::val(ctx, fib(n)),
  }
}
Actualmente, a macro de procedimento #[js] apenas acrescenta uma constante fib_args_lenque identifica o número de argumentos para a função.
No futuro, a macro de procedimento ./rust_macro pode ser escrita para permitir a exportação totalmente automática de funções.
Implementação da função sono na ferrugem
A função de sono importada em js é de rust/src/export/sleep.rs e o código é o seguinte :
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
  if n <= 1 {
    return if n == 1 { 1 } else { 0 };
  }
  n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => js::val(ctx, fib(n)),
  }
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
  Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => run(ctx, async move {
      sleep(n as u64).await;
      Ok(())
    }),
  }
}
Como se pode ver acima, todas as funções exportadas estão definidas no directório ./rust/src/export. Este directório mod.rs é gerado automaticamente quando ./rust/build.xsh é executado, exportando todos os ficheiros .rs sob ele.
Leitura e validação de parâmetros de entrada js
Os parâmetros são lidos e validados em src/js/arg.rs com o seguinte código :
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
  if n <= 1 {
    return if n == 1 { 1 } else { 0 };
  }
  n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => js::val(ctx, fib(n)),
  }
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
  Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => run(ctx, async move {
      sleep(n as u64).await;
      Ok(())
    }),
  }
}
use crate::js::throw;
use quickjs_ffi::{JSContext, JSValue, JS_IsNumber, JS_ToInt64};
use std::{mem::MaybeUninit, os::raw::c_int};
pub(crate) fn arg_miss(ctx: *mut JSContext, argc: c_int, need: c_int) -> Result<(), JSValue> {
  if argc < need {
    throw(ctx, format!("miss : args need {need} pass {argc}"))?
  }
  Ok(())
}
pub(crate) fn arg_i64(ctx: *mut JSContext, argv: *mut JSValue, pos: isize) -> Result<i64, JSValue> {
  unsafe {
    let val = *argv.offset(pos);
    if JS_IsNumber(val) == 0 {
      throw(ctx, format!("not number : args position {pos}"))?
    }
    let mut n = MaybeUninit::uninit();
    JS_ToInt64(ctx, n.as_mut_ptr() as _, val);
    Ok(n.assume_init())
  }
}
Actualmente apenas o número de argumentos é verificado, e o tipo i64 é lido.
Pode adicionar estas funções conforme necessário, ver as funções em qjs_sys começando por JS_To.
Conversão do tipo de dados de ferrugem para js
A conversão do tipo é feita em src/js/val.rs com o seguinte código :
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
  if n <= 1 {
    return if n == 1 { 1 } else { 0 };
  }
  n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => js::val(ctx, fib(n)),
  }
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
  Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => run(ctx, async move {
      sleep(n as u64).await;
      Ok(())
    }),
  }
}
use crate::js::throw;
use quickjs_ffi::{JSContext, JSValue, JS_IsNumber, JS_ToInt64};
use std::{mem::MaybeUninit, os::raw::c_int};
pub(crate) fn arg_miss(ctx: *mut JSContext, argc: c_int, need: c_int) -> Result<(), JSValue> {
  if argc < need {
    throw(ctx, format!("miss : args need {need} pass {argc}"))?
  }
  Ok(())
}
pub(crate) fn arg_i64(ctx: *mut JSContext, argv: *mut JSValue, pos: isize) -> Result<i64, JSValue> {
  unsafe {
    let val = *argv.offset(pos);
    if JS_IsNumber(val) == 0 {
      throw(ctx, format!("not number : args position {pos}"))?
    }
    let mut n = MaybeUninit::uninit();
    JS_ToInt64(ctx, n.as_mut_ptr() as _, val);
    Ok(n.assume_init())
  }
}
use quickjs_ffi::{JSContext, JSValue, JS_NewInt64, JS_NewString, JS_NULL, JS_UNDEFINED};
use std::ffi::CString;
pub enum Val {
  None,
  Undefined,
  I64(i64),
  CString(CString),
}
impl From<()> for Val {
  fn from(_: ()) -> Self {
    Val::Undefined
  }
}
impl From<i64> for Val {
  fn from(t: i64) -> Self {
    Val::I64(t)
  }
}
impl From<CString> for Val {
  fn from(t: CString) -> Self {
    Val::CString(t)
  }
}
pub(crate) fn val(ctx: *mut JSContext, t: impl Into<Val>) -> JSValue {
  match t.into() {
    Val::None => JS_NULL,
    Val::Undefined => JS_UNDEFINED,
    Val::I64(n) => unsafe { JS_NewInt64(ctx, n) },
    Val::CString(cstr) => unsafe { JS_NewString(ctx, cstr.as_ptr()) },
  }
}
Apenas quatro tipos estão definidos para conversão de None, (), i64e CString para js. Pode adicionar quantos quiser.
Mais tipos de dados podem ser declarados nas funções em qjs_sys começando por JS_New.
Ambiente de desenvolvimento
Estou a desenvolver num portátil Apple, a ferrugem está a usar 1.62.0-nocturno.
Primeiro instalar direnv, ir ao directório e direnv allow por um tempo
Instalar python3, então pip3 install -r ./requirements.txt
correr ./build.xsh para compilar e executar a demonstração
Por defeito, o repositório oficial quickjs será clonado, se quiser modificar o quickjs no repositório Kraken, primeiro
git clone --recursive git@github.com:openkraken/kraken.git --depth=1
então faça o seguinte
rm -rf quickjs
ln -s ../kraken/bridge/third_party/quickjs .
Por fim, volte a executar o ./build.xsh
Estrutura do directório
- ./quickjs_rust
 Modificar o ficheiro c do código quickjs
- ./quickjs_ffi
 Exportar as funções do ficheiro de cabeçalho- quickjspara- rust
- ./rust
 Utilize- rustpara implementar as funções em- quickjs- ./rust/src/qjs.rs
 Implementação de chamadas assíncronas. Uma vez que- quickjsé de rosca única, as chamadas de função envolvendo- quckjsestão escritas no fio principal.
 
- ./rust_macro- rustImplementação do macro procedimento- #[js]- No futuro, ver wasmedge-quickjs para uma exportação automática das funções de ferrugem para as funções js. wasmedge-quickjs → JsFunctionTrampoline 
 Guias de construção build.xsh
Sem mais demoras, vamos directamente para o código fonte do guião de construção build.xsh
#!/usr/bin/env xonsh
from pathlib import Path
from os.path import dirname,abspath,exists,join
PWD = dirname(abspath(__file__))
cd @(PWD)
p".xonshrc".exists() && source .xonshrc
quickjs = 'quickjs'
if not exists(quickjs):
  git clone git@github.com:bellard/@(quickjs).git --depth=1
./quickjs_rust/patch.py
./rust/build.xsh
./quickjs_rust/gen.py
def ln_s(li):
  for arg in li.split(' '):
    fp = join(quickjs,arg)
    if not exists(fp):
      ln -s @(PWD)/@(arg) @(fp)
ln_s('quickjs_rust rust quickjs_ffi rust_macro')
cd @(quickjs)
make qjs
cd @(PWD)
./quickjs/qjs --unhandled-rejection -m test.js 2>&1 | tee test.js.out
Explicação do princípio
 quickjs_rust/patch.py
A execução ./quickjs_rust/patch.py fará algumas pequenas alterações ao código-fonte quickjs.
Uma das funções JS_AddRust é utilizada para injectar no módulo de ferrugem.
rust_run é injectado em JS_ExecutePendingJob para chamar funções assíncronas.
Uma imagem de ecrã de todas as alterações é mostrada abaixo :

 quickjs_rust.h
A partir das alterações acima, pode ver que introduzimos um novo ficheiro de cabeçalho quickjs_rust.h com o seguinte código
#ifndef QUICKJS_RUST_H
#define QUICKJS_RUST_H
#include "../quickjs/quickjs.h"
#include "../rust/rust.h"
#define countof(x) (sizeof(x) / sizeof((x)[0]))
#define JS_RUSTFUNC_DEF(name) JS_CFUNC_DEF(#name, name##_args_len, js_##name)
#include "./js_rust_funcs.h"
static const unsigned int js_rust_funcs_count = countof(js_rust_funcs);
static int
js_rust_init(JSContext* ctx, JSModuleDef* m)
{
  return JS_SetModuleExportList(ctx, m, js_rust_funcs,
      js_rust_funcs_count);
}
#define JS_INIT_MODULE js_init_module_rust
JSModuleDef* JS_INIT_MODULE(JSContext* ctx, const char* module_name)
{
  JSModuleDef* m;
  m = JS_NewCModule(ctx, module_name, js_rust_init);
  if (!m)
    return NULL;
  js_rust_init(ctx, m);
  return m;
}
void JS_AddRust(JSContext* ctx, JSRuntime* rt)
{
  JSModuleDef* m = JS_INIT_MODULE(ctx, "rust");
  for (unsigned int i = 0; i < js_rust_funcs_count; i++) {
    JS_AddModuleExport(ctx, m, js_rust_funcs[i].name);
  }
  rust_init(ctx, rt);
}
#endif
 rust/rust.h
Pode ver que quickjs_rust/quickjs_rust.h introduz quickjs_rust/js_rust_funcs.h, que é gerado automaticamente a partir do ficheiro de cabeçalho da função de exportação de ferrugem rust/rust.h e não deve ser modificado à mão.
E rust/rust.h é gerado ligando para cbindgen de ./rust/build.xsh.
 rust/build.xsh
#!/usr/bin/env xonsh
from os.path import dirname,abspath
import platform
PWD = dirname(abspath(__file__))
cd @(PWD)
p"../.xonshrc".exists() && source ../.xonshrc
./src/export/mod.gen.py
system = platform.system().lower()
if system == 'darwin':
  system = f'apple-{system}'
TARGET=f'{platform.machine()}-{system}'
def cbindgen():
  cbindgen -q --config cbindgen.toml --crate rust --output rust.h
try:
  cbindgen()
except:
  cargo clean
  cbindgen()
cargo build \
--release \
-Z build-std=std,panic_abort \
-Z build-std-features=panic_immediate_abort \
--target @(TARGET)
mv ./target/@(TARGET)/release/librust.a ./target/release
Notas de Desenvolvimento
 quickjs_ffi
Código de quijine/main/quijine_core/src/ffi.rs
com algumas pequenas modificações, substituindo
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
  if n <= 1 {
    return if n == 1 { 1 } else { 0 };
  }
  n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => js::val(ctx, fib(n)),
  }
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
  Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => run(ctx, async move {
      sleep(n as u64).await;
      Ok(())
    }),
  }
}
use crate::js::throw;
use quickjs_ffi::{JSContext, JSValue, JS_IsNumber, JS_ToInt64};
use std::{mem::MaybeUninit, os::raw::c_int};
pub(crate) fn arg_miss(ctx: *mut JSContext, argc: c_int, need: c_int) -> Result<(), JSValue> {
  if argc < need {
    throw(ctx, format!("miss : args need {need} pass {argc}"))?
  }
  Ok(())
}
pub(crate) fn arg_i64(ctx: *mut JSContext, argv: *mut JSValue, pos: isize) -> Result<i64, JSValue> {
  unsafe {
    let val = *argv.offset(pos);
    if JS_IsNumber(val) == 0 {
      throw(ctx, format!("not number : args position {pos}"))?
    }
    let mut n = MaybeUninit::uninit();
    JS_ToInt64(ctx, n.as_mut_ptr() as _, val);
    Ok(n.assume_init())
  }
}
use quickjs_ffi::{JSContext, JSValue, JS_NewInt64, JS_NewString, JS_NULL, JS_UNDEFINED};
use std::ffi::CString;
pub enum Val {
  None,
  Undefined,
  I64(i64),
  CString(CString),
}
impl From<()> for Val {
  fn from(_: ()) -> Self {
    Val::Undefined
  }
}
impl From<i64> for Val {
  fn from(t: i64) -> Self {
    Val::I64(t)
  }
}
impl From<CString> for Val {
  fn from(t: CString) -> Self {
    Val::CString(t)
  }
}
pub(crate) fn val(ctx: *mut JSContext, t: impl Into<Val>) -> JSValue {
  match t.into() {
    Val::None => JS_NULL,
    Val::Undefined => JS_UNDEFINED,
    Val::I64(n) => unsafe { JS_NewInt64(ctx, n) },
    Val::CString(cstr) => unsafe { JS_NewString(ctx, cstr.as_ptr()) },
  }
}
pub use libquickjs_sys::*;
para
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
  if n <= 1 {
    return if n == 1 { 1 } else { 0 };
  }
  n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => js::val(ctx, fib(n)),
  }
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
  Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
  ctx: *mut JSContext,
  _this: JSValue,
  argc: c_int,
  argv: *mut JSValue,
) -> JSValue {
  if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
    return err;
  }
  match arg::arg_i64(ctx, argv, 0) {
    Err(err) => err,
    Ok(n) => run(ctx, async move {
      sleep(n as u64).await;
      Ok(())
    }),
  }
}
use crate::js::throw;
use quickjs_ffi::{JSContext, JSValue, JS_IsNumber, JS_ToInt64};
use std::{mem::MaybeUninit, os::raw::c_int};
pub(crate) fn arg_miss(ctx: *mut JSContext, argc: c_int, need: c_int) -> Result<(), JSValue> {
  if argc < need {
    throw(ctx, format!("miss : args need {need} pass {argc}"))?
  }
  Ok(())
}
pub(crate) fn arg_i64(ctx: *mut JSContext, argv: *mut JSValue, pos: isize) -> Result<i64, JSValue> {
  unsafe {
    let val = *argv.offset(pos);
    if JS_IsNumber(val) == 0 {
      throw(ctx, format!("not number : args position {pos}"))?
    }
    let mut n = MaybeUninit::uninit();
    JS_ToInt64(ctx, n.as_mut_ptr() as _, val);
    Ok(n.assume_init())
  }
}
use quickjs_ffi::{JSContext, JSValue, JS_NewInt64, JS_NewString, JS_NULL, JS_UNDEFINED};
use std::ffi::CString;
pub enum Val {
  None,
  Undefined,
  I64(i64),
  CString(CString),
}
impl From<()> for Val {
  fn from(_: ()) -> Self {
    Val::Undefined
  }
}
impl From<i64> for Val {
  fn from(t: i64) -> Self {
    Val::I64(t)
  }
}
impl From<CString> for Val {
  fn from(t: CString) -> Self {
    Val::CString(t)
  }
}
pub(crate) fn val(ctx: *mut JSContext, t: impl Into<Val>) -> JSValue {
  match t.into() {
    Val::None => JS_NULL,
    Val::Undefined => JS_UNDEFINED,
    Val::I64(n) => unsafe { JS_NewInt64(ctx, n) },
    Val::CString(cstr) => unsafe { JS_NewString(ctx, cstr.as_ptr()) },
  }
}
pub use libquickjs_sys::*;
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/c.rs"));
 Undefined symbols for architecture x86_64: "_JS_ToInt32"
Modificar '. /rust/Cargo.toml' como se segue, mantendo apenas o staticlib
[lib]
#crate-type = ["lib", "cdylib", "staticlib"]
crate-type = ["staticlib"]
Referências
- Do motor JSpara o motorJSruntime (em cima) (em baixo)
- Desenvolvimento de um módulo nativo para QuickJSem C
- Usar o Rust para implementar o JS API
- Exemplos QuickJS
- rust-bindgen
- Como criar código assíncrono para QuickJS
- rquickjs → JS_NewPromiseCapability
- wasmedge-quickjs → new_promise
- wasmedge-quickjs → Método JsMethod
- wasmedge-quickjs → call
- A armadilha imperceptível - cadeados em Ferrugem
Sobre
Este projecto faz parte do projecto de código rmw.link ( rmw.link ).