Giter Club home page Giter Club logo

delphiduktape's Introduction

JavaScripting with Duktape for Delphi

Looking to add JavaScript capabilities to you app, but without the bulk and overhead of JIT engines like V8, SpiderMonkey and Chakra? Then take a look at Duktape.

Duktape is a lightweight, embeddable and cross-platform JavaScript engine. It fits into a DLL just over half a megabyte in size but supports the complete ECMAScript 5.1 specification (as well as parts of ECMAScript 2015 and 2016).

Duktape for Delphi

Duktape uses a C API, so that it can be accessed from other languages. We created header translations for this API and some higher level wrappers to make these easier to use in Delphi. In addition, we supply prebuilt binaries of the Duktape library for Windows (32-bit and 64-bit), macOS, iOS, Android and Linux.

This repository contains the source code, Duktape libraries and a couple of sample applications. In addition it contains build scripts for building the Duktape libraries for all platforms.

The Delphi header translations and binaries apply to the latest Duktape version (2.2.0) at the time of writing. You can use the build scripts to build newer binaries if needed.

Deploying the Duktape Libraries

For iOS and Android, the Duktape library is statically linked into the executable and there are no additional files that need to be deployed. For the other platforms, you need to deploy a dynamic library:

  • Windows: Place duktape32.dll or duktape64.dll (depending on platform) in your application directory.
  • macOS: Add libduktape_osx32.dylib to the deployment manager, using Contents\MacOS\ as the Remote Path.
  • Linux: Add libduktape_linux64.so to the deployment manager, using .\ as the Remote Path.

API Levels

We provide three API levels for working with Duktape. The low-level API consists of the C header translations. The medium-level API adds a very thin layer on top of this that is a bit easier to use. Finally, a limited high-level API makes it easier to register Delphi routines that can be called from JavaScript code.

In presenting these APIs, we won't go into any details of how Duktape works. I suggest you take a look at the Duktape Programmer's Guide to learn about its architecture. Most of what is presented there applies to Delphi as well.

Low-Level API

The low-level API follows the C API exactly. So please refer to the Duktapi API documentation for detailed information about this API. An example usage of this API level can be found in the DuktapeLowLevel demo project. It registers 2 functions that can be called from JavaScript code:

  • A function called print that takes a variable number of arguments and outputs these to the console window.
  • A function called add that adds two numbers together.

It then evaluates some JavaScript code that calls these Delphi functions:

procedure Run;
var
  Context: PDukContext;
begin
  { Create Duktape context }
  Context := duk_create_heap_default;
  try
    { Register native function called "print" that takes a
      variable number of arguments. }
    duk_push_c_function(Context, NativePrint, DUK_VARARGS);
    duk_put_global_string(Context, 'print');

    { Register native function called "add" that takes 2 arguments. }
    duk_push_c_function(Context, NativeAdd, 2);
    duk_put_global_string(Context, 'add');

    { Evaluate some JavaScript code.
      This will call into our NativePrint and Add functions. }
    duk_eval_string(Context, 'print("Hello", "World!");');
    duk_eval_string(Context, 'print("2 + 3 =", add(2, 3));');

    { Pop eval result }
    duk_pop(Context);
  finally
    duk_destroy_heap(Context);
  end;
end;

The top half registers the Delphi functions and the bottom half executes some JavaScript code that calls these functions. The Delphi NativePrint function is called when the JavaScript code calls print. It concatenates all arguments together and writes them to the console window:

function NativePrint(AContext: PDukContext): TDukRet; cdecl;
var
  S: UTF8String;
begin
  { Join all arguments together with spaces between them. }
  duk_push_string(AContext, ' ');
  duk_insert(AContext, 0);
  duk_join(AContext, duk_get_top(AContext) - 1);

  { Get result and output to console }
  S := UTF8String(duk_safe_to_string(AContext, -1));
  WriteLn(S);

  { "print" function does not return a value. }
  Result := 0;
end;

Likewise, the NativeAdd function is called when JavaScript calls add:

function NativeAdd(AContext: PDukContext): TDukRet; cdecl;
var
  Sum: Double;
begin
  { Add two arguments together }
  Sum := duk_to_number(AContext, 0) + duk_to_number(AContext, 1);

  { Push result }
  duk_push_number(AContext, Sum);

  { "add" function returns a single value. }
  Result := 1;
end;

Note that all Delphi functions that can be called from JavaScript must have the following signature:

  • A single parameter containing the Duktape context.
  • A return value of type TDukRet. This is just an integer and should be set to 1 if the function returns a value, 0 if it does not return a value, or a negative value representing an error code.
  • It must use the cdecl calling convention.

Medium-Level API

The medium-level API just adds a very thin layer on top of the low-level API. The purposes of this layer are:

  • Provides a more object-oriented interface: the Duktape context is encapsulated in a TDuktape object (actually a record).
  • Most Duktape APIs are now methods of the TDuktape object. These methods use Delphi naming conventions and types, make them more familiar.
  • It takes care of marshalling data to the lower level. In particular the marshalling of strings to pointers to UTF8 C strings.
  • It uses enumerated types instead of integers to improve type safety.

Other than that, the medium-level API is the same as the low-level API and doesn't provide other benefits or simplifications.

The medium-level API covers all aspects of the low-level API, so there should rarely be a need to use the low-level API directly.

The medium-level version of the example above (see the DuktapeMediumLevel demo project) looks like this:

procedure Run;
var
  Duktape: TDuktape;
begin
  { Create Duktape object, using Delphi's memory manager. }
  Duktape := TDuktape.Create(True);
  try
    { Register native function called "print" that takes a
      variable number of arguments. }
    Duktape.PushDelphiFunction(NativePrint, DT_VARARGS);
    Duktape.PutGlobalString('print');
    
    { Register native function called "add" that takes 2 arguments. }
    Duktape.PushDelphiFunction(NativeAdd, 2);
    Duktape.PutGlobalString('add');
    
    { Evaluate some JavaScript code.
      This will call into our NativePrint and Add functions. }
    Duktape.Eval('print("Hello", "World!");');
    Duktape.Eval('print("2 + 3 =", add(2, 3));');
    
    { Pop eval result }
    Duktape.Pop;
  finally
    Duktape.Free;
  end;
end;

This time, you create a Duktape context by creating a TDuktape object. You pass a single parameter to the constructor indicating whether you want to use Delphi's memory manager (True) or Duktape's own memory manager (False). Using Delphi's memory manager can be useful for detecting memory leaks (through the ReportMemoryLeaksOnShutdown global variable).

The code looks very similar to the low-level version, just a bit more Delphi-like. The Delphi versions of the print and add functions are also similar to the ones above:

function NativePrint(const ADuktape: TDuktape): TdtResult; cdecl;
var
  S: DuktapeString;
begin
  { Join all arguments together with spaces between them. }
  ADuktape.PushString(' ');
  ADuktape.Insert(0);
  ADuktape.Join(ADuktape.GetTop - 1);

  { Get result and output to console }
  S := ADuktape.SafeToString(-1);
  WriteLn(S);

  { "print" function does not return a value. }
  Result := TdtResult.NoResult;
end;

function NativeAdd(const ADuktape: TDuktape): TdtResult; cdecl;
var
  Sum: Double;
begin
  { Add two arguments together }
  Sum := ADuktape.ToNumber(0) + ADuktape.ToNumber(1);

  { Push result }
  ADuktape.PushNumber(Sum);

  { "add" function returns a value. }
  Result := TdtResult.HasResult;
end;

These functions take a TDuktape parameter now and return a value of the enumerated type TdtResult.

High-Level API

The high-level API is designed to make it easier to register Delphi functions. This API level is in no way complete, so you still need to use the medium (or low) level API for most other functions.

To goal of this API level is that you can write your Delphi functions like this:

function NativeAdd(const AArg1, AArg2: Double): Double;
begin
  Result := AArg1 + AArg2;
end;

You'll probably agree that this is much easier than the version above. To register this function, you use the TDukGlue class:

DukGlue.RegisterFunction<Double, Double, Double>(NativeAdd, 'add');

This is a generic function that takes up to 4 type parameters for the types of the arguments, and 1 type parameter for the function result type. In this example, the two parameters and the function result are all of type Double.

Delphi is not able to use type inference for the generic RegisterFunction method, so you have to provide the type parameters yourself.

The complete high-level example looks like this (as found in the DuktapeHighLevel demo project):

procedure Run;
var
  DukGlue: TDukGlue;
begin
  { Create Duktape Glue object, using Delphi's memory manager. }
  DukGlue := TDukGlue.Create(True);
  try
    { Register native function called "print" that takes a
      variable number of arguments. TDukGlue does not support
      native functions with a variable number of arguments,
      so use the underlying "medium" level API. }
    DukGlue.Duktape.PushDelphiFunction(NativePrint, DT_VARARGS);
    DukGlue.Duktape.PutGlobalString('print');
    
    { Register native function called "add" that takes 2 arguments.
      We can use TDukGlue for this. }
    DukGlue.RegisterFunction<Double, Double, Double>(NativeAdd, 'add');
    
    { Evaluate some JavaScript code.
      This will call into our NativePrint and Add functions. }
    DukGlue.Duktape.Eval('print("Hello", "World!");');
    DukGlue.Duktape.Eval('print("2 + 3 =", add(2, 3));');
    
    { Pop eval result }
    DukGlue.Duktape.Pop;
  finally
    DukGlue.Free;
  end;
end;

Instead of a TDuktape object, you create a TDukGlue object instead and use its RegisterProcedure or RegisterFunction methods to register Delphi routines.

The medium-level API is exposed through its Duktape property. You can also access the low-level API if desired with the Context property.

The high-level API is less performant than the other levels, although this usually doesn't matter much.

Wish List

The name TDukGlue is borrowed from a C++ wrapper for Duktape. Like the C++ wrapper, the goal of this class is to greatly simplify common JavaScript+Delphi scenarios.

However, I only implemented a couple of simplifications, such as registering Delphi routines. It would be very nice if you could register complete Delphi classes and make them available to JavaScript. This is possible with the Duktapi API and the Delphi RTTI interface. But since we don't need this for our purposes at Grijjy, I didn't get around to implementing it.

But of course we welcome your pull requests. So if you create any additions that improve this library, please let us know!

delphiduktape's People

Contributors

erikvanbilsen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

delphiduktape's Issues

Running in Thread

Hello, I want to use this process in thread, but for native methods, data parameter is required. How can I do it.

duk_compile

Hi, can we add duk_compile funtion into the wrapper? Check the js code before run it.

Issue trying to use this with Lazarus

So i've downloaded the source right now (March 25, 2021), created a new Program (windowed) and put Duktape.Api.pas, Duktape.Glue.pas, Duktape.pas and Grijjy.inc into my project folder, alongside the .dlls on Bin.

I just added the includes on uses, my code for Unit1.pas is exactly this but when i try to compile and run the project i get this error:

Duktape.pas(10,3) Fatal: Unable to find System.SysUtils used by Duktape. (the message here can sounds weird because my Lazarus is set to Portuguese and i tried to translate the error to English for better understanding).

Going to Duktape.pas and changing System.SysUtils to just SysUtils supress this warning, but trying to run again i got this other error:

Duktape.pas(28,5) Fatal: Syntax error, ":" expected but "identifier _DATA" found

So, what i'm doing wrong here? Or the project is just compatible with Delphi?

Thanks in advance!

mac osx 10.13 build error. please fix it.

by duktape last version(v2.3.0), run ./BuildMacOS.sh,
ld: warning: The i386 architecture is deprecated for macOS (remove from the Xcode build setting: ARCHS)
ld: warning: ignoring file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/libSystem.tbd, missing required architecture i386 in file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/libSystem.tbd
Undefined symbols for architecture i386:
"___bzero", referenced from:
_duk_hbuffer_resize in duktape-5409d3.o
_duk_bi_json_stringify_helper in duktape-5409d3.o
_duk__do_compile in duktape-5409d3.o
_duk_create_heap in duktape-5409d3.o
_duk_push_thread_raw in duktape-5409d3.o
_duk_hthread_init_stacks in duktape-5409d3.o
_duk_hbuffer_alloc in duktape-5409d3.o
...
"___fpclassifyd", referenced from:
_duk_hobject_enumerator_next in duktape-5409d3.o

GetSystemTimePreciseAsFileTime win32 API is not available on Win7?

First, congrats for the new amazing project!

And here is an issue report:
I've successfully compiled the DuktapeHighLevel demo, upon starting up, it gives the `Cannot locate GetSystemTimePreciseAsFileTime in kerner.dll' error.

According to MSDN, GetSystemTimePreciseAsFileTime needs at least Windows 8?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.