Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
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: |
86107183864f5783ac6557305c0eb32f |
User & Date: | User 2012-02-26 18:29:17 |
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 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; |