mORMot and Open Source friends
Check-in [8610718386]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:mORMot framework now implements Client-Server service implementation using regular Delphi interfaces (and a JSON-RPC like protocol)
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 86107183864f5783ac6557305c0eb32fa73bd1a4
User & Date: User 2012-02-26 18:29:17
Context
2012-02-28
08:39
added sample about logging in a library check-in: 103f3e926f user: G018869 tags: trunk
2012-02-26
18:29
mORMot framework now implements Client-Server service implementation using regular Delphi interfaces (and a JSON-RPC like protocol) check-in: 8610718386 user: User tags: trunk
00:38
small fixes check-in: 7fc613f8b2 user: User tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/SQLite3Commons.pas.

438
439
440
441
442
443
444


445
446
447
448
449
450
451
....
3981
3982
3983
3984
3985
3986
3987

3988
3989
3990
3991
3992
3993
3994
....
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
....
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
....
8260
8261
8262
8263
8264
8265
8266
8267
8268
8269
8270
8271
8272
8273
8274
.....
21378
21379
21380
21381
21382
21383
21384
21385
21386
21387
21388
21389
21390
21391
21392
.....
21581
21582
21583
21584
21585
21586
21587
21588
21589


21590
21591
21592
21593
21594
21595
21596
.....
21629
21630
21631
21632
21633
21634
21635
21636
21637
21638
21639
21640
21641
21642
21643
21644
21645
21646
21647
21648
.....
21733
21734
21735
21736
21737
21738
21739
21740
21741
21742
21743
21744
21745
21746
21747
.....
21766
21767
21768
21769
21770
21771
21772
21773
21774
21775
21776
21777
21778
21779
21780
.....
21926
21927
21928
21929
21930
21931
21932

21933
21934
21935
21936
21937
21938
21939
.....
22012
22013
22014
22015
22016
22017
22018
22019
22020
22021
22022
22023
22024
22025
22026
.....
22050
22051
22052
22053
22054
22055
22056
22057
22058
22059
22060
22061
22062
22063
22064
22065
22066
22067
22068
22069
22070
22071
22072
22073
22074
22075
22076
22077
22078
22079
22080
22081
.....
22088
22089
22090
22091
22092
22093
22094
22095
22096
22097
22098
22099
22100
22101
22102
.....
22143
22144
22145
22146
22147
22148
22149
22150
22151
22152
22153
22154
22155
22156
22157
.....
22194
22195
22196
22197
22198
22199
22200
22201
22202
22203
22204
22205
22206
22207
22208
.....
22265
22266
22267
22268
22269
22270
22271
22272
22273
22274
22275
22276

22277
22278
22279
22280
22281
22282
22283
22284

22285
22286
22287
22288
22289
22290
22291


22292
22293
22294
22295
22296
22297
22298
.....
22329
22330
22331
22332
22333
22334
22335
22336
22337
22338
22339
22340
22341
22342
22343
22344
22345

22346

22347
22348
22349
22350
22351
22352
22353
22354
22355
22356
22357


22358
22359
22360
22361
22362
22363
22364
22365
22366
22367
22368
22369
22370
22371
22372
22373
22374
22375
22376
22377
22378
.....
22397
22398
22399
22400
22401
22402
22403
22404

22405














22406
22407
22408
22409
22410
22411

































































































22412


22413
22414

22415
22416

22417
22418
22419
22420
22421
22422
22423
22424
22425
22426



22427


22428

22429
22430
22431
22432
22433
22434
22435
22436
22437
22438
22439
22440
22441
22442

22443
22444
22445
22446
22447
22448
22449
22450
22451
22452
22453
22454
22455
22456
22457
22458
22459
      by TSQLRestServerStaticInMemory.SaveToBinary)
    - fixed issue with TAuthSession.IDCardinal=0 after 76 connections
    - fixed issue in SetInt64Prop() with a setter method
    - fixed potential issue in TSQLTable.SearchValue in case of invalid Client
      supplied parameter (now checks TSQLRest class type)

  Version 1.16


    - added dedicated Exception classes (EORMException, EParsingException,
      ESecurityException, ECommunicationException, EBusinessLayerException,
      EServiceException) all inheriting from SynCommons.ESynException
    - added a generic JSON error message mechanism within the framework
      (including error code as integer and text, with custom error messages
      in RecordCanBeUpdated method and also in TSQLRestServerCallBackParams)
    - the TSQLRestServerCallBack method prototype has been modified to supply
................................................................................
  // enhanced types handled by JSONToObject/ObjectToJSON functions (smvObject)
  // or TDynArray.LoadFromJSON / TTextWriter.AddDynArrayJSON methods (smvDynArray)
  TServiceMethodValueType = (
    smvNone,
    smvSelf,
    smvBoolean,
    smvInteger,

    smvInt64,
    smvDouble,
    smvDateTime,
    smvCurrency,
    smvRawUTF8,
    smvString,
    smvWideString,
................................................................................
  protected
    fInterfaceTypeInfo: PTypeInfo;
    fInterfaceIID: TGUID;
    fInterfaceURI: RawUTF8;
    fInterfaceMangledURI: RawUTF8;
    fInstanceCreation: TServiceInstanceImplementation;
    fRest: TSQLRest;
    fMethodsCount: integer;
    fMethods: TServiceMethodDynArray;
    fSharedInstance: TInterfacedObject;
  public
    /// initialize the service provider parameters
    // - it will check and retrieve all methods of the supplied interface,
    // and prepare all internal structures for its serialized execution
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
................................................................................
    // - on success, aResp shall contain a serialized JSON object with one
    // nested result property, which may be a JSON array, containing the
    // method main result at first, then all "out" parameters values - for
    // instance, ExecuteMethod(..,'[1,2]') over ICalculator.Add will return:
    // $ {"result":[3],"id":0}
    // the returned "id" number is the Instance identifier to be used for any later
    // sicClientDriven remote call - or just 0 in case of sicSingle or sicShared
    function ExecuteMethod(aSession: cardinal; aMethodIndex, aInstanceID: Integer;
      aParamsJSONArray: PUTF8Char; var aResp, aHead, aErrorMsg: RawUTF8): cardinal;
  end;

  /// a service provider implemented on the client side
  TServiceFactoryClient = class(TServiceFactory)
  protected
    fClient: TSQLRestClientURI;
    fRemoteClassName: RawUTF8;
    fFakeVTable: array of pointer;
    fFakeStub: PByteArray;
    function CallClient(const aMethod: RawUTF8; const aParams: RawUTF8='';
      aResult: PRawUTF8=nil; aClientDrivenID: PCardinal=nil): boolean;
  public
    /// initialize the service provider parameters
    // - it will check and retrieve all methods of the supplied interface,
    // and prepare all internal structures for its serialized execution
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation);
    /// finalize the service provider used structures
................................................................................
begin
  if self=nil then begin
    result := -1;
    exit;
  end;
  if (fResults<>nil) and (aID>0) then begin
    // search aID as UTF-8 in fIDColumn[] or fResults[]
    ID := {$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(aID);
    if Assigned(fIDColumn) then begin // get hidden ID column UTF-8 content
      for result := 1 to RowCount do
        if StrComp(fIDColumn[result],pointer(ID))=0 then
          exit;
    end else begin
      FID := FieldIndexID;  // get ID column field index
      if FID>=0 then begin
................................................................................
      fAccessRights := User.GroupRights.SQLAccessRights;
      if aServer.fSessionCounter>=cardinal(maxInt) then
        aServer.fSessionCounter := 10 else
        if aServer.fSessionCounter=76 then // avoid fIDCardinal=0
          aServer.fSessionCounter := 78 else
          inc(aServer.fSessionCounter);
      fIDCardinal := aServer.fSessionCounter xor 77;
      fID := Int64ToUtf8(fIDCardinal);
      fPrivateKey := SHA256(NowToString+fID);
      fPrivateSalt := fID+'+'+fPrivateKey;
      fPrivateSaltHash :=
        crc32(crc32(0,pointer(fPrivateSalt),length(fPrivateSalt)),
          pointer(User.PasswordHashHexa),length(User.PasswordHashHexa));
      {$ifdef WITHLOG}
      SQLite3Log.Family.SynLog.Log(sllUserAuth,
................................................................................

function TypeInfoToMethodValueType(P: PTypeInfo): TServiceMethodValueType;
begin
  result := smvNone;
  if P<>nil then
  case P^.Kind of
  tkInteger:
    if P^.OrdType in [otSLong, otULong] then
      result := smvInteger;


  tkInt64:
    result := smvInt64;
  {$ifdef FPC}
  tkBool:
    result := smvBoolean;
  {$else}
  tkEnumeration:
................................................................................
const
  REGEAX = -1;
  REGEDX = -2;
  REGECX = -3;
  PTRSIZ = sizeof(Pointer);

  CONST_ARGS_TO_VAR: array[TServiceMethodValueType] of TServiceMethodValueVar = (
    smvvNone, smvvSelf, smvv64, smvv64, smvv64, smvv64, smvv64, smvv64,
    smvvRawUTF8, smvvString, smvvWideString, smvvObject, smvvDynArray);

  CONST_ARGS_IN_STACK_SIZE: array[TServiceMethodValueType] of Cardinal = (
     0,  PTRSIZ,  4,       4,       8,     8,      8,        8,
 // None, Self, Boolean, Integer, Int64, Double, DateTime, Currency,
     PTRSIZ,  PTRSIZ, PTRSIZ,    PTRSIZ,  PTRSIZ);
 // RawUTF8, String, WideString, Object, DynArray

  CONST_ARGS_RESULT_BY_REF: TServiceMethodValueTypes = [
    smvRawUTF8, smvString, smvWideString, smvDynArray];

  CONST_RESULT_NAME: string[6] = 'Result';
................................................................................
      ParamName := @CONST_RESULT_NAME;
      ValueDirection := smdResult;
      TypeName := PS;
      PS := @PS^[ord(PS^[0])+1];
      TypeInfo := PP^^;
      inc(PP);
      ValueType := TypeInfoToMethodValueType(TypeInfo);
      if ValueType=smvNone then
        raise EServiceException.CreateFmt('%s.%s method has unexpected result type %s',
          [fInterfaceTypeInfo^.ShortName,URI,TypeName^]);
    end;
    {$ifdef ISDELPHIXE2}
    inc(PW); // skip attributes
    {$endif}
  end;
................................................................................
        OffsetInStack := ArgsSize;
        inc(ArgsSize,SizeInStack);
      end else begin
        OffsetInStack := reg;
        dec(reg);
      end;
    end;
    // pascal/register convention are passed left-to-right -> reverse
    offs := ArgsSize;
    for a := 0 to high(Args) do
    with Args[a] do
      if OffsetInStack>=0 then begin
        dec(offs,SizeInStack);
        OffsetInStack := offs;
      end;
................................................................................
    for i := 0 to fList.Count-1 do begin
      result := fList.List[i];
      if IsEqualGUID(result.InterfaceIID,aGUID) then
        exit;
    end;
  result := nil;
end;


{ TServiceFactoryServer }

constructor TServiceFactoryServer.Create(aRest: TSQLRest; aInterface: PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  aImplementationClass: TInterfacedObjectClass; aTimeOutSec: cardinal);
begin
................................................................................
      if (LastAccess<Inst.LastAccess) or
         (LastAccess>TimeOutTimeStamp) then begin
        InstanceID := 0; // mark this entry is empty
        FreeAndNil(Instance);
      end;
    // retrieve or initialize the instance
    if Inst.InstanceID=0 then begin
      if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
        exit;
      // initialize the new instance
      inc(fInstanceCurrentID);
      Inst.InstanceID := fInstanceCurrentID;
      for i := 0 to fInstancesCount-1 do
        if fInstances[i].InstanceID=0 then begin
          Inst.Instance := fImplementationClass.Create; // found an empty entry
................................................................................
          break;
        end;
  finally
    LeaveCriticalSection(fInstanceLock);
  end;
end;

function TServiceFactoryServer.ExecuteMethod(aSession: cardinal;
  aMethodIndex, aInstanceID: Integer; aParamsJSONArray: PUTF8Char;
  var aResp, aHead, aErrorMsg: RawUTF8): cardinal;
var Inst: TServiceFactoryServerInstance;
    WR: TTextWriter;
    entry: PInterfaceEntry;
begin
  result := 400;
  // 1. initialize Inst.Instance and Inst.InstanceID
  Inst.InstanceID := 0;
  Inst.Instance := nil;
  case InstanceCreation of
    sicSingle:
      if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
        exit else
        Inst.Instance := fImplementationClass.Create;
    sicShared:
      if cardinal(aMethodIndex)>=cardinal(fMethodsCount) then
        exit else
        Inst.Instance := fSharedInstance;
    sicClientDriven: begin
      Inst.InstanceID := aInstanceID;
      if ClientDrivenRetrieve(Inst,aMethodIndex) then begin
        result := 200;
        exit; // {"method":"free", "params":[], "id":1234}
................................................................................
    exit;
  end;
  // 2. call method implementation
  try
    entry := Inst.Instance.GetInterfaceEntry(fInterfaceIID);
    if entry=nil then
      exit;
    WR := TTextWriter.CreateOwnedStream;
    try
      // root/calculator {"method":"add","params":[1,2]} -> {"result":[3],"id":0}
      try
        WR.AddShort('{"result":[');
        if not fMethods[aMethodIndex].InternalExecute(
           Inst.Instance,entry,aParamsJSONArray,WR) then
          exit; // wrong request
................................................................................
    Strings: TStringDynArray;
    WideStrings: TWideStringDynArray;
    Objects: array of TObject;
    DynArrays: array of TDynArrayFake;
    Value, method: pointer;
    i,a: integer;
    wasString, valid: boolean;
    EndOfObject: AnsiChar;
    Val: PUTF8Char;
    cla: TClass;
    obj: TJSONObject;
    r: packed record EAX, EDX, ECX, EAX2, EDX2: integer; end;
begin
  result := false;
  StackSize := ArgsSize;
................................................................................
          Par := Wrapper.LoadFromJSON(Par);
          if Par=nil then
            exit;
        end;
      end;
      smvBoolean..smvWideString:
      if ValueDirection in [smdConst,smdVar] then begin
        Val := GetJSONField(Par,Par,@wasString,@EndOfObject);
        if (Val=nil) or (wasString<>ValueIsString) then
          exit;
        case ValueType of
        smvBoolean..smvInt64:  Int64s[IndexVar] := GetInt64(Val);
        smvDouble,smvDateTime: PDouble(@Int64s[IndexVar])^ := GetExtended(Val);
        smvCurrency:   Int64s[IndexVar] := StrToCurr64(Val);
        smvRawUTF8:    RawUTF8s[IndexVar] := Val;
................................................................................
    for a:= 0 to high(Args) do
    with Args[a] do
    if ValueDirection in [smdVar,smdOut,smdResult] then begin
      if ValueIsString then
        Res.Add('"');
      if (ValueVar=smvv64) and (ValueDirection=smdResult) then begin
        case ValueType of // ordinal/real result values from CPU/FPU registers
        smvDouble, smvDateTime: Res.Add(LoadDouble);
        smvCurrency:            Res.Add(LoadCurrency);
        smvBoolean:             Res.Add(PByte(@r.EAX2)^);
        smvInteger:             Res.Add(r.EAX2);
        smvInt64:               Res.Add(PInt64(@r.EAX2)^);

        else raise EServiceException.CreateFmt('Invalid result type %d',[ord(ValueType)]);
        end;
      end else
      case ValueType of
      smvObject:     Res.WriteObject(Objects[IndexVar],False,False,true);
      smvDynArray:   Res.AddDynArrayJSON(DynArrays[IndexVar].Wrapper);
      smvBoolean:    Res.Add(PByte(@Int64s[IndexVar])^);
      smvInteger:    Res.Add(PInteger(@Int64s[IndexVar])^);

      smvInt64:      Res.Add(Int64s[IndexVar]);
      smvDouble,
      smvDateTime:   Res.Add(PDouble(@Int64s[IndexVar])^);
      smvCurrency:   Res.AddCurr64(@Int64s[IndexVar]);
      smvRawUTF8:    Res.AddJSONEscape(pointer(RawUTF8s[IndexVar]));
      smvString:     Res.AddJSONEscapeString(Strings[IndexVar]);
      smvWideString: Res.AddJSONEscapeW(pointer(WideStrings[IndexVar]));


      end;
      if ValueIsString then
        Res.Add('"',',') else
        Res.Add(',');
    end;
    Res.CancelLastComma;
    Result := true;
................................................................................
end;


{ TServiceFactoryClient }

type
  /// map the stack memory layout at TInterfacedObjectFake.FakeCall()
  TFakeCall = packed record
    EDX, ECX, MethodIndex, EBP, Ret2: integer;
    Args: array[word] of byte;
  end;

  /// instances of this class will emulate a given interface
  TInterfacedObjectFake = class(TInterfacedObject)
  protected
    fFactory: TServiceFactoryClient;
    fVTable: PPointerArray;

    function FakeCall(const aCall: TFakeCall): Int64;

    function SelfFromFake: TInterfacedObjectFake;
    function FakeQueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function Fake_AddRef: Integer; stdcall;
    function Fake_Release: Integer; stdcall;
  public
    /// create an instance, using the specified interface
    constructor Create(aFactory: TServiceFactoryClient);
    /// release the remote server instance (in sicClientDriven mode);
    destructor Destroy; override;
    /// the associated interface factory
    property Factory: TServiceFactoryClient read fFactory;


  end;

constructor TInterfacedObjectFake.Create(aFactory: TServiceFactoryClient);
begin
  inherited Create;
  fFactory := aFactory;
  fVTable := Pointer(aFactory.fFakeVTable);
end;

destructor TInterfacedObjectFake.Destroy;
begin
  if (fFactory<>nil) and (fFactory.InstanceCreation=sicClientDriven) then
  try
    fFactory.CallClient('free'); // release server instance
  except
    ; // ignore any exception here
  end;
  inherited;
end;

function TInterfacedObjectFake.Fake_AddRef: Integer;
................................................................................
end;

function TInterfacedObjectFake.SelfFromFake: TInterfacedObjectFake;
asm
  sub eax,TInterfacedObjectFake.fVTable
end;

function TInterfacedObjectFake.FakeCall(const aCall: TFakeCall): Int64;

begin














  self := SelfFromFake;
  assert(fFactory.ClassNameIs('TServiceFactoryClient'));
  with aCall do  { TODO: remote RESTful server call using JSON }
  case MethodIndex of
  0: result := EDX+ECX;
  1: result := PInt64(@Args[8])^*PInt64(@Args[0])^;

































































































  else result := 0;


  end;
end;


function TServiceFactoryClient.CallClient(const aMethod, aParams: RawUTF8;

  aResult: PRawUTF8; aClientDrivenID: Pcardinal): boolean;
var sent,resp,head: RawUTF8;
    Values: TPUtf8CharDynArray;
begin
  Result := false;
  if Self=nil then
    exit;
  if fClient=nil then
    fClient := fRest as TSQLRestClientURI;
  sent := '{"method":"'+aMethod+'","params":['+aParams+']}';



  if fClient.URI(fClient.Model.Root+'/'+fInterfaceURI,'POST',@resp,@head,@sent).Lo<>200 then


    exit;

  JSONDecode(resp,['RESULT','ID'],Values,True);
  if (Values[0]=nil) or (Values[1]=nil) then
    Exit;
  if aResult<>nil then
    aResult^ := Values[0];
  if aClientDrivenID<>nil then
    aClientDrivenID^ := GetCardinal(Values[1]);
  result := true;
end;

constructor TServiceFactoryClient.Create(aRest: TSQLRest;
  aInterface: PTypeInfo; aInstanceCreation: TServiceInstanceImplementation);
var i, siz: integer;
    P: PCardinal;

begin
  // extract RTTI from the interface
  if not aRest.InheritsFrom(TSQLRestClientURI) then
    EServiceException.CreateFmt('%s interface needs a Client connection',
      [aInterface^.ShortName]);
  inherited Create(aRest,aInterface,aInstanceCreation);
  // check if this interface is supported on the server
  if not CallClient('ClassName','',@fRemoteClassName) then
    raise EServiceException.CreateFmt('%s interface not supported by server',
      [fInterfaceURI]);
  // create the fake interface
  SetLength(fFakeVTable,fMethodsCount+3);
  fFakeVTable[0] := @TInterfacedObjectFake.FakeQueryInterface;
  fFakeVTable[1] := @TInterfacedObjectFake.Fake_AddRef;
  fFakeVTable[2] := @TInterfacedObjectFake.Fake_Release;
  siz := (((fMethodsCount*24) shr 12)+1) shl 12; // 4 KB granularity
  fFakeStub := VirtualAlloc(nil,siz,MEM_COMMIT,PAGE_EXECUTE_READWRITE);






>
>







 







>







 







|







 







|










|
|







 







|







 







|







 







|
|
>
>







 







|



|
|







 







|







 







|







 







>







 







|







 







|
<











|



|







 







|







 







<







 







|







 







|
|
|
|
|
>




<
<


>

<
|




>
>







 







|
|
|





<

>
|
>











>
>













|







 







|
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>

<
<
<
|
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
|
|
>

|
>




|




|
>
>
>
|
>
>

>


|











>







|
|
|







438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
....
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
....
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
....
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
....
8263
8264
8265
8266
8267
8268
8269
8270
8271
8272
8273
8274
8275
8276
8277
.....
21381
21382
21383
21384
21385
21386
21387
21388
21389
21390
21391
21392
21393
21394
21395
.....
21584
21585
21586
21587
21588
21589
21590
21591
21592
21593
21594
21595
21596
21597
21598
21599
21600
21601
.....
21634
21635
21636
21637
21638
21639
21640
21641
21642
21643
21644
21645
21646
21647
21648
21649
21650
21651
21652
21653
.....
21738
21739
21740
21741
21742
21743
21744
21745
21746
21747
21748
21749
21750
21751
21752
.....
21771
21772
21773
21774
21775
21776
21777
21778
21779
21780
21781
21782
21783
21784
21785
.....
21931
21932
21933
21934
21935
21936
21937
21938
21939
21940
21941
21942
21943
21944
21945
.....
22018
22019
22020
22021
22022
22023
22024
22025
22026
22027
22028
22029
22030
22031
22032
.....
22056
22057
22058
22059
22060
22061
22062
22063

22064
22065
22066
22067
22068
22069
22070
22071
22072
22073
22074
22075
22076
22077
22078
22079
22080
22081
22082
22083
22084
22085
22086
.....
22093
22094
22095
22096
22097
22098
22099
22100
22101
22102
22103
22104
22105
22106
22107
.....
22148
22149
22150
22151
22152
22153
22154

22155
22156
22157
22158
22159
22160
22161
.....
22198
22199
22200
22201
22202
22203
22204
22205
22206
22207
22208
22209
22210
22211
22212
.....
22269
22270
22271
22272
22273
22274
22275
22276
22277
22278
22279
22280
22281
22282
22283
22284
22285


22286
22287
22288
22289

22290
22291
22292
22293
22294
22295
22296
22297
22298
22299
22300
22301
22302
22303
.....
22334
22335
22336
22337
22338
22339
22340
22341
22342
22343
22344
22345
22346
22347
22348

22349
22350
22351
22352
22353
22354
22355
22356
22357
22358
22359
22360
22361
22362
22363
22364
22365
22366
22367
22368
22369
22370
22371
22372
22373
22374
22375
22376
22377
22378
22379
22380
22381
22382
22383
22384
22385
22386
.....
22405
22406
22407
22408
22409
22410
22411
22412
22413
22414
22415
22416
22417
22418
22419
22420
22421
22422
22423
22424
22425
22426
22427
22428
22429



22430

22431
22432
22433
22434
22435
22436
22437
22438
22439
22440
22441
22442
22443
22444
22445
22446
22447
22448
22449
22450
22451
22452
22453
22454
22455
22456
22457
22458
22459
22460
22461
22462
22463
22464
22465
22466
22467
22468
22469
22470
22471
22472
22473
22474
22475
22476
22477
22478
22479
22480
22481
22482
22483
22484
22485
22486
22487
22488
22489
22490
22491
22492
22493
22494
22495
22496
22497
22498
22499
22500
22501
22502
22503
22504
22505
22506
22507
22508
22509
22510
22511
22512
22513
22514
22515
22516
22517
22518
22519
22520
22521
22522
22523
22524
22525
22526
22527
22528
22529
22530
22531
22532
22533
22534
22535
22536
22537
22538
22539
22540
22541
22542
22543
22544
22545
22546
22547
22548
22549
22550
22551
22552
22553
22554
22555
22556
22557
22558
22559
22560
22561
22562
22563
22564
22565
22566
22567
22568
22569
22570
22571
22572
22573
22574
22575
22576
22577
22578
22579
22580
22581
22582
22583
22584
22585
22586
      by TSQLRestServerStaticInMemory.SaveToBinary)
    - fixed issue with TAuthSession.IDCardinal=0 after 76 connections
    - fixed issue in SetInt64Prop() with a setter method
    - fixed potential issue in TSQLTable.SearchValue in case of invalid Client
      supplied parameter (now checks TSQLRest class type)

  Version 1.16
    - mORMot framework now implements Client-Server service implementation
      using regular Delphi interfaces (and a JSON-RPC like protocol) 
    - added dedicated Exception classes (EORMException, EParsingException,
      ESecurityException, ECommunicationException, EBusinessLayerException,
      EServiceException) all inheriting from SynCommons.ESynException
    - added a generic JSON error message mechanism within the framework
      (including error code as integer and text, with custom error messages
      in RecordCanBeUpdated method and also in TSQLRestServerCallBackParams)
    - the TSQLRestServerCallBack method prototype has been modified to supply
................................................................................
  // enhanced types handled by JSONToObject/ObjectToJSON functions (smvObject)
  // or TDynArray.LoadFromJSON / TTextWriter.AddDynArrayJSON methods (smvDynArray)
  TServiceMethodValueType = (
    smvNone,
    smvSelf,
    smvBoolean,
    smvInteger,
    smvCardinal,
    smvInt64,
    smvDouble,
    smvDateTime,
    smvCurrency,
    smvRawUTF8,
    smvString,
    smvWideString,
................................................................................
  protected
    fInterfaceTypeInfo: PTypeInfo;
    fInterfaceIID: TGUID;
    fInterfaceURI: RawUTF8;
    fInterfaceMangledURI: RawUTF8;
    fInstanceCreation: TServiceInstanceImplementation;
    fRest: TSQLRest;
    fMethodsCount: cardinal;
    fMethods: TServiceMethodDynArray;
    fSharedInstance: TInterfacedObject;
  public
    /// initialize the service provider parameters
    // - it will check and retrieve all methods of the supplied interface,
    // and prepare all internal structures for its serialized execution
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
................................................................................
    // - on success, aResp shall contain a serialized JSON object with one
    // nested result property, which may be a JSON array, containing the
    // method main result at first, then all "out" parameters values - for
    // instance, ExecuteMethod(..,'[1,2]') over ICalculator.Add will return:
    // $ {"result":[3],"id":0}
    // the returned "id" number is the Instance identifier to be used for any later
    // sicClientDriven remote call - or just 0 in case of sicSingle or sicShared
    function ExecuteMethod(aSession, aMethodIndex, aInstanceID: cardinal;
      aParamsJSONArray: PUTF8Char; var aResp, aHead, aErrorMsg: RawUTF8): cardinal;
  end;

  /// a service provider implemented on the client side
  TServiceFactoryClient = class(TServiceFactory)
  protected
    fClient: TSQLRestClientURI;
    fRemoteClassName: RawUTF8;
    fFakeVTable: array of pointer;
    fFakeStub: PByteArray;
    function CallClient(const aMethod: RawUTF8; aErrorMsg: PRawUTF8=nil;
      const aParams: RawUTF8=''; aResult: PRawUTF8=nil; aClientDrivenID: PCardinal=nil): boolean;
  public
    /// initialize the service provider parameters
    // - it will check and retrieve all methods of the supplied interface,
    // and prepare all internal structures for its serialized execution
    constructor Create(aRest: TSQLRest; aInterface: PTypeInfo;
      aInstanceCreation: TServiceInstanceImplementation);
    /// finalize the service provider used structures
................................................................................
begin
  if self=nil then begin
    result := -1;
    exit;
  end;
  if (fResults<>nil) and (aID>0) then begin
    // search aID as UTF-8 in fIDColumn[] or fResults[]
    ID := {$ifndef ENHANCEDRTL}UInt32ToUtf8{$else}IntToStr{$endif}(aID);
    if Assigned(fIDColumn) then begin // get hidden ID column UTF-8 content
      for result := 1 to RowCount do
        if StrComp(fIDColumn[result],pointer(ID))=0 then
          exit;
    end else begin
      FID := FieldIndexID;  // get ID column field index
      if FID>=0 then begin
................................................................................
      fAccessRights := User.GroupRights.SQLAccessRights;
      if aServer.fSessionCounter>=cardinal(maxInt) then
        aServer.fSessionCounter := 10 else
        if aServer.fSessionCounter=76 then // avoid fIDCardinal=0
          aServer.fSessionCounter := 78 else
          inc(aServer.fSessionCounter);
      fIDCardinal := aServer.fSessionCounter xor 77;
      fID := UInt32ToUtf8(fIDCardinal);
      fPrivateKey := SHA256(NowToString+fID);
      fPrivateSalt := fID+'+'+fPrivateKey;
      fPrivateSaltHash :=
        crc32(crc32(0,pointer(fPrivateSalt),length(fPrivateSalt)),
          pointer(User.PasswordHashHexa),length(User.PasswordHashHexa));
      {$ifdef WITHLOG}
      SQLite3Log.Family.SynLog.Log(sllUserAuth,
................................................................................

function TypeInfoToMethodValueType(P: PTypeInfo): TServiceMethodValueType;
begin
  result := smvNone;
  if P<>nil then
  case P^.Kind of
  tkInteger:
    case P^.OrdType of
    otSLong: result := smvInteger;
    otULong: result := smvCardinal;
    end;
  tkInt64:
    result := smvInt64;
  {$ifdef FPC}
  tkBool:
    result := smvBoolean;
  {$else}
  tkEnumeration:
................................................................................
const
  REGEAX = -1;
  REGEDX = -2;
  REGECX = -3;
  PTRSIZ = sizeof(Pointer);

  CONST_ARGS_TO_VAR: array[TServiceMethodValueType] of TServiceMethodValueVar = (
    smvvNone, smvvSelf, smvv64, smvv64, smvv64, smvv64, smvv64, smvv64, smvv64,
    smvvRawUTF8, smvvString, smvvWideString, smvvObject, smvvDynArray);

  CONST_ARGS_IN_STACK_SIZE: array[TServiceMethodValueType] of Cardinal = (
     0,  PTRSIZ,  4,       4,       4, 8,     8,      8,        8,
 // None, Self, Boolean, Integer, Cardinal, Int64, Double, DateTime, Currency,
     PTRSIZ,  PTRSIZ, PTRSIZ,    PTRSIZ,  PTRSIZ);
 // RawUTF8, String, WideString, Object, DynArray

  CONST_ARGS_RESULT_BY_REF: TServiceMethodValueTypes = [
    smvRawUTF8, smvString, smvWideString, smvDynArray];

  CONST_RESULT_NAME: string[6] = 'Result';
................................................................................
      ParamName := @CONST_RESULT_NAME;
      ValueDirection := smdResult;
      TypeName := PS;
      PS := @PS^[ord(PS^[0])+1];
      TypeInfo := PP^^;
      inc(PP);
      ValueType := TypeInfoToMethodValueType(TypeInfo);
      if ValueType in [smvNone,smvObject] then
        raise EServiceException.CreateFmt('%s.%s method has unexpected result type %s',
          [fInterfaceTypeInfo^.ShortName,URI,TypeName^]);
    end;
    {$ifdef ISDELPHIXE2}
    inc(PW); // skip attributes
    {$endif}
  end;
................................................................................
        OffsetInStack := ArgsSize;
        inc(ArgsSize,SizeInStack);
      end else begin
        OffsetInStack := reg;
        dec(reg);
      end;
    end;
    // pascal/register convention are passed left-to-right -> reverse order
    offs := ArgsSize;
    for a := 0 to high(Args) do
    with Args[a] do
      if OffsetInStack>=0 then begin
        dec(offs,SizeInStack);
        OffsetInStack := offs;
      end;
................................................................................
    for i := 0 to fList.Count-1 do begin
      result := fList.List[i];
      if IsEqualGUID(result.InterfaceIID,aGUID) then
        exit;
    end;
  result := nil;
end;


{ TServiceFactoryServer }

constructor TServiceFactoryServer.Create(aRest: TSQLRest; aInterface: PTypeInfo;
  aInstanceCreation: TServiceInstanceImplementation;
  aImplementationClass: TInterfacedObjectClass; aTimeOutSec: cardinal);
begin
................................................................................
      if (LastAccess<Inst.LastAccess) or
         (LastAccess>TimeOutTimeStamp) then begin
        InstanceID := 0; // mark this entry is empty
        FreeAndNil(Instance);
      end;
    // retrieve or initialize the instance
    if Inst.InstanceID=0 then begin
      if cardinal(aMethodIndex)>=fMethodsCount then
        exit;
      // initialize the new instance
      inc(fInstanceCurrentID);
      Inst.InstanceID := fInstanceCurrentID;
      for i := 0 to fInstancesCount-1 do
        if fInstances[i].InstanceID=0 then begin
          Inst.Instance := fImplementationClass.Create; // found an empty entry
................................................................................
          break;
        end;
  finally
    LeaveCriticalSection(fInstanceLock);
  end;
end;

function TServiceFactoryServer.ExecuteMethod(aSession, aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char;

  var aResp, aHead, aErrorMsg: RawUTF8): cardinal;
var Inst: TServiceFactoryServerInstance;
    WR: TTextWriter;
    entry: PInterfaceEntry;
begin
  result := 400;
  // 1. initialize Inst.Instance and Inst.InstanceID
  Inst.InstanceID := 0;
  Inst.Instance := nil;
  case InstanceCreation of
    sicSingle:
      if aMethodIndex>=fMethodsCount then
        exit else
        Inst.Instance := fImplementationClass.Create;
    sicShared:
      if aMethodIndex>=fMethodsCount then
        exit else
        Inst.Instance := fSharedInstance;
    sicClientDriven: begin
      Inst.InstanceID := aInstanceID;
      if ClientDrivenRetrieve(Inst,aMethodIndex) then begin
        result := 200;
        exit; // {"method":"free", "params":[], "id":1234}
................................................................................
    exit;
  end;
  // 2. call method implementation
  try
    entry := Inst.Instance.GetInterfaceEntry(fInterfaceIID);
    if entry=nil then
      exit;
    WR := TJSONWriter.CreateOwnedStream;
    try
      // root/calculator {"method":"add","params":[1,2]} -> {"result":[3],"id":0}
      try
        WR.AddShort('{"result":[');
        if not fMethods[aMethodIndex].InternalExecute(
           Inst.Instance,entry,aParamsJSONArray,WR) then
          exit; // wrong request
................................................................................
    Strings: TStringDynArray;
    WideStrings: TWideStringDynArray;
    Objects: array of TObject;
    DynArrays: array of TDynArrayFake;
    Value, method: pointer;
    i,a: integer;
    wasString, valid: boolean;

    Val: PUTF8Char;
    cla: TClass;
    obj: TJSONObject;
    r: packed record EAX, EDX, ECX, EAX2, EDX2: integer; end;
begin
  result := false;
  StackSize := ArgsSize;
................................................................................
          Par := Wrapper.LoadFromJSON(Par);
          if Par=nil then
            exit;
        end;
      end;
      smvBoolean..smvWideString:
      if ValueDirection in [smdConst,smdVar] then begin
        Val := GetJSONField(Par,Par,@wasString);
        if (Val=nil) or (wasString<>ValueIsString) then
          exit;
        case ValueType of
        smvBoolean..smvInt64:  Int64s[IndexVar] := GetInt64(Val);
        smvDouble,smvDateTime: PDouble(@Int64s[IndexVar])^ := GetExtended(Val);
        smvCurrency:   Int64s[IndexVar] := StrToCurr64(Val);
        smvRawUTF8:    RawUTF8s[IndexVar] := Val;
................................................................................
    for a:= 0 to high(Args) do
    with Args[a] do
    if ValueDirection in [smdVar,smdOut,smdResult] then begin
      if ValueIsString then
        Res.Add('"');
      if (ValueVar=smvv64) and (ValueDirection=smdResult) then begin
        case ValueType of // ordinal/real result values from CPU/FPU registers
        smvBoolean:  Res.Add(PByte(@r.EAX2)^);
        smvInteger:  Res.Add(r.EAX2);
        smvCardinal: Res.AddU(r.EAX2);
        smvInt64:    Res.Add(PInt64(@r.EAX2)^);
        smvDouble, smvDateTime: Res.Add(LoadDouble);
        smvCurrency: Res.Add(LoadCurrency);
        else raise EServiceException.CreateFmt('Invalid result type %d',[ord(ValueType)]);
        end;
      end else
      case ValueType of


      smvBoolean:    Res.Add(PByte(@Int64s[IndexVar])^);
      smvInteger:    Res.Add(PInteger(@Int64s[IndexVar])^);
      smvCardinal:   Res.AddU(PCardinal(@Int64s[IndexVar])^);
      smvInt64:      Res.Add(Int64s[IndexVar]);

      smvDouble, smvDateTime: Res.Add(PDouble(@Int64s[IndexVar])^);
      smvCurrency:   Res.AddCurr64(@Int64s[IndexVar]);
      smvRawUTF8:    Res.AddJSONEscape(pointer(RawUTF8s[IndexVar]));
      smvString:     Res.AddJSONEscapeString(Strings[IndexVar]);
      smvWideString: Res.AddJSONEscapeW(pointer(WideStrings[IndexVar]));
      smvObject:     Res.WriteObject(Objects[IndexVar],False,False,true);
      smvDynArray:   Res.AddDynArrayJSON(DynArrays[IndexVar].Wrapper);
      end;
      if ValueIsString then
        Res.Add('"',',') else
        Res.Add(',');
    end;
    Res.CancelLastComma;
    Result := true;
................................................................................
end;


{ TServiceFactoryClient }

type
  /// map the stack memory layout at TInterfacedObjectFake.FakeCall()
  TFakeCallStack = packed record
    EDX, ECX, MethodIndex, EBP, Ret: Cardinal;
    Stack: array[word] of byte;
  end;

  /// instances of this class will emulate a given interface
  TInterfacedObjectFake = class(TInterfacedObject)
  protected

    fVTable: PPointerArray;
    fFactory: TServiceFactoryClient;
    fClientDrivenID: Cardinal;
    function FakeCall(var aCall: TFakeCallStack): Int64;
    function SelfFromFake: TInterfacedObjectFake;
    function FakeQueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function Fake_AddRef: Integer; stdcall;
    function Fake_Release: Integer; stdcall;
  public
    /// create an instance, using the specified interface
    constructor Create(aFactory: TServiceFactoryClient);
    /// release the remote server instance (in sicClientDriven mode);
    destructor Destroy; override;
    /// the associated interface factory
    property Factory: TServiceFactoryClient read fFactory;
    /// the ID used in sicClientDriven mode
    property ClientDrivenID: Cardinal read fClientDrivenID;
  end;

constructor TInterfacedObjectFake.Create(aFactory: TServiceFactoryClient);
begin
  inherited Create;
  fFactory := aFactory;
  fVTable := Pointer(aFactory.fFakeVTable);
end;

destructor TInterfacedObjectFake.Destroy;
begin
  if (fFactory<>nil) and (fFactory.InstanceCreation=sicClientDriven) then
  try
    fFactory.CallClient('free',nil,'',nil,@fClientDrivenID); // release server instance
  except
    ; // ignore any exception here
  end;
  inherited;
end;

function TInterfacedObjectFake.Fake_AddRef: Integer;
................................................................................
end;

function TInterfacedObjectFake.SelfFromFake: TInterfacedObjectFake;
asm
  sub eax,TInterfacedObjectFake.fVTable
end;

function TInterfacedObjectFake.FakeCall(var aCall: TFakeCallStack): Int64;
procedure RaiseError(const Msg: string);
begin
  raise EServiceException.CreateFmt('Invalid %s interface call: %s',
    [fFactory.InterfaceURI,Msg]);
end;
var Params: TJSONWriter;
    method: ^TServiceMethod;
    Error, ResArray: RawUTF8;
    a: integer;
    DynArrays: array of TDynArray;
    Value: array of pointer;
    I64s: TInt64DynArray;
    V: PPointer;
    R, Val: PUTF8Char;
    valid, wasString: boolean;
begin
  self := SelfFromFake;



  result := 0;

  if aCall.MethodIndex>=fFactory.fMethodsCount then
    RaiseError('out of range method');
  method := @fFactory.fMethods[aCall.MethodIndex];
  Params := TJSONWriter.CreateOwnedStream;
  try
    // create the parameters
    SetLength(I64s,method^.ArgsUsedCount[smvv64]);
    SetLength(DynArrays,method^.ArgsUsedCount[smvvDynArray]);
    SetLength(Value,Length(method^.Args));
    for a := 0 to high(method^.Args) do
    with method^.Args[a] do
    if ValueType>smvSelf then begin
      case OffsetInStack of
      REGEAX: RaiseError('unexpected self');
      REGEDX: V := @aCall.EDX;
      REGECX: V := @aCall.ECX;
      else if SizeInStack>0 then
        V := @aCall.Stack[OffsetInStack] else
        V := @I64s[IndexVar]; // for results in CPU
      end;
      if (ValueDirection in [smdVar,smdOut]) or
         ((ValueDirection=smdResult) and (ValueType in CONST_ARGS_RESULT_BY_REF)) then
        V := PPointer(V)^; // passed by reference -> retrieve original var
      if ValueType=smvDynArray then
        DynArrays[IndexVar].Init(TypeInfo,V);
      Value[a] := V;
      if not (ValueDirection in [smdConst,smdVar]) then
        continue;
      if ValueIsString then
        Params.Add('"');
      case ValueType of
      smvBoolean:    Params.Add(PByte(V)^);
      smvInteger:    Params.Add(PInteger(V)^);
      smvCardinal:   Params.AddU(PCardinal(V)^);
      smvInt64:      Params.Add(PInt64(V)^);
      smvDouble, smvDateTime: Params.Add(PDouble(V)^);
      smvCurrency:   Params.AddCurr64(PInt64(V)^);
      smvRawUTF8:    Params.AddJSONEscape(V^);
      smvString:     Params.AddJSONEscapeString(PString(V)^);
      smvWideString: Params.AddJSONEscapeW(V^);
      smvObject:     Params.WriteObject(V^,false,false,true);
      smvDynArray:   Params.AddDynArrayJSON(DynArrays[IndexVar]);
      end;
      if ValueIsString then
        Params.Add('"',',') else
        Params.Add(',');
    end;
    Params.CancelLastComma;
    // call remote server
    if not fFactory.CallClient(method^.URI,@Error,
       Params.Text,@ResArray,@fClientDrivenID) then
      raise EServiceException.CreateFmt('Error calling %s.%s remote method%s',
        [fFactory.fInterfaceURI,method^.URI,Error]);
  finally
    Params.Free;
  end;
  // retrieve method result and var/out parameters content
  R := pointer(ResArray);
  while (R^<>#0) and (R^<=' ') do inc(R);
  if R^<>'[' then
    RaiseError('array result expected');
  inc(R);
  for a:= 0 to high(method^.Args) do
  with method^.Args[a] do
  if ValueDirection in [smdVar,smdOut,smdResult] then begin
    V := Value[a];
    case ValueType of
    smvObject: begin
      R := JSONToObject(V^,R,valid);
      if not valid then
        RaiseError('result object');
    end;
    smvDynArray: begin
      R := DynArrays[IndexVar].LoadFromJSON(R);
      if R=nil then
        RaiseError('result array');
    end;
    smvBoolean..smvWideString: begin
      Val := GetJSONField(R,R,@wasString);
      if (Val=nil) or (wasString<>ValueIsString) then
        RaiseError('result item');
      case ValueType of
      smvBoolean:    PByte(V)^ := GetCardinal(Val);
      smvInteger:    PInteger(V)^ := GetInteger(Val);
      smvCardinal:   PCardinal(V)^ := GetCardinal(Val);
      smvInt64:      PInt64(V)^ := GetInt64(Val);
      smvDouble, smvDateTime: PDouble(V)^ := GetExtended(Val);
      smvCurrency:   PInt64(V)^ := StrToCurr64(Val);
      smvRawUTF8:    PRawUTF8(V)^ := Val;
      smvString:     PString(V)^ := UTF8DecodeToString(Val,StrLen(Val));
      smvWideString: UTF8ToWideString(Val,StrLen(Val),PWideString(V)^);
      end;
    end;
    end;
    if ValueDirection=smdResult then
    case ValueType of // ordinal/real result values to CPU/FPU registers
    smvBoolean, smvInteger, smvCardinal:  Int64Rec(result).Lo := PInteger(V)^;
    smvInt64:    result := PInt64(V)^;
    smvDouble:   asm mov eax,V; fld  qword ptr [eax] end;
    smvCurrency: asm mov eax,V; fild qword ptr [eax] end;
    end;
  end;
end;

function TServiceFactoryClient.CallClient(const aMethod: RawUTF8;
  aErrorMsg: PRawUTF8; const aParams: RawUTF8;
  aResult: PRawUTF8; aClientDrivenID: Pcardinal): boolean;
var sent,resp,head: RawUTF8;
    Values: TPUtf8CharDynArray;
begin
  result := false;
  if Self=nil then
    exit;
  if fClient=nil then
    fClient := fRest as TSQLRestClientURI;
  sent := '{"method":"'+aMethod+'","params":['+aParams;
  if aClientDrivenID=nil then
    sent := sent+']}' else
    sent := sent+'], "id":'+UInt32ToUTF8(aClientDrivenID^)+'}';
  if fClient.URI(fClient.Model.Root+'/'+fInterfaceURI,'POST',@resp,@head,@sent).Lo<>200 then begin
    if aErrorMsg<>nil then
      aErrorMsg^ := ': '+resp;
    exit;
  end;
  JSONDecode(resp,['RESULT','ID'],Values,True);
  if (Values[0]=nil) or (Values[1]=nil) then
    exit;
  if aResult<>nil then
    aResult^ := Values[0];
  if aClientDrivenID<>nil then
    aClientDrivenID^ := GetCardinal(Values[1]);
  result := true;
end;

constructor TServiceFactoryClient.Create(aRest: TSQLRest;
  aInterface: PTypeInfo; aInstanceCreation: TServiceInstanceImplementation);
var i, siz: integer;
    P: PCardinal;
    Error: RawUTF8;
begin
  // extract RTTI from the interface
  if not aRest.InheritsFrom(TSQLRestClientURI) then
    EServiceException.CreateFmt('%s interface needs a Client connection',
      [aInterface^.ShortName]);
  inherited Create(aRest,aInterface,aInstanceCreation);
  // check if this interface is supported on the server
  if not CallClient('ClassName',@Error,'',@fRemoteClassName) then
    raise EServiceException.CreateFmt('%s interface not supported by server%s',
      [fInterfaceURI,Error]);
  // create the fake interface
  SetLength(fFakeVTable,fMethodsCount+3);
  fFakeVTable[0] := @TInterfacedObjectFake.FakeQueryInterface;
  fFakeVTable[1] := @TInterfacedObjectFake.Fake_AddRef;
  fFakeVTable[2] := @TInterfacedObjectFake.Fake_Release;
  siz := (((fMethodsCount*24) shr 12)+1) shl 12; // 4 KB granularity
  fFakeStub := VirtualAlloc(nil,siz,MEM_COMMIT,PAGE_EXECUTE_READWRITE);

Changes to SynCommons.pas.

1104
1105
1106
1107
1108
1109
1110


1111
1112
1113
1114
1115
1116
1117
....
9167
9168
9169
9170
9171
9172
9173








9174
9175
9176
9177
9178
9179
9180
.....
17707
17708
17709
17710
17711
17712
17713
17714
17715
17716
17717
17718
17719
17720
17721
.....
20032
20033
20034
20035
20036
20037
20038
20039
20040
20041
20042
20043
20044
20045
20046
20047
20048
20049
20050
20051
20052
.....
20068
20069
20070
20071
20072
20073
20074
20075
20076
20077
20078
20079
20080
20081
20082
.....
24869
24870
24871
24872
24873
24874
24875
24876
24877
24878
24879
24880
24881
24882
24883
24884
24885
24886
24887
24888
24889
24890
24891
24892
24893
24894
24895
24896
24897
24898
24899
function Int64ToUtf8(Value: Int64): RawUTF8;

/// use our fast RawUTF8 version of IntToStr()
// - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009
// - only usefull if our Enhanced Runtime (or LVCL) library is not installed
function Int32ToUtf8(Value: integer): RawUTF8;




/// faster version than default SysUtils.IntToStr implementation
function IntToString(Value: integer): string; overload;

/// faster version than default SysUtils.IntToStr implementation
function IntToString(Value: cardinal): string; overload;

................................................................................
{$else}
  P := StrInt64(@tmp[23],Value);
{$endif}
  SetString(result,P,@tmp[23]-P);
end;

{$endif}









{.$define EXTENDEDTOSTRING_USESTR}
// see http://synopse.info/fossil/tktview?name=6593f0fbd1

{$ifndef WITHUXTHEME}
  {$define EXTENDEDTOSTRING_USESTR} // no TFormatSettings before Delphi 6
{$endif}
................................................................................
    aName_: RawUTF8;
begin
  c := FindHashedForAdding(aName,added);
  if not added then begin // force unique column name
    aName_ := aName+'_';
    j := 1;
    repeat
      aName := aName_+Int32ToUTF8(j);
      c := FindHashedForAdding(aName,added);
      inc(j);
    until added;
  end;
  assert(c=Count-1);
  result := PAnsiChar(Value^)+cardinal(c)*ElemSize;
  PRawUTF8(result)^ := aName; // store unique name at 1st elem position
................................................................................
  if bytes>=1024*1024 then begin
    if bytes>=1024*1024*1024 then begin
      bytes := bytes shr 20;
      result := ' GB';
    end else
      result := ' MB';
    result :=
      Int32ToUtf8(bytes shr 20)+'.'+
      Int32ToUtf8((PtrUInt(bytes) and pred(1 shl 20))div (102*1024))+
      result;
  end else
  if bytes>1023*9 then
    result := Int32ToUtf8(PtrUInt(bytes) shr 10)+' KB' else
    result := Int32ToUtf8(PtrUInt(bytes))+' B';
end;

function IntToThousandString(Value: integer; const ThousandSep: RawUTF8=','): RawUTF8;
var i,L,Len: cardinal;
begin
  Result := {$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(value);
  L := length(Result);
................................................................................
    result := '0.0'+result else // '3' -> '0.03'
  if L=2 then
    result := '0.'+result else // '35' -> '0.35'
    insert('.',result,L-1); // '103' -> '1.03'
end;
begin
  if Micro<1000 then
    result := {$ifndef ENHANCEDRTL}Int64ToUtf8{$else}IntToStr{$endif}(Micro)+'us' else
  if Micro<1000*1000 then
    result := TwoDigitToString(Micro div 10)+'ms' else
    result := TwoDigitToString(Micro div (10*1000))+'s';
end;

{$ifdef MSWINDOWS}

................................................................................
    PC: PAnsiChar absolute FieldBuffer;
begin
  case FieldType of
  // fixed-sized field value
  tftBoolean:
    result := JSON_BOOLEAN[PBoolean(FieldBuffer)^];
  tftUInt8:
    result := Int32ToUtf8(PB^);
  tftUInt16:
    result := Int32ToUtf8(PWord(FieldBuffer)^);
  tftUInt24:
    // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file
    result := Int32ToUtf8(PWord(FieldBuffer)^+integer(PByteArray(FieldBuffer)^[2])shl 16);
  tftInt32:
    result := Int32ToUtf8(PInteger(FieldBuffer)^);
  tftInt64:
    result := Int64ToUtf8(PInt64(FieldBuffer)^);
  tftCurrency:
    result := Curr64ToStr(PInt64(FieldBuffer)^);
  tftDouble:
    result := DoubleToStr(PDouble(FieldBuffer)^);
  // some variable-size field value
  tftVarUInt32:
    result := Int64ToUtf8(FromVarUInt32(PB));
  tftVarInt32:
    result := Int32ToUtf8(FromVarInt32(PB));
  tftVarUInt64:
    result := Int64ToUtf8(FromVarUInt64(PB));
  tftVarInt64:
    result := Int64ToUtf8(FromVarInt64(PB));
  // text storage - WinAnsi could use less space than UTF-8






>
>







 







>
>
>
>
>
>
>
>







 







|







 







|
|



|
|







 







|







 







|

|


|










|







1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
....
9169
9170
9171
9172
9173
9174
9175
9176
9177
9178
9179
9180
9181
9182
9183
9184
9185
9186
9187
9188
9189
9190
.....
17717
17718
17719
17720
17721
17722
17723
17724
17725
17726
17727
17728
17729
17730
17731
.....
20042
20043
20044
20045
20046
20047
20048
20049
20050
20051
20052
20053
20054
20055
20056
20057
20058
20059
20060
20061
20062
.....
20078
20079
20080
20081
20082
20083
20084
20085
20086
20087
20088
20089
20090
20091
20092
.....
24879
24880
24881
24882
24883
24884
24885
24886
24887
24888
24889
24890
24891
24892
24893
24894
24895
24896
24897
24898
24899
24900
24901
24902
24903
24904
24905
24906
24907
24908
24909
function Int64ToUtf8(Value: Int64): RawUTF8;

/// use our fast RawUTF8 version of IntToStr()
// - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009
// - only usefull if our Enhanced Runtime (or LVCL) library is not installed
function Int32ToUtf8(Value: integer): RawUTF8;

/// optimized conversion of a cardinal into RawUTF8
function UInt32ToUtf8(Value: cardinal): RawUTF8;

/// faster version than default SysUtils.IntToStr implementation
function IntToString(Value: integer): string; overload;

/// faster version than default SysUtils.IntToStr implementation
function IntToString(Value: cardinal): string; overload;

................................................................................
{$else}
  P := StrInt64(@tmp[23],Value);
{$endif}
  SetString(result,P,@tmp[23]-P);
end;

{$endif}

function UInt32ToUTF8(Value : Cardinal): RawUTF8; // faster than SysUtils.IntToStr
var tmp: array[0..15] of AnsiChar;
    P: PAnsiChar;
begin
  P := StrUInt32(@tmp[15],Value);
  SetString(result,P,@tmp[15]-P);
end;

{.$define EXTENDEDTOSTRING_USESTR}
// see http://synopse.info/fossil/tktview?name=6593f0fbd1

{$ifndef WITHUXTHEME}
  {$define EXTENDEDTOSTRING_USESTR} // no TFormatSettings before Delphi 6
{$endif}
................................................................................
    aName_: RawUTF8;
begin
  c := FindHashedForAdding(aName,added);
  if not added then begin // force unique column name
    aName_ := aName+'_';
    j := 1;
    repeat
      aName := aName_+UInt32ToUTF8(j);
      c := FindHashedForAdding(aName,added);
      inc(j);
    until added;
  end;
  assert(c=Count-1);
  result := PAnsiChar(Value^)+cardinal(c)*ElemSize;
  PRawUTF8(result)^ := aName; // store unique name at 1st elem position
................................................................................
  if bytes>=1024*1024 then begin
    if bytes>=1024*1024*1024 then begin
      bytes := bytes shr 20;
      result := ' GB';
    end else
      result := ' MB';
    result :=
      UInt32ToUtf8(bytes shr 20)+'.'+
      UInt32ToUtf8((PtrUInt(bytes) and pred(1 shl 20))div (102*1024))+
      result;
  end else
  if bytes>1023*9 then
    result := UInt32ToUtf8(PtrUInt(bytes) shr 10)+' KB' else
    result := UInt32ToUtf8(PtrUInt(bytes))+' B';
end;

function IntToThousandString(Value: integer; const ThousandSep: RawUTF8=','): RawUTF8;
var i,L,Len: cardinal;
begin
  Result := {$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(value);
  L := length(Result);
................................................................................
    result := '0.0'+result else // '3' -> '0.03'
  if L=2 then
    result := '0.'+result else // '35' -> '0.35'
    insert('.',result,L-1); // '103' -> '1.03'
end;
begin
  if Micro<1000 then
    result := {$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(Micro)+'us' else
  if Micro<1000*1000 then
    result := TwoDigitToString(Micro div 10)+'ms' else
    result := TwoDigitToString(Micro div (10*1000))+'s';
end;

{$ifdef MSWINDOWS}

................................................................................
    PC: PAnsiChar absolute FieldBuffer;
begin
  case FieldType of
  // fixed-sized field value
  tftBoolean:
    result := JSON_BOOLEAN[PBoolean(FieldBuffer)^];
  tftUInt8:
    result := UInt32ToUtf8(PB^);
  tftUInt16:
    result := UInt32ToUtf8(PWord(FieldBuffer)^);
  tftUInt24:
    // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file
    result := UInt32ToUtf8(PWord(FieldBuffer)^+integer(PByteArray(FieldBuffer)^[2])shl 16);
  tftInt32:
    result := Int32ToUtf8(PInteger(FieldBuffer)^);
  tftInt64:
    result := Int64ToUtf8(PInt64(FieldBuffer)^);
  tftCurrency:
    result := Curr64ToStr(PInt64(FieldBuffer)^);
  tftDouble:
    result := DoubleToStr(PDouble(FieldBuffer)^);
  // some variable-size field value
  tftVarUInt32:
    result := UInt32ToUtf8(FromVarUInt32(PB));
  tftVarInt32:
    result := Int32ToUtf8(FromVarInt32(PB));
  tftVarUInt64:
    result := Int64ToUtf8(FromVarUInt64(PB));
  tftVarInt64:
    result := Int64ToUtf8(FromVarInt64(PB));
  // text storage - WinAnsi could use less space than UTF-8

Changes to SynSelfTests.pas.

1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
procedure TTestServiceOrientedArchitecture.ClientSide;
var I: ICalculator;
begin
  Check(fClient.ServiceRegister([TypeInfo(ICalculator)],sicShared));
  // once registered, can be accessed by its GUID or URI
  if CheckFailed(fClient.Services.Info(TypeInfo(ICalculator)).Get(I)) then
    exit;
  Check(I.Add(1,2)=3);
  Check(I.Multiply(2,3)=6);
  I := nil;
  if CheckFailed(fClient.Services.GUID(IID_ICalculator).Get(I)) then
    exit;
  Check(I.Add(1,2)=3);
  Check(I.Multiply(2,3)=6);
  I := nil;
  if CheckFailed(fClient.Services['Calculator'].Get(I)) then
    exit;
  Check(I.Add(1,2)=3);
  Check(I.Multiply(2,3)=6);
end;

destructor TTestServiceOrientedArchitecture.Destroy;
begin
  fClient.Free;
  fModel.Free;
  inherited;






|
<



|
<



|
<







1305
1306
1307
1308
1309
1310
1311
1312

1313
1314
1315
1316

1317
1318
1319
1320

1321
1322
1323
1324
1325
1326
1327
procedure TTestServiceOrientedArchitecture.ClientSide;
var I: ICalculator;
begin
  Check(fClient.ServiceRegister([TypeInfo(ICalculator)],sicShared));
  // once registered, can be accessed by its GUID or URI
  if CheckFailed(fClient.Services.Info(TypeInfo(ICalculator)).Get(I)) then
    exit;
  Test(I);

  I := nil;
  if CheckFailed(fClient.Services.GUID(IID_ICalculator).Get(I)) then
    exit;
  Test(I);

  I := nil;
  if CheckFailed(fClient.Services['Calculator'].Get(I)) then
    exit;
  Test(I);

end;

destructor TTestServiceOrientedArchitecture.Destroy;
begin
  fClient.Free;
  fModel.Free;
  inherited;