You are not logged in.
I just commit a beta version of SyNode - the server-side JavaScript execution using the SpiderMonkey 45 library with nodeJS modules support.
The main features:
- based on SpiderMonkey45, so almost full support of ES6
- a remote debugger protocol realization, so can be debugged remotely using Firefox - see SyNode\Samples\02 - Bindings
- CommonJS modules realization, so compatible with NPM modules
- the first (as far as I know) server-side JavaScript engine which supports ES6 modules (import keyword). Relative path resolving not finished yet, but remote debugger already use a ES6 modules import/export
- modules can be implemented using Delphi (as a dll) - see SyNode\Samples\01 - Dll Modules
- JavaScript prototype definition based on Delphi RTTI (supported both "new" and old)
Well tested with Delphi XE2 (32bit). Verified in Delphi7.
The future direction:
- documentation and additional samples
- add a binding implementation for a nodeJS core modules to be more compatible with 3d party nodeJS modules - here community can help a lot
- fpc support? linux support? x64 support?
Last edited by mpv (2016-08-25 16:29:53)
Offline
Offline
Amazing! My inner version of NewPascal uses NodeJS for few purposes, but thanks to your work I think is worth to review my code and support this effort
- fpc support? linux support? x64 support?
FPC is the most important part. The Delphi licence (after XE2) is evil:
Licensee agrees not to use the Product to develop an application that is directly competitive to the Product or to any other Embarcadero products
Seems like mORMot breaks that point in many fields, IMO very bad licence for mORMot... The comment from Embarcadero employee is for me very strange and nasty:
There isn't really any official clarification on what that means. The license agreement is the official clarification - anything "official" beyond that would limit the scope of the agreement.
My understanding is similar to what +Roland Kossow said. Anything that someone would do to specifically target or clone our products. I expect that unless what you are doing is pretty blatant there won't be a reaction.
Basically, don't try to bite the hand that feeds you.
Another point for support for FPC: Delphi Linux compiler will be ARC only.
For extended RTTI for FPC we need to push forward NewPascal project.
Last edited by hnb (2016-08-26 08:31:47)
best regards,
Maciej Izak
Offline
Hi Friends,
Tried in Delphi 7 but ShLwApi.pas required in SyNode.pas (line 73) are missing.
unit SyNode;
...
uses
{$ifdef MSWINDOWS} Windows, ShLwApi,{$endif}
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
Classes,
Last edited by macfly (2016-08-26 20:27:56)
Offline
Realy strange.
This unit dont exists in my default installation of Delphi 7.
In Delphi xe5 exists.
For Delphi 7 i have found and downloaded from Jedi API
http://www.delphi-jedi.org/apilibrary.html
Now hi is calling for mozjs-45.dll
I have searched but dont found this dll to download.
Only mozjs-24.dll
Offline
@mpv, this is huge! Well done!
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
Dear mpv,
http://synopse.info/forum/viewtopic.php?id=3544
http://synopse.info/forum/viewtopic.php?id=3571
In the links above, you have identified that there should be a conflict between FastMM in FullDebug mode and a nspr memory manager (SpiderMonkey use a memory manager from Netscape Portable Run-time nspr4.dll).
However, FullDebugMode is needed for FastMM to log all memory leaks to a text file, and therefore rather important for finding memory leaks.
In SynSMAPI.pas, there is mentioned "compile mozjs without nspr" under "road map". Does this mean that this incompatibility between FastMM w/ FullDebugMode and SpiderMonkey will be removed in future ?
Offline
This idea is obsolete. At last, SM45 require nspr. Actually, Im not understand your problem.personnaly I did use fastmm in full debug mode may be several time during last 10 years. Even in non full debug mode fastmm can detect a memory leak very well.
Offline
This idea is obsolete. At last, SM45 require nspr. Actually, Im not understand your problem.personnaly I did use fastmm in full debug mode may be several time during last 10 years. Even in non full debug mode fastmm can detect a memory leak very well.
FastMM with FullDebugMode on can give the call stack trace with line numbers leading to the individual memory leak. Could you comment whether you use this feature, and if yes, how to work around ? Many thanks for your efforts to share your experience ! (It should be mentioned that FastMM with FullDebugMode on is also very useful to track any buffer overflow, again, with the call stack trace with line numbers .)
Last edited by ComingNine (2016-10-12 01:12:27)
Offline
It seems that the memory footer corruption can be avoided with some modification of TSynTempBuffer to avoid the re-allocation in TSynTempBuffer.Init. The reason is unclear. Could ab or you help to correct the root problem ?
private
// tmp: array[0..4095] of AnsiChar; <--- memory footer corruption
//
// for 'mORMot\SyNode\Samples\02 - Bindings\SpiderMonkey45Binding.dpr',
// where 'mORMot\SyNode\core_modules\DevTools\Debugger.js' will be loaded
// = Length of 'mORMot\SyNode\core_modules\DevTools\Debugger.js' * 2 + 1
tmp: array[0..118038] of AnsiChar;
//
// if only the files 'mORMot\SyNode\core_modules\node_modules\' are to be loaded
// = Length of NativeModule.wrap('mORMot\SyNode\core_modules\node_modules\util.js') * 2 + 1
// tmp: array[0..33584] of AnsiChar;
end;
Offline
It seems that the memory footer corruption can be avoided with some modification of TSynTempBuffer to avoid the re-allocation in TSynTempBuffer.Init. The reason is unclear. Could ab or you help to correct the root problem ?
Maybe related to http://synopse.info/forum/viewtopic.php?id=3468 ?
best regards,
Maciej Izak
Offline
Maybe related to http://synopse.info/forum/viewtopic.php?id=3468 ?
If so, then a quick test using Delphi 7 compiler & FullDebugMode should work without any problems. Does it?
Offline
Issue fixed by [d66449a2c4]. Thanks for reporting!
@AB - please, review the RawUnicodeToUtf8 function - may be we need to allocate one more byte there also?
tmp.Init(WideCharCount*3+1);
instead of
tmp.Init(WideCharCount*3);
Offline
Issue fixed by [d66449a2c4]. Thanks for reporting!
@AB - please, review the RawUnicodeToUtf8 function - may be we need to allocate one more byte there also?tmp.Init(WideCharCount*3+1);
instead of
tmp.Init(WideCharCount*3);
Dear @mpv, thank you very much for your efforts ! The fix looks too fundamental to the framework to believe !...
Thanks for the input.
I guess that RawUnicodeToUtf8() is safe, since its output is 8-bit content, so trailing #0 is stored as a single 0 byte.
Dear @ab,
function RawUnicodeToUtf8(Dest: PUTF8Char; DestLen: PtrInt; Source: PWideChar;
SourceLen: PtrInt; Flags: TCharConversionFlags): PtrInt; overload;
......
result := PtrInt(Dest);
inc(DestLen,PtrInt(Dest));
......
if (PtrInt(Dest)<DestLen) and (PtrInt(Source)<SourceLen) then
repeat
......
until false;
if not (ccfNoTrailingZero in Flags) then
Dest^ := #0;
as shown in SynCommons, if all UTF8 chars converted need 3 bytes to store, Dest^ := #0; is essentially setting the WideCharCount*3+1 th position instead of the WideCharCount*3 th position. Is this right ? It seems that @mpv has a good point here.
Last edited by ComingNine (2016-10-13 15:46:43)
Offline
???
There is no such thing as an "UTF-8 char", and Dest^ := #0 is setting only a single byte in the latest position, which is already reserved by TSynTempBuffer.Init:
GetMem(buf,len+1); // +1 to include trailing #0
Offline
???
There is no such thing as an "UTF-8 char", and Dest^ := #0 is setting only a single byte in the latest position, which is already reserved by TSynTempBuffer.Init:GetMem(buf,len+1); // +1 to include trailing #0
I should be more precise: characters encoded with UTF8.
I see your point. Now I feel lost again why mpv's fix works...
Last edited by ComingNine (2016-10-13 16:09:44)
Offline
It works because when you convert to UTF-16, the trailing #0 is stored in two bytes, so GetMem(buf,len+1) is not enough, and we have to call Init( +1) to add space for another byte.
+1 byte +1 byte = +2 bytes = +1 UTF-16 codepoint (WideChar)
and
+1 byte = +1 UTF-8 codepoint (AnsiChar)
Offline
Thank you for your patience and efforts to explain ! Too dumb to realize that the null terminator has to be two bytes in UTF-16 (also shown here http://stackoverflow.com/questions/2685730/)
https://www.securecoding.cert.org/confl … terminated
The first paragraph of the link above states "...... UTF-16 strings may contain \u0000 in the middle of the string, ......", but still, for UTF-16, the u'\0' is two bytes .
Last edited by ComingNine (2016-10-13 16:51:46)
Offline
Could you help to comment why the "JSObject.Is***Array" returns false in the code below ? The array element is apparently numeric. Many thanks !
PS: The function can be appended to SpiderMonkey45Binding: ufrmSM45Demo.
// Test with:
// mainForm.JSTestArray1D([1,2,3])
// mainForm.JSTestArray1D([1.0,2.0,3.0])
// mainForm.JSTestArray1D([1.1,2.2,3.3])
function TfrmSM45Demo.JSTestArray1D(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
jsArr, jsArrElement: jsval;
Cnt, I: Cardinal;
begin
jsArr := vp.argv[0];
mResult.Lines.Add('###########################');
mResult.Lines.Add(BoolToStr(jsArr.isObject, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.isArray(cx), True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsTypedArrayObject, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsInt8Array, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsUInt8Array, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsInt16Array, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsUInt16Array, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsInt32Array, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsUInt32Array, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsFloat32Array, True));
mResult.Lines.Add(BoolToStr(jsArr.asObject.IsFloat64Array, True));
mResult.Lines.Add('---------------------------');
jsArr.asObject.GetArrayLength(cx, Cnt);
mResult.Lines.Add(IntToStr(Cnt));
jsArr.asObject.GetElement(cx, 0, jsArrElement);
mResult.Lines.Add(BoolToStr(jsArrElement.isInteger, True));
mResult.Lines.Add(BoolToStr(jsArrElement.isDouble, True));
mResult.Lines.Add(BoolToStr(jsArrElement.isNumber, True));
mResult.Lines.Add(BoolToStr(jsArrElement.isSimpleVariant[cx], True));
mResult.Lines.Add('###########################');
Result := True;
end;
Last edited by ComingNine (2016-10-18 16:09:07)
Offline
Could you help to comment why the "jsval.asDouble" returns NaN for 0.0 and 1.0 and so forth in the code below, and how to work around this inconvenience ? Is it not possible to pass 0.0 to JavaScript function and retrieve afterwards ? Many thanks !
PS: The function can be appended to SpiderMonkey45Binding: ufrmSM45Demo.
// mainForm.JSTestOnlyZeroAfterDecimal(0.0) <--- NaN
// mainForm.JSTestOnlyZeroAfterDecimal(0.010)
// mainForm.JSTestOnlyZeroAfterDecimal(1.0) <--- NaN
// mainForm.JSTestOnlyZeroAfterDecimal(1.010)
function TfrmSM45Demo.JSTestOnlyZeroAfterDecimal(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
dblval: Double;
begin
dblval := vp.argv[0].asDouble;
mResult.Lines.Add('######## ' + FloatToStrF(dblval, ffFixed, 20, 8) + ' ########');
Result := True;
end;
Last edited by ComingNine (2016-10-19 03:45:49)
Offline
why the "JSObject.Is***Array" returns false
functions JSObject.Is***Array uses for JavaScript typed arrays. Array is not JavaScript typed array, so this functions return false.
Also your using jsArr.isObject without rooting is not good idea - garbage collecting can start in any moment, so your jsArr.isObject in any moment can point to invalid address. Also you do not handle exception if it can throw. the right ussage is
function TfrmSM45Demo.JSTestArray1D(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
jsArrElement: jsval;
jsArr: PJSRootedObject;
Cnt, I: Cardinal;
begin
try
mResult.Lines.Add('###########################');
mResult.Lines.Add(BoolToStr(vp.argv[0].isObject, True));
jsArr := cx.NewRootedObject(vp.argv[0].asObject);
try
mResult.Lines.Add(BoolToStr(jsArr.ptr.isArray(cx), True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsTypedArrayObject, True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsInt8Array, True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsUInt8Array, True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsInt16Array, True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsUInt16Array, True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsInt32Array, True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsUInt32Array, True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsFloat32Array, True));
mResult.Lines.Add(BoolToStr(jsArr.ptr.IsFloat64Array, True));
mResult.Lines.Add('---------------------------');
jsArr.ptr.GetArrayLength(cx, Cnt);
mResult.Lines.Add(IntToStr(Cnt));
jsArr.ptr.GetElement(cx, 0, jsArrElement);
mResult.Lines.Add(BoolToStr(jsArrElement.isInteger, True));
mResult.Lines.Add(BoolToStr(jsArrElement.isDouble, True));
mResult.Lines.Add(BoolToStr(jsArrElement.isNumber, True));
mResult.Lines.Add(BoolToStr(jsArrElement.isSimpleVariant[cx], True));
mResult.Lines.Add('###########################');
finally
cx.FreeRootedObject(jsArr);
end;
Result := True;
except
on E: Exception do begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
Last edited by Orel (2016-10-19 09:17:32)
Offline
why the "jsval.asDouble" returns NaN for 0.0 and 1.0
SpiderMonkey stores number value in 2 ways: signed int32 or double. 0.0 or 1.0 is converts to int32 and you can not use it as double.
So for using number value you can use the next code
function TfrmSM45Demo.JSTestOnlyZeroAfterDecimal(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
dblval: Double;
begin
try
if (argc = 0) or not vp.argv[0].isNumber then
raise ESMException.Create('Expectig argument number type ');
if vp.argv[0].isDouble then
dblval := vp.argv[0].asDouble
else if vp.argv[0].isInteger then
dblval := vp.argv[0].asInteger
else
raise ESMException.Create('This can never happen');
mResult.Lines.Add('######## ' + FloatToStrF(dblval, ffFixed, 20, 8) + ' ########');
Result := True;
except
on E: Exception do begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
Offline
Dear Orel, thank you very much for your knowledgeable posts ! (PJSRootedObject does not have isObject so that mResult.Lines.Add(BoolToStr(jsArr.isObject, True)); line has to be commented out.)
It then seems that we could not get Delphi array directly from jsval but has to call ptr.GetElement(cx, index, jsArrElement); manually.
Offline
isObject is property of jsval. Fixed in example.
It then seems that we could not get Delphi array directly from jsval but has to call ptr.GetElement(cx, index, jsArrElement); manually.
Yes, you are right.
JavaScript allow the next construction
var arr = [];
arr[5] = 5;
arr[770000001] = 100;
in this case arr.length is 770000002, but array has only 2 values. arr[4] or arr[77778] is undefined. SpiderMonkey do not allocate memory for other values. So, you can not get all values to delphi array directly. Array in JS and array in Delphi is not the same.
Dirrectly acces to values you can get if you use JavaScript typed array. But in this case you cannot change array dimension
Last edited by Orel (2016-10-19 09:51:21)
Offline
Thank you very much for your helpful comments !
Offline
Dear mpv and Orel,
Could you help to comment how to use SyNode (SpiderMonkey) to expose a whole set of Delphi class/object hierarchy to JavaScript ?
As shown in ADOBE ILLUSTRATOR CC 2015.3 SCRIPTING REFERENCE: JAVASCRIPT, Adobe Illustrator has exposed its whole CPP class/object hierarchy to JavaScript. Consequently, we can create arbitrary Illustrator CPP instances within JavaScript, and call methods of these Illustrator CPP instances within JavaScript.
#include "lib/lodash.3.10.1.js"
var docRef = Application.Documents.Add(); // Create an empty Document with default settings
...
var groupRef = docRef.layers[0].groupItems[0]; // Manipulate the properties of the newly-created Document
_.each(groupRef.pathItems, function(pathItemRef)
{
if(pathItemRef.strokeDashes.length >= 1)
{
pathItemRef.strokeWidth = 2.0;
pathItemRef.strokeDashes = [2, 4];
}
})
Nonetheless, as shown in the DoOnCreateNewEngine callback in mORMot\SyNode\Samples\02 - Bindings, it seems that only existing Delphi instance is exposed to the JavaScript global object as property.
aEngine.GlobalObject.ptr.DefineProperty(aEngine.cx, 'mainForm',
// proeprty value is a wrapper around the Self
CreateJSInstanceObjForSimpleRTTI(aEngine.cx, Self, aEngine.GlobalObject),
// we can enumerate this property, it read-only and can not be deleted
JSPROP_ENUMERATE or JSPROP_READONLY or JSPROP_PERMANENT
);
Furthermore, as shown in 22.3.2. Calling Delphi code from JavaScript, it seems that only method of existing Delphi instance is exposed to the JavaScript global object as method.
procedure TTestServer.DoOnNewEngine(const Engine: TSMEngine);
...
// add native function to the engine
Engine.RegisterMethod(Engine.GlobalObj,'loadFile',LoadFile,1);
end;
function TTestServer.LoadFile(const This: variant; const Args: array of variant): variant;
begin
if length(Args)<>1 then
raise Exception.Create('Invalid number of args for loadFile(): required 1 (file path)');
result := AnyTextFileToSynUnicode(Args[0]);
end;
Lastly, I could not figure out how to do this from https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_Cookbook
I am therefore rather lost how to achieve similar effect with SyNode (SpiderMonkey). For the hierarchy shown below, could you help to comment how to expose not only the instance of the TMainForm class but also the TWorkerForm and TWorker classes (instances may not be created yet), so that TWorderForm can be created in JavaScript, and methods of TWorker can be called in JavaScript ?
Many thanks !!
Last edited by ComingNine (2016-10-22 03:52:02)
Offline
You should expose classes TWorkerForm & TWorker to the engine using TSMEngine.defineClass method. In JavaScript you must define a prototype for a class - for Delphi objects you have 2 choice TSMSimpleRTTIProtoObject & TSMNewRTTIProtoObject.
In the sample "02 - Bindings" we define a prototype TStringsProto and register it in the DoOnCreateNewEngine - see the sample sources . After a class is registered you can create it directly from JS using
var myForm = new TWorkerForm()
Offline
You should expose classes TWorkerForm & TWorker to the engine using TSMEngine.defineClass method. In JavaScript you must define a prototype for a class - for Delphi objects you have 2 choice TSMSimpleRTTIProtoObject & TSMNewRTTIProtoObject.
In the sample "02 - Bindings" we define a prototype TStringsProto and register it in the DoOnCreateNewEngine - see the sample sources . After a class is registered you can create it directly from JS using
var myForm = new TWorkerForm()
Thank you very much for your instructions !
Nonetheless, even though TSringsProto is defined in the sample "02 - Bindings", there seems to be lack of any exemplary usage of it… Could you suggest me the guideline to implement feature like var viewFrm = mainForm.ViewForms.Add ? I mean, after I use defineClass, how should I implement correctly the typed collection properties (ViewForms) of mainForm, and the Add method of the typed collection ? Many thanks !
Last edited by ComingNine (2016-10-23 08:06:53)
Offline
Could you help to comment a few more small problems that I meet when trying mORMot\SyNode\Samples\02 - Bindings:
(1) when running, in the left memo, calling fs.writeFileSync fails complaining writeFileNew is undefined, calling fs.appendFileSync fails complaining appendFileNew is undefined.
(2) when running, in the left memo, calling alert fails complaining alert is undefined...
(3) when running, in the left memo, calling const console = require('console') or const console = require('console').Console fails complaining Console expects a writable stream instance...
(4) when running, in the left memo, adding four lines below will not trigger function TStringsTextRead at all(breakpoint does not stop)... Is TStringsProto used at all in this sample ?
let msg1 = mainForm.results.Text
let msg2 = mainForm.results
var msg3 = mainForm.results.Text
var msg4 = mainForm.results
(5) when running, in the left memo, var x = new TStrings() is evaluated without error, even after the aEngine.defineClass(mSource.lines.ClassType, TStringsXProto, aEngine.GlobalObject); line in the source code is commented out. Why is this possible ? It seems that SyNode does not automatically call defineClass by itself.
(6) when running, in the left memo, after the code below is evaluated, the right memo gives "HelloWorld" without the line break ? More importantly, neither function TStringsTextRead nor function TStringsWrite is triggered (breakpoint does not stop)...
var x = new TStrings()
x.Text = "Hello\nWorld"
mainForm.toLog(x.Text)
var y = new TMemoStrings()
y.Text = "Hello\nWorld"
mainForm.toLog(y.Text)
[7] when running, in the left memo, the code below exemplifies the usage of TStringsProto to me !
mainForm.toLog("line 1");
mainForm.toLog("line 2\r\nline 3");
mainForm.toLog("line 4");
let l_memo = mainForm.results /* results is the published property of TMainForm and thus accessible automatically ? */
let l_memostrings = mainForm.results.lines /* Lines is the published property of TMemos and thus accessible automatically, as implemented in SyNodeSimpleProto.pas:477: ? */
var v_memo = mainForm.results /* Points to the same reference as l_memo */
var v_memostrings = mainForm.results.lines /* Points to the same reference as l_memostrings */
/* TStringsTextRead */
mainForm.toLog("l_memo.lines.text: " + l_memo.lines.text)
mainForm.toLog("v_memo.lines.text: " + v_memo.lines.text)
/* TStringsTextWrite */
l_memostrings.text = "line 1\r\nline 2\r\nline 3\r\nline 4"
mainForm.toLog("v_memostrings.text: " + v_memostrings.text)
Many thanks !!
Last edited by ComingNine (2016-10-23 09:07:00)
Offline
Taking "mORMot\SyNode\Samples\02 - Bindings" for example, if another TForm descendant unit is added
unit Unit1;
interface
uses
SysUtils, Classes, Forms;
type
{$M+}
TWorker = class
private
fName: string;
public
constructor Create;
published
property Name: string read fName;
end;
TForm1 = class(TForm)
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
fWorker: TWorker;
public
published
property Worker: TWorker read fWorker;
end;
implementation
{ TWorker }
constructor TWorker.Create;
begin
fName := 'A new TWorker instance';
end;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
fWorker := TWorker.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
fWorker.Free;
end;
end.
,and the ufrmSM45Demo unit is modified with the addition of
...
published
...
function NewForm1(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
...
function TfrmSM45Demo.NewForm1(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
Frm1: TForm1;
begin
Frm1 := TForm1.Create(Self);
vp.rval := CreateJSInstanceObjForSimpleRTTI({FEngine.}cx, Frm1, FEngine.GlobalObject);
Result := True;
end;
,one can run the JavaScript code below
var f = mainForm.NewForm1()
mainForm.toLog(f.worker.name)
.
Please notice that the first letter of the published property has to be typed in lower case, and there is no such restriction regarding the first letter of the published function.
Could you help to comment about the reason ? Is it possible to remove the restriction for published properties ?
Offline
Today I found some spare time connected from FireFox to the binding002 demo program, very cool
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
Would it be possible to use SynNode to compile smart pascal to javascript?
Following ComingNine's example, generate similar code:
var TWorker = {
$ClassName:"TWorker",$Parent:TObject
,$Init:function ($) {
TObject.$Init($);
$.fName = "";
}
,Create$61:function(Self) {
Self.fName = "A new TWorker instance";
return Self
}
,Destroy:TObject.Destroy
};
Self.fWorker = TWorker.Create$61($New(TWorker));
console.log(Self.fWorker.fName); // "A new TWorker instance"
Offline
Dear mpv,
Could you help to comment about returning Delphi object from JavaScript function, using mORMot\SyNode\Samples\02 - Bindings as an example:
(1) add TForm1 to the project, as shown in #35 post of this thread, and modify the btnEvaluateClick as below
procedure TfrmSM45Demo.btnEvaluateClick(Sender: TObject);
var
res: jsval;
Frm1V: Variant;
Frm1: TForm1;
begin
// evaluate a text from mSource memo
if mSource.SelText <> '' then
FEngine.Evaluate(mSource.SelText, 'mSourceSelected.js', 1, res)
else
FEngine.Evaluate(mSource.lines.Text, 'mSource.js', 1, res);
res := FEngine.CallObjectFunction(FEngine.GlobalObject, 'NewForm1AsJSFunc', []);
Frm1V := res.asSimpleVariant[FEngine.cx]; <---- ESMException in function jsval.getSimpleVariant(cx: PJSContext): Variant;
Frm1 := TVarData(V).VPointer; // Seems feasible to hold object in Variant according to Barry Kelly's comment in this SO post http://stackoverflow.com/questions/366329/why-cant-delphi-variants-hold-objects
Frm1.Caption := 'hello ! Delphi Object from JavaScript!';
end;
(2) when running, in the left memo, type in the following code and evaluate. Unfortunately, ESMException occurs in function jsval.getSimpleVariant(cx: PJSContext): Variant;
function NewForm1AsJSFunc()
{
var f = mainForm.NewForm1()
return f
}
Could you help to finish the TODOs concerning JSTYPE_OBJECT and JSTYPE_FUNCTION in function jsval.getSimpleVariant, so that Delphi code can call JavaScript functions which return customized Delphi object ?
Last edited by ComingNine (2016-10-26 15:11:31)
Offline
You can get a pointer to the native (Delphi) instance using IsInstanceObject. Unfortunately, we lost a variant compatibility for a complex types because of rooting mechanism in SM45
var
res: jsval;
instRef: PSMInstanceRecord;
..
res := FEngine.CallObjectFunction(FEngine.GlobalObject, 'NewForm1AsJSFunc', []);
if IsInstanceObject(FEngine.cx, res, instRef) then
if (instRef.instance is TForm) then
.. do something with TForm
Last edited by mpv (2016-10-27 05:36:00)
Offline
Please notice that the first letter of the published property has to be typed in lower case, and there is no such restriction regarding the first letter of the published function.
Could you help to comment about the reason ? Is it possible to remove the restriction for published properties ?
I'm force the conversion of first character of Delphi properties to the lower case to follow JavaScript naming convention. We do not do it for a method's, because method's (function in term of JS) can be a constructor (as you write in example above mainForm.NewForm1() ), and JavaScript naming convention for constructors is "start it name from a upper case". So this is a developer responsibility to name the methods in a right way (camelCase for usual functions, StartFromUpper for a functions what must be called with new).
Offline
You can get a pointer to the native (Delphi) instance using IsInstanceObject. Unfortunately, we lost a variant compatibility for a complex types because of rooting mechanism in SM45
var res: jsval; instRef: PSMInstanceRecord; .. res := FEngine.CallObjectFunction(FEngine.GlobalObject, 'NewForm1AsJSFunc', []); if IsInstanceObject(FEngine.cx, res, instRef) then if (instRef.instance is TForm) then .. do something with TForm
Thank you very much for your instructions !
ComingNine wrote:Please notice that the first letter of the published property has to be typed in lower case, and there is no such restriction regarding the first letter of the published function.
Could you help to comment about the reason ? Is it possible to remove the restriction for published properties ?I'm force the conversion of first character of Delphi properties to the lower case to follow JavaScript naming convention. We do not do it for a method's, because method's (function in term of JS) can be a constructor (as you write in example above mainForm.NewForm1() ), and JavaScript naming convention for constructors is "start it name from a upper case". So this is a developer responsibility to name the methods in a right way (camelCase for usual functions, StartFromUpper for a functions what must be called with new).
Thank you very much for your helpful comments !
PS: Could you also help to comment about my problems concerning undefined JS functions in #34 post of this thread ? More importantly, do you recommend loading into SyNode renowned JS libraries such as Node.js and Lodash and so forth ? I mean, on the official page of Node.js, it is written that Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine...
Many thanks !
Last edited by ComingNine (2016-10-27 13:43:22)
Offline
About fs.writeFile* - we are working on `fs` module right now (this part is already implemented inside UnityBase, but we need do remove some internal dependencies to move it to SyNode)
About `lodash` - you can use it right now
1) Install lodash
>cd "D:\Work\synopse\SyNode\Samples\02 - Bindings\"
>npm install lodash
2) Use it
var _ = require('lodash');
var sortedSQ = _([3, 2, 1]).map( (x) => x*x ).sort().values();
mainForm.toLog(JSON.stringify(sortedSQ));
Offline
About fs.writeFile* - we are working on `fs` module right now (this part is already implemented inside UnityBase, but we need do remove some internal dependencies to move it to SyNode)
About `lodash` - you can use it right now
1) Install lodash>cd "D:\Work\synopse\SyNode\Samples\02 - Bindings\" >npm install lodash
2) Use it
var _ = require('lodash'); var sortedSQ = _([3, 2, 1]).map( (x) => x*x ).sort().values(); mainForm.toLog(JSON.stringify(sortedSQ));
Great to know that !
Thank you for your efforts and helpful comments !
Last edited by ComingNine (2016-10-27 15:07:45)
Offline
Dear mpv, could you help to comment the following problem of mine ? Many thanks !
I had a Delphi .dpk package (for Win32) which could build and install without any problem. However, after adding JavaScript-related code (thanks to great mORMot and SyNode projects), even if the mozjs-45.dll, nsp4.dll and the rest dll files are within the .bpl directory, or put into C:\Windows\SysWOW64, the package can no longer be installed. The error is
The procedure entry point JS_GetSharedArrayBufferViewType could not be located in the dynamic library test.bpl
It is really confusing me because mozjs-45.dll is right there in the same directory as the .bpl file.
Could you help to comment about the reason and possibly the workround ? Many thanks !
Last edited by ComingNine (2016-10-28 16:36:37)
Offline
We commit a huge improvement to the SyNode. The most important changes are:
- x64 SpiderMonkey support - compiled x64 binary is here. Tested with XE2 & XE4
- nodeJS Buffer binding & Buffer module
- pure JavaScript implementation of timer loop, so setTimeout/setInterval etc. are now work as expected
@ComingNine - also this commit fix a issue with JS_GetSharedArrayBufferViewType.
Since we now have a Buffer we can implement a fully nodeJS compatible fs module. Current implementation is based on AnyTextFileToRawUTF8 and do not allow chunked file reading/writing
Offline
We commit a huge improvement to the SyNode. The most important changes are:
- x64 SpiderMonkey support - compiled x64 binary is here. Tested with XE2 & XE4
- nodeJS Buffer binding & Buffer module
- pure JavaScript implementation of timer loop, so setTimeout/setInterval etc. are now work as expected@ComingNine - also this commit fix a issue with JS_GetSharedArrayBufferViewType.
Since we now have a Buffer we can implement a fully nodeJS compatible fs module. Current implementation is based on AnyTextFileToRawUTF8 and do not allow chunked file reading/writing
Really great news ! Really fabulous work !!
Offline
x64 bit - incredible news! Congratulations!!!
Erick
Offline
About fs.writeFile* - we are working on `fs` module right now (this part is already implemented inside UnityBase, but we need do remove some internal dependencies to move it to SyNode)
About `lodash` - you can use it right now
1) Install lodash>cd "D:\Work\synopse\SyNode\Samples\02 - Bindings\" >npm install lodash
2) Use it
var _ = require('lodash'); var sortedSQ = _([3, 2, 1]).map( (x) => x*x ).sort().values(); mainForm.toLog(JSON.stringify(sortedSQ));
Dear mpv and Orel,
at the #26, #29 posts of this thread, Orel has mentioned that Delphi array and JS (normal/typed ?) array are different things. Could you help to suggest how to code the JSNative function at Delphi side, to return array to JS side, which can be used with lodash ? Many thanks !
Last edited by ComingNine (2016-10-31 08:52:31)
Offline
Could you help to suggest how to code the JSNative function at Delphi side, to return array to JS side, which can be used with lodash ? Many thanks !
for normal Array
function <funcName>(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var arr: PJSRootedObject;
...
begin
try
....
arr := cx.NewRootedObject(cx.NewArrayObject(0));
try
arr.ptr.SetElement(cx, 0, SimpleVariantToJSval(cx, 'Some string'));
arr.ptr.SetElement(cx, 1, SimpleVariantToJSval(cx, 25));
arr.ptr.SetElement(cx, 2, SimpleVariantToJSval(cx, true));
...
vp.rval := arr.ptr.ToJSValue;
finally
cx.FreeRootedObject(arr);
end;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
for Typed array(it is like delphi array - all elements has the same type but you can not change size of array after creation)
function <funcName>(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
const arrSize = 50;
var arr: PJSRootedObject;
arrData: Puint32Vector;
i: integer;
isShared: Boolean;
...
begin
try
....
arr := cx.NewRootedObject(cx.NewUint32Array(arrSize));
try
arrData := arr.GetUint32ArrayData(isShared, nil);
for i := 0 to arrSize - 1 do
arrData [i] := i*i;
...
vp.rval := arr.ptr.ToJSValue;
finally
cx.FreeRootedObject(arr);
end;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
end;
Last edited by Orel (2016-10-31 09:53:52)
Offline
Dear Orel,
thank you very much for your kind help and instructive sample code !
PS: It seems that in your above post and also your #26 post, one should pay strict attention to the rooting and the exception handling.
Could you help to comment whether you would suggest that I revise the code below (also shown in my #35 post) accordingly ? Many thanks !
From
function TfrmSM45Demo.NewForm1(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
Frm1: TForm1;
begin
Frm1 := TForm1.Create(Self);
vp.rval := CreateJSInstanceObjForSimpleRTTI({FEngine.}cx, Frm1, FEngine.GlobalObject);
Result := True;
end;
to
function TfrmSM45Demo.NewForm1(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean;
var
frm1: TForm1;
rootObj: PJSRootedObject;
begin
try
frm1 := TForm1.Create(Self);
try
rootObj := cx.NewRootedObject(CreateJSInstanceObjForSimpleRTTI({FEngine.}cx, frm1, FEngine.GlobalObject));
vp.rval := rootObj.ptr.ToJSValue;
finally
cx.FreeRootedObject(rootObj);
end;
Result := True;
except
on E: Exception do
begin
Result := False;
vp.rval := JSVAL_VOID;
JSError(cx, E);
end;
end;
More over, if the above revision is correct, could you help to comment why there is cx.FreeRootedObject(rootObj); ? I mean, the return value of this JSNative function, vp.rval, will be needed afterwards. The vp.rval stores rootObj.ptr.ToJSValue, but rootObj is freed ? The GC Rooting Guide does not help me understand here. It really puzzles me
Last edited by ComingNine (2016-10-31 12:58:48)
Offline