{*******************************************************} { } { Tocsg.Serializer } { } { Copyright (C) 2022 sunk } { } {*******************************************************} unit Tocsg.Serializer; interface uses Tocsg.Obj, System.Classes, System.SysUtils, Winapi.Windows, Tocsg.Exception; const SER_SIG: AnsiString = 'KzSer.'; SER_VER = 1; type TTgSerHeader = packed record sSig: array [0..11] of AnsiChar; nVer: Integer; end; ETgSerializer = class(ETgException); TTgSerializerBase = class(TTgObject) private bStreamFree_: Boolean; protected Header_: TTgSerHeader; Stream_: TStream; function GetPosition: LONGLONG; function GetSize: LONGLONG; public Constructor Create(aStream: TStream; bAutoStreamFree: Boolean = false); overload; Constructor Create; overload; Destructor Destroy; override; procedure SeekFront; procedure SeekEnd; property Stream: TStream read Stream_; property Header: TTgSerHeader read Header_; end; TTgSerializerSave = class(TTgSerializerBase) public procedure SaveHeader(nVer: Integer = SER_VER); procedure S_WideString(sVal: WideString); procedure S_AnsiString(sVal: AnsiString); procedure S_UTF8String(sVal: UTF8String); procedure S_Integer(nVal: Integer); procedure S_DWORD(dwVal: DWORD); procedure S_WORD(wVal: WORD); procedure S_LONGLONG(llVal: LONGLONG); procedure S_ULONGLONG(ullVal: ULONGLONG); procedure S_DateTime(dtVal: TDateTime); procedure S_Strings(aList: TStrings); procedure S_Boolean(bVal: Boolean); procedure S_Stream(aStream: TStream); procedure SaveToFile(sPath: String); end; TTgSerializerLoad = class(TTgSerializerSave) public function LoadHeader: Boolean; function L_WideString: WideString; function L_AnsiString: AnsiString; function L_UTF8String: UTF8String; function L_Integer: Integer; function L_DWORD: DWORD; function L_WORD: WORD; function L_LONGLONG: LONGLONG; function L_ULONGLONG: ULONGLONG; function L_DateTime: TDateTime; function L_Strings(aList: TStrings): Integer; function L_Boolean: Boolean; function L_Stream(aStream: TStream): LONGLONG; function IsEndOfFile: Boolean; function GetRemainSize: LONGLONG; function GetReadPercent: WORD; procedure LoadFromFile(sPath: String); end; implementation { TTgSerializerBase } Constructor TTgSerializerBase.Create(aStream: TStream; bAutoStreamFree: Boolean = false); begin Inherited Create; ZeroMemory(@Header_, SizeOf(Header_)); bStreamFree_ := bAutoStreamFree; Stream_ := aStream; if Stream_ = nil then raise ETgSerializer.Create('스트림이 지정되지 않았습니다.'); end; Constructor TTgSerializerBase.Create; begin Inherited Create; ZeroMemory(@Header_, SizeOf(Header_)); Stream_ := TMemoryStream.Create; bStreamFree_ := true; end; Destructor TTgSerializerBase.Destroy; begin if (Stream_ <> nil) and bStreamFree_ then FreeAndNil(Stream_); Inherited; end; function TTgSerializerBase.GetPosition: LONGLONG; begin Result := Stream_.Position; end; function TTgSerializerBase.GetSize: LONGLONG; begin Result := Stream_.Size; end; procedure TTgSerializerBase.SeekFront; begin Stream_.Seek(0, soBeginning); end; procedure TTgSerializerBase.SeekEnd; begin Stream_.Seek(0, soEnd) end; { TTgSerializerSave } procedure TTgSerializerSave.SaveHeader(nVer: Integer = SER_VER); begin if Stream_.Position > 0 then raise Exception.Create('offset 값이 0보다 큽니다.'); if Length(SER_SIG) > SizeOf(Header_.sSig) then raise Exception.Create('시그너처 길이가 너무 큽니다.'); ZeroMemory(@Header_.sSig, SizeOf(Header_.sSig)); CopyMemory(@Header_.sSig[0], @SER_SIG[1], Length(SER_SIG)); Header_.nVer := nVer; Stream_.Write(Header_, SizeOf(Header_)); end; procedure TTgSerializerSave.S_WideString(sVal: WideString); var nLen: Integer; begin nLen := Length(sVal) * 2; S_Integer(nLen); Stream_.Write(PWideChar(sVal)^, nLen); end; procedure TTgSerializerSave.S_AnsiString(sVal: AnsiString); var nLen: Integer; begin nLen := Length(sVal); S_Integer(nLen); Stream_.Write(PAnsiChar(sVal)^, nLen); end; procedure TTgSerializerSave.S_UTF8String(sVal: UTF8String); var nLen: Integer; begin nLen := Length(sVal); S_Integer(nLen); Stream_.Write(PUTF8String(sVal)^, nLen); end; procedure TTgSerializerSave.S_Integer(nVal: Integer); begin try Stream_.Write(nVal, SizeOf(nVal)); except _Trace('Fail .. S_Integer()'); end; end; procedure TTgSerializerSave.S_DWORD(dwVal: DWORD); begin try Stream_.Write(dwVal, SizeOf(dwVal)); except _Trace('Fail .. S_DWORD()'); end; end; procedure TTgSerializerSave.S_WORD(wVal: WORD); begin try Stream_.Write(wVal, SizeOf(wVal)); except _Trace('Fail .. S_WORD()'); end; end; procedure TTgSerializerSave.S_LONGLONG(llVal: LONGLONG); begin try Stream_.Write(llVal, SizeOf(llVal)); except _Trace('Fail .. S_LONGLONG()'); end; end; procedure TTgSerializerSave.S_ULONGLONG(ullVal: ULONGLONG); begin try Stream_.Write(ullVal, SizeOf(ullVal)); except _Trace('Fail .. S_ULONGLONG()'); end; end; procedure TTgSerializerSave.S_DateTime(dtVal: TDateTime); begin try Stream_.Write(dtVal, SizeOf(dtVal)); except _Trace('Fail .. S_DateTime()'); end; end; procedure TTgSerializerSave.S_Strings(aList: TStrings); var i: Integer; begin try S_Integer(aList.Count); for i := 0 to aList.Count - 1 do S_UTF8String(aList[i]); except _Trace('Fail .. S_Strings()'); end; end; procedure TTgSerializerSave.S_Boolean(bVal: Boolean); begin try Stream_.Write(bVal, SizeOf(bVal)); except _Trace('Fail .. S_Boolean()'); end; end; procedure TTgSerializerSave.S_Stream(aStream: TStream); begin try aStream.Position := 0; S_LONGLONG(aStream.Size); Stream_.CopyFrom(aStream, aStream.Size); except _Trace('Fail .. S_Stream()'); end; end; procedure TTgSerializerSave.SaveToFile(sPath: String); begin if not (Stream_ is TMemoryStream) then raise ETgSerializer.Create('Stream이 TMemoryStream가 아닙니다.'); try TMemoryStream(Stream_).SaveToFile(sPath); except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. SaveToFile()'); end; end; { TTgSerializerLoad } function TTgSerializerLoad.LoadHeader: Boolean; begin Result := false; ZeroMemory(@Header_, SizeOf(Header_)); if Stream_.Position > 0 then raise Exception.Create('offset 값이 0보다 큽니다.'); try if Stream_.Read(Header_, SizeOf(Header_)) <> SizeOf(Header_) then exit; // 크기 체크 추가 19_0710 17:01:52 kku Result := (Stream_.Size > Stream_.Position) and CompareMem(@Header_.sSig[0], @SER_SIG[1], Length(SER_SIG)); finally if not Result then Stream_.Position := 0; end; end; function TTgSerializerLoad.L_WideString: WideString; var nLen: Integer; begin Result := ''; try nLen := L_Integer; if nLen = 0 then exit; if nLen > GetRemainSize then exit; // raise ETgSerializer.Create('남은 데이터가 부족합니다.'); SetLength(Result, nLen div 2); Stream_.Read(Result[1], nLen); except _Trace('Fail .. L_WideString()'); end; end; function TTgSerializerLoad.L_AnsiString: AnsiString; var nLen: Integer; begin Result := ''; try nLen := L_Integer; if nLen = 0 then exit; if nLen > GetRemainSize then exit; // raise ETgSerializer.Create('남은 데이터가 부족합니다.'); SetLength(Result, nLen); Stream_.Read(Result[1], nLen); except _Trace('Fail .. L_AnsiString()'); end; end; function TTgSerializerLoad.L_UTF8String: UTF8String; var nLen: Integer; begin Result := ''; try nLen := L_Integer; if nLen = 0 then exit; if nLen > GetRemainSize then exit; // raise ETgSerializer.Create('남은 데이터가 부족합니다.'); SetLength(Result, nLen); Stream_.Read(Result[1], nLen); except _Trace('Fail .. L_UTF8String()'); end; end; function TTgSerializerLoad.L_Integer: Integer; begin try Stream_.Read(Result, SizeOf(Result)); except _Trace('Fail .. L_Integer()'); end; end; function TTgSerializerLoad.L_DWORD: DWORD; begin try Stream_.Read(Result, SizeOf(Result)); except _Trace('Fail .. L_DWORD()'); end; end; function TTgSerializerLoad.L_WORD: WORD; begin try Stream_.Read(Result, SizeOf(Result)); except _Trace('Fail .. L_WORD()'); end; end; function TTgSerializerLoad.L_LONGLONG: LONGLONG; begin try Stream_.Read(Result, SizeOf(Result)); except _Trace('Fail .. L_LONGLONG()'); end; end; function TTgSerializerLoad.L_ULONGLONG: ULONGLONG; begin try Stream_.Read(Result, SizeOf(Result)); except _Trace('Fail .. L_ULONGLONG()'); end; end; function TTgSerializerLoad.L_DateTime: TDateTime; begin try Stream_.Read(Result, SizeOf(Result)); except _Trace('Fail .. L_DateTime()'); end; end; function TTgSerializerLoad.L_Strings(aList: TStrings): Integer; var i, nCnt: Integer; begin try nCnt := L_Integer; for i := 0 to nCnt - 1 do aList.Add(L_UTF8String); except _Trace('Fail .. L_Strings()'); end; end; function TTgSerializerLoad.L_Boolean: Boolean; begin try Stream_.Read(Result, SizeOf(Result)); except _Trace('Fail .. L_Boolean()'); end; end; function TTgSerializerLoad.L_Stream(aStream: TStream): LONGLONG; var llSize: LONGLONG; begin try llSize := L_LONGLONG; if llSize > 0 then begin Result := aStream.CopyFrom(Stream_, llSize); if Result <> llSize then raise ETgSerializer.Create('남은 데이터가 부족합니다.'); end else Result := 0; except _Trace('Fail .. L_Stream()'); end; end; function TTgSerializerLoad.IsEndOfFile: Boolean; begin Result := Stream_.Position >= Stream_.Size; end; function TTgSerializerLoad.GetRemainSize: LONGLONG; begin Result := Stream_.Size - Stream_.Position; end; function TTgSerializerLoad.GetReadPercent: WORD; begin if Stream_.Size > 0 then Result := (Stream_.Position * 100) div Stream_.Size else Result := 0; end; procedure TTgSerializerLoad.LoadFromFile(sPath: String); begin if not (Stream_ is TMemoryStream) then raise ETgSerializer.Create('Stream이 TMemoryStream가 아닙니다.'); try TMemoryStream(Stream_).LoadFromFile(sPath); Stream_.Position := 0; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. LoadFromFile()'); end; end; end.