You are not logged in.
Many thanks for your help ! ![]()
It works after the following change:
...
{$IFDEF FPC}
plain := mmoPlain.Text;
{$ELSE ~FPC}
plain := StringToUTF8(mmoPlain.Text);
{$ENDIF ~FPC}
...
{$IFDEF FPC}
mmoPlain.Text := plain;
{$ELSE ~FPC}
mmoPlain.Text := UTF8ToString(plain);
{$ENDIF ~FPC}
...IIRC there is an explicit check in SynCommons.pas which check for the TSynPersistent class type.
Many thanks for your helpful comments !
Indeed the explicit check for TSynPersistent class type is the root reason ! If SynCommons.TSynPersistent is found in the inheritance chain, the TSynPersistentClass(ItemClass).Create call ensures that the correct constructor is called. For TPersistent for example, the ItemClass.Create call goes to TObject.Create, in which case the deserialization will not work unless the field is initialized in the overridden AfterConstruction procedure.
procedure TClassInstance.Init(C: TClass);
begin
ItemClass := C;
if C<>nil then
repeat // this unrolled loop is faster than cascaded if C.InheritsFrom()
...
if C<>TSynPersistent then
...
ItemCreate := cicTSynPersistent;
exit;
... function TClassInstance.CreateNew: TObject;
begin
...
case ItemCreate of
...
cicTSynPersistent: begin
result := TSynPersistentClass(ItemClass).Create;
exit;
end;
...
cicTObject: begin
result := ItemClass.Create;
exit;
end;
...
end;I am trying to learn about the JSON deserialization inside mORMot. As shown in the code below (and in the compilable code in gist), I am trying to deserialize a class with a published TStrings property from JSON. The deserialization only works if the class inherits from SynCommons.TSynPersistent, and does not work if the class inherits from Classes.TPersistent. Interestingly, the deserialization also does not work if the TSynPersistent code is copied exactly into the .dpr and the class is made to inherit from this copy.
type
{$M+}
// Exactly the same as in SynCommons.pas
TSynPersistent = class(TObject)
...
// TSample = class(TPersistent) // Does not work
TSample = class(TSynPersistent) // Does not work
// TSample = class(SynCommons.TSynPersistent) // Works
private
FContent: TStrings;
published
property Content: TStrings read FContent;
public
// constructor Create; // if inherits from TPersistent
constructor Create; override; // if inherits from TSynPersistent or SynCommons.TSynPersistent
destructor Destroy; override;
end;var
Sample: TSample;
JsonContentRaw: RawByteString;
JsonContentUTF8: RawUTF8;
JsonContentUTF8Buf: PUTF8Char;
JsonContentValid: Boolean;
begin
TJSONSerializer.RegisterClassForJSON([TSynPersistent, TSample]);
Sample := TSample.Create;
Sample.Content.Add('new line 3');
SynCommons.FileFromString(ObjectToJSON(Sample, [woHumanReadable, woStoreClassName]), ChangeFileExt(ParamStr(0), '.Sample.json'));
FreeAndNil(Sample);
JsonContentRaw := SynCommons.StringFromFile(ChangeFileExt(ParamStr(0), '.Sample.json'));
JsonContentUTF8 := CurrentAnsiConvert.AnsiToUTF8(JsonContentRaw);
JsonContentUTF8Buf := @JsonContentUTF8[1];
Sample := JSONToNewObject(JsonContentUTF8Buf, JsonContentValid) as TSample;
Writeln(Format('%s', [BoolToStr(JsonContentValid, True)]));
if JsonContentValid then
Writeln(Format('%s', [Sample.Content.Text]));
end.After following into mORMot.pas, it seems that the obvious reason could be that the GetterAddr(Instance) call returns the pointer to the underlying field if the class inherits from SynCommons.TSynPersistent, but returns a pointer to nil if the class inherits from TPersistent or, interestingly, the exact copy of TSynPersistent. Could you help to comment why a pointer to nil is returned if the class inherits from TPersistent or, more interestingly, the exact copy of TSynPersistent ? ![]()
function TPropInfo.ClassFromJSON(Instance: TObject; From: PUTF8Char;
var Valid: boolean; Options: TJSONToObjectOptions): PUTF8Char;
...
if GetterIsField then
// no setter -> use direct in-memory access from getter (if available)
Field := GetterAddr(Instance) else
// no setter, nor direct field offset -> impossible to set the instance
exit;
result := JSONToObject(Field^,From,Valid,nil,Options);
... For Delphi, TMemo now shows correct characters after calls of CurrentAnsiConvert routines are modified to be "symmetric". ![]()
procedure TMainForm.btnEncryptWrongClick(Sender: TObject);
var
Key: TSHA256Digest;
plain, cipher: RawByteString;
begin
SHA256Weak(CurrentAnsiConvert.UTF8ToAnsi(StringToUTF8(AES_KEY)), Key);
plain := StringToUTF8(mmoPlain.Text);
cipher := SynCrypto.AES(Key, 256, plain, true);
// cipher := SynCrypto.AES(Key, 256, CurrentAnsiConvert.UTF8ToAnsi(plain), true);
mmoCipher.Text := UTF8ToString(BinToBase64(cipher));
end;
procedure TMainForm.btnDecryptWrongClick(Sender: TObject);
var
Key: TSHA256Digest;
plain, cipher: RawByteString;
begin
SHA256Weak(CurrentAnsiConvert.UTF8ToAnsi(StringToUTF8(AES_KEY)), Key);
cipher := Base64ToBin(StringToUTF8(mmoCipher.Text));
plain := SynCrypto.AES(Key, 256, cipher, false);
// plain := CurrentAnsiConvert.AnsiToUTF8(SynCrypto.AES(Key, 256, cipher, false)); // Calling of CurrentAnsiConvert routines has to be "symmetric"
mmoPlain.Text := UTF8ToString(plain);
end;However, for FPC (on Windows), TMemo displays some characters correctly but question marks for others... O_O Could you help to comment about the reason of this behavior and possible workaround ?
The updated test files can be viewed at https://gist.github.com/anonymous/1995e … 1bad7f2974
I am trying to encrypt the content from the TMemo (Chinese characters), and then decrypt and show them. The data (bytes) seems to be the same after AES encryption/decryption, as can be seen by going to the address "plain[1]" in the Memory debug window. However, the TMemo cannot display the original/correct characters. Could you help to comment what is the correct way out here ? Many thanks ! O_O
PS: The situation is tested with Delphi 7 ( comment out type ChineseString = type Ansistring(936) ) and Delphi Tokyo.
PS: Under Delphi Tokyo, the line calling SHA256Weak generates complaint "W1058 Implicit string cast with potential data loss from 'string' to 'RawByteString'." I thought methods with RawByteString parameter are ready for any string argument... O_O Could you help to comment the correct way to call SHA256Weak ?
uses
SynCommons, SynLog, SynCrypto;
const
TEST_KEY: string = 'TEST_KEY';
procedure TMainForm.btnEncryptClick(Sender: TObject);
var
Key: TSHA256Digest;
plain, cipher: RawByteString;
begin
SHA256Weak(TEST_KEY, Key);
plain := StringToUTF8(mmoPlain.Text);
// mmoPlain.Text := UTF8ToString(plain);
// mmoPlain.Text := UTF8ToString(AnyAnsiToUTF8(plain));
// Exit;
cipher := SynCrypto.AES(Key, 256, plain, true);
mmoCipher.Text := UTF8ToString(BinToBase64(cipher));
end;
type
ChineseString = type Ansistring(936); // https://stackoverflow.com/questions/7222615/how-can-i-convert-string-encoded-with-windows-codepage-1251-to-a-unicode-string
procedure TMainForm.btnDecryptClick(Sender: TObject);
var
Key: TSHA256Digest;
plain, cipher: RawByteString;
ChineseStr: ChineseString;
begin
SHA256Weak(TEST_KEY, Key);
cipher := Base64ToBin(StringToUTF8(mmoCipher.Text));
plain := SynCrypto.AES(Key, 256, cipher, false);
mmoPlain.Text := UTF8ToString(AnyAnsiToUTF8(plain)); // Does not work
// mmoPlain.Text := UTF8ToString(CurrentAnsiConvert.AnsiToUTF8(plain)); // Does not work
// mmoPlain.Text := UTF8ToUnicodeString(CurrentAnsiConvert.AnsiToUTF8(plain)); // Does not work
// ChineseStr := UTF8ToString(AnyAnsiToUTF8(plain)); // Does not work
// mmoPlain.Text := ChineseStr;
// mmoPlain.Text := TEncoding.Default.GetString(TBytes(@plain[1])); // Does not work
end;The test files can be viewed at http://gist.github.com/anonymous/fa917e … 447137bcbb
Sorry. It seems that linking the Crt unit will drastically change how SynLog colorize the echoed log. For example, sllInfo level log will be bold instead of being ccWhite.
For me, it looks more natural to reset color to ccBlack instead of ccLightGray at the end of TSynLog.ConsoleEcho with Linux.
It seems NormVideo is not void ?
Procedure NormVideo;
{
Set normal back and foregroundcolors.
}
Begin
TextColor(7);
TextBackGround(0);
End;
I just tried to add "Crt.NormVideo" at the end of the above .lpr under FPC and it worked.
Many thanks for your effort ! I will insert "Crt.NormVideo" at the end of .lpr under FPC ![]()
After searching the internet I am still not sure. For example, on some distributions the default terminal color is dependent on the "theme". Perhaps it is better to leave it as is...
Since normally the console color is black, could you consider to reset color to ccBlack instead of ccLightGray at the end of TSynLog.ConsoleEcho ?
Currently, if EchoToConsole is set to LOG_VERBOSE, the color of the prompt for the next command becomes from black to light gray, which seems odd... ![]()
Example:
program Project1;
{$IFNDEF FPC}
{$APP CONSOLE}
{$ENDIF}
uses
SysUtils,
SynCommons, SynLog;
procedure xx;
var
ILog: ISynLog;
SMethodName: string;
SMethodNameUTF8: RawUTF8;
begin
SMethodName := 'enter xx';
SMethodNameUTF8 := StringToUTF8(SMethodName);
(*
[url]https://synopse.info/forum/viewtopic.php?pid=25540[/url]
@ returns a pointer to a variable: @str is a pointer to the string pointer, not to the string content
@str[1] is a pointer to the first char, i.e., the pointer to the string content
Pointer() type-cast the variable to another type: hard-cast of a string variable as Pointer returns the pointer to the string content
*)
// Does not work.
// ILog := TSynLog.Enter(nil, @SMethodNameUTF8);
// Either way works.
ILog := TSynLog.Enter(nil, @SMethodNameUTF8[1]);
// ILog := TSynLog.Enter(nil, Pointer(SMethodNameUTF8));
ILog.Log(sllWarning, 'hi xx');
end;
begin
with TSynLog.Family do
begin
{$IFDEF MSWINDOWS}
AutoFlushTimeOut := 1;
{$ENDIF ~MSWINDOWS}
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE;
PerThreadLog := ptIdentifiedInOnFile;
RotateFileCount := 50;
RotateFileSizeKB := 20 * 1024; // rotate by 20 MB logs
end;
xx;
end.
Many thanks for your kind help ! ![]()
Thank you very much for your helpful explanation !
@SMethodNameUTF8 is in fact what I have used, and does not work as shown.
However, Pointer(SMethodNmeUTF8) does work. The overloaded TSynLog.Enter method, which uses TextFmt/TextArgs input parameters, is even better.
Could you help to comment why Pointer(SMethodNmeUTF8) works but @SMethodNameUTF8 does not ?
Is it documented that hard-cast of a string variable as Pointer returns the pointer to the string content ? ![]()
I try to provide customized string to TSynLog.Enter. However, the following code gives unreadable characters as shown in the picture. The same problem can be seen with Delphi/Win, FPC/Win and FPC/Linux.
Could you help to show the correct way to use the version of TSynLog.Enter with PUTF8Char parameter ?
Many thanks !
program Project1;
{$IFNDEF FPC}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
SysUtils,
SynCommons, SynLog;
procedure xx;
var
ILog: ISynLog;
SMethodName: string;
SMethodNameUTF8: RawUTF8;
begin
SMethodName := 'enter xx';
SMethodNameUTF8 := StringToUTF8(SMethodName);
ILog := TSynLog.Enter(nil, @SMethodNameUTF8); // <--- Will show unreadable character
ILog.Log(sllWarning, 'hi xx');
end;
procedure yy;
var
ILog: ISynLog;
SMethodName: string;
SMethodNameUTF8: RawUTF8;
begin
SMethodName := 'enter yy';
SMethodNameUTF8 := StringToUTF8(SMethodName);
ILog := TSynLog.Enter(nil, @SMethodNameUTF8); // <--- Will show unreadable character
ILog.Log(sllWarning, 'hi yy');
end;
begin
with TSynLog.Family do
begin
{$IFDEF MSWINDOWS}
AutoFlushTimeOut := 1;
{$ENDIF ~MSWINDOWS}
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE;
PerThreadLog := ptIdentifiedInOnFile;
RotateFileCount := 50;
RotateFileSizeKB := 20 * 1024; // rotate by 20 MB logs
end;
xx;
yy;
end.
Dear mpv, could you share us the news of SyNode support with NewPascal under Linux ![]()
Many thanks !
> IMHO it is up to the consumers (other objets) to make their own private copy of the original dynamic array.
In principle you are right.
Still, it does not hurt anyone to be defensive, when the overhead of copying the primitive array is negligible. ![]()
> Using such a getter is slower when you access the array items,
Iteration will not be done on the property but always on a local var instead. "ArrByRef" is provided in case "consumer" is reading only.
> , and also probably misleading the serializer expectations.
Apparently the serializer is "definitely" misled. The value of "Arr" is "[]", which should be the same as the value of "ArrByRef" "[0]" or "[0,1,..]". Therefore, could you help to comment whether it is technically possible for mORMot to serialize "Arr" property correctly here ? ![]()
In your code:
function TPOJO.GetArr: TIntegerDynArray; begin Result := Copy(FArr); end;Are you sure what doing a copy of an array inside the property getter is a good idea? Why do so?
Providing a copy of a primitive array serves me very well when the original primitive array is held by a object ("manual-Box" since there is no auto-Box) and needed by other objects who go on to modify the array according to their own needs.
Dear ab, in the sample code below which tries to serialize a plain object and then de-serialize, it seems that certain property ("Arr" as in the sample) makes the generated json invalid (see the value of "Arr", which should be the same as the value of "ArrByRef") to parse back (see the difference of "ArrByRef" in the above and below block, which should be the same. Apparently, JsonToObject detects invalid json content and aborts parsing). If that "Arr" property is removed from the published section, the generated json is valid and can be parsed back. Could you help to comment whether this is a bug or by design ?
Many thanks !
https://gist.github.com/anonymous/333f077797aecdc0ee65253e8ad819baDear mpv, I am trying to access the "mainForm" exported by the host SpiderMonkey45Binding.exe from the plugin mathModule.dll. The reason behind my attempt is that if this is indeed possible, this looks like a good way of manipulating "central business service" exported by the host .exe from plugin .dll. Complex additional manipulation referencing the "central business service" could be compiled into plugin .dll without changing the host .exe.......
http://www.delphigroups.info/2/f9/304620.html
http://www.delphigroups.info/2/41/523159.html
http://www.delphigroups.info/2/aa/523141.htmlAccording to the links above, the prerequisite is that (1) everything which needs to be referred to in both .exe and .dll should be put into a package, and (2) both .exe and .dll should be built with Runtime-package enabled.
Furethermore, according to the FastMM FAQ, these FullDebugMode, UseRuntimePackages, ShareMM, ShareMMIfLibrary, AttemptToUseSharedMM defines are enabled. Also, according to one post on the FastMM forum, FastMM is put into its own package and referred to in all other packages, .exe, and .dll projects.
However, the problem now is that when testing in Win7 x64 VM (1 core, 2GB mem), memory leaks are reported when main form is shut down, which should be related to the unload order of runtime packages.
Note that there are no memory leaks reported in Win10 x61 OS (4 core, 32GB mem).
The modified files are upload as mORMot.20161108151029.zip, and the steps to reproduce the memory leaks are below:
(*
In Win7 x64 VM (1 core, 2GB mem),
use D:\mORMot\SyNode\Samples\02 - Bindings\SpiderMonkey45Binding_D24.bat to run the main form,
paste the javascript below,
run any of them,
close main form directly without closing Form1 or Form2,
there is memory leak:
const mathModule = require('../../Samples/01 - Dll Modules/math-module');
mathModule.work_tform1_1(mainForm)
mathModule.work_tform1_2(mainForm)
mathModule.work_tform1_3(mainForm)
mathModule.work_tform2_1(mainForm)
mathModule.work_tform2_2(mainForm)
mathModule.work_tform2_3(mainForm)
--------------------------------2016/11/8 15:04:04--------------------------------
This application has leaked memory. The small block leaks are (excluding expected leaks registered by pointer):
13 - 20 bytes: AnsiString x 2
21 - 36 bytes: AnsiString x 1, Unknown x 2
37 - 52 bytes: TSMDebugger x 1
69 - 84 bytes: TCrtSocket x 1
85 - 100 bytes: TSMRemoteDebuggerThread x 1
149 - 164 bytes: TObjectListLocked x 2
245 - 276 bytes: TRawUTF8ListHashedLocked x 2
*)ShareMem in the uses clause and replacement BorlndMM.dll in the path does not help.
FastMM's NeverUninstall combined with disabling FastMM's reporting memory leaks completely makes debugging memory leaks much more difficult.
Could you help to comment whether it is possible to prevent the memory leaks when using runtime packages, by making FastMM to unload last or in some other ways ? ![]()
Dear mpv, I am trying to access the "mainForm" exported by the host SpiderMonkey45Binding.exe from the plugin mathModule.dll. The reason behind my attempt is that if this is indeed possible, this looks like a good way of manipulating "central business service" exported by the host .exe from plugin .dll. Complex additional manipulation referencing the "central business service" could be compiled into plugin .dll without changing the host .exe.
Modified files are uploaded onto gist : uMathModule.pas belongs to "01 - Dll Modules\math-module\src\" and the rest six files belong to "02 - Bindings".
However, I have met several problems in trying with the above files:
(1) The plugin methods have to reference the business class. That is to say, ufrmSM45Demo has to be used in uMathModule.pas. But then the type incompatibility issue arises. For example, in function try_work_tform1_1, InstRec.instance is TfrmSM45Demo fails. I wonder whether it is safe or dangerous to hard cast as done in the code ?
(2) Further attempts try to access the ability of the mainForm , which is obtained by hard-cast, to evaluate JavaScript. In other words:
(a) host evaluates JavaScript string, where host exports mainForm
(b) in the JavaScript string, host calls plugin JS method with the mainForm exported
(c) in the plugin native method, the mainForm obtained by hard-cast is asked to evaluate JavaScript
However, the attempts all fail: function try_work_tform1_2 does not completes 100% correct, and function try_work_tform2_2 gives access violation; function try_work_tform1_3 and try_work_tform2_3 both give "A component named Form? already exists."
(3) The above problems are the same no matter FastMM4 or ShareMem is used for host or plugin or both.
Could you help to check the code and comment the reason ? ![]()
Furthermore, if I am taking the wrong way, could you help to comment the correct solution to access the "central business service" of host .exe within the plugin dll ? ![]()
About rooting in SpiderMonkey:
Everything (strings, objects, integers, result of CreateJSInstanceObjForSimpleRTTI call, etc.) inside SpiderMonkey represented as JSValue - a 64 bit structure. SpiderMonkey engine create a reference counter for each structure, and when reference counter === 0 will free a JSValue (during garbage collection circle) - this is the same as for Strings or dynamic array in Delphi, but in Delphi string will be destroyed just after reference counted became zero, in JavaScript - when garbage collection circle is executed.
jsval := CreateJSInstanceObjForSimpleRTTI(....); // 0) create a new JSValue inside Engine try rootObj := cx.NewRooted*(jsval); // 1) When we want to use a JSValue from outside the SpiderMonkey Engine (from Delphi code) we must say Engine to increase a reference counter // do something with JSValue vp.rval := rootObj.ptr.ToJSValue; //see notes* below about ptr finally cx.FreeRooted*(rootObj); // 2) When we do not need a reference to JSValue from Delphy, we must say Engine to decrease a reference counter bycx.FreeRooted is not actually a Free operation - just a dec to the internal reference counter.
Notes* about ptr
Between events 1) and 2) SpiderMonkey can in any moment run a internal memory optimization procedure and move the actual in-memory position of the JSValue to another place. This is why we must not memorize a direct pointer to a JSValue (in our case jsval created on the step 0), but use a rootObj.ptr.Notes about root/unroot order
Important thing about rooting/unrooting order - inside the SM45 rooting is a stack, so we must unroot things in order opposite we root it. In C++ this solved by automatically variable scoping. In Delphi even if we use a Variants as AB did in SM24 there is no guaranty of variant uninit order, so we must manually and carefully root/unroot.
I appreciate your efforts and helpful comments very much !
AFAIR the Delphi Web Script JavaScript back-end has been released again under Open Source.
We may include it to compile directly from high-level pascal to JavaScript, then execute it in SyNode... and its full JIT performance... may be a perspective of some interest...
Sounds really interesting ! Looking forward to the proto type ![]()
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 ![]()
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 lodash2) 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 !
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 !! ![]()
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 !
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 lodash2) 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 ! ![]()
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 !
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 ?
Many thanks for your helpful comments ! ![]()
Latest mORMot for FPC here: https://github.com/newpascal-ccr/mORMot/
Dear AOG, does this mean that we can download zipped archive from that github link and replace the c:\NewPascal\ccr\mORMot ?
If yes, can you comment whether you can keep mORMot in your github link above always sync with http://synopse.info/fossil/timeline ?
Many thanks !
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 ? ![]()
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 !!
![]()
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 !
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 !!
Thank you very much for your helpful comments ! ![]()
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.
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;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;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 .
???
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... ![]()
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. ![]()
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;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 .)
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 ? ![]()
Yes, we also reproduce a FreeMem error in case FastMM FullDebugMode enabled. At the moment we do not know the reason
Thank you very much for your efforts in researching ! I hope this issue could be resolved in future, because FullDebugMode is rather important for FastMM for finding memory leaks.
We reproduce a issue - this is because FastMM FullDebugMode
{$IFDEF DEBUG} {$define FullDebugMode} {$ENDIF}is enabled in your FastMM4Options.inc
Without this option enabled everything is fine. At the moment we do not know the reason. Looks like a conflict between FastMM in FullDebug mode and a nspr memory manager (SpiderMonkey use a memory manager from Netscape Portable Run-time nspr4.dll)
Thank you very much for your efforts in researching ! Because FullDebugMode is rather important for FastMM for finding memory leaks, I hope this incompatibility between FastMM w/ FullDebugMode and SpiderMonkey could be removed in future. ![]()
The content of FastMM4Options.inc used is
Using mORMot '1.18.3006', if compiled with Delphi 7 with FastMM FullDebugMode enabled, TestSynSM gives various errors when run second time, i.e., when js/jslint.js already exists. Could you help to check ?
The screen shots can be seen at imgur.
The content of TestSynSM log is
https://gist.github.com/anonymous/fce89 … 71ec1bed94
The content of TestSynSM_MemoryManager_EventLog.txt is
https://gist.github.com/anonymous/0155c … 7f27be26e2
The content of FastMM4Options.inc used is
https://gist.github.com/anonymous/5ebb8 … f1240e28ea
PS: If compiled with Delphi 7 without FastMM FullDebugMode enabled, TestSynSM runs fine no matter how many times.
PS: If compiled with Delphi XE, XE8 or Berlin, TestSynSM runs fine no matter how many times.
I'll check tomorrow
Can you comment whether you can at least reproduce this issue ?
I fix a warning - actually this is a bug in our test. See [a30165310f]
I'm totally not understand why it raised only if debugger is off. AFAIR autotest do some hack for errors handling in debuger mode, but can't find where
Thank you for fixing the code and committing the change to trunk!
About the exception when debugger is off: do you mean ab might know all the details ? ![]()
Thank you very much for your efforts !
Can you help to comment : (1) Why the exception only is raised for certain circumstances (for D7, outside IDE; for latest versions, without debugger) ? (2) Is this test-failing expected ?