{***************************************************************************** The DEC team (see file NOTICE.txt) licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. A copy of this licence is found in the root directory of this project in the file LICENCE.txt or alternatively at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *****************************************************************************} /// /// Base unit for all the hash algorithms. The key deviation algorithms are /// in the DECHashAUthentication unit and hash algorithms which can process /// messages with a length specified in bits instead of whole bytes have /// to inherit from TDECHashBit /// unit DECHashBase; interface {$INCLUDE DECOptions.inc} uses {$IFDEF FPC} SysUtils, Classes, {$ELSE} System.SysUtils, System.Classes, {$ENDIF} DECBaseClass, DECFormatBase, DECUtil, DECTypes, DECHashInterface; type /// /// Meta class for all the hashing classes in order to support the /// registration mechanism /// TDECHashClass = class of TDECHash; /// /// Base class for all hash algorithm implementation classes /// {$IFDEF FPC} TDECHash = class(TDECObject) // does not find methods of the interface as it // searches for AnsiString instead of RawByteString // and thus does not find that {$ELSE} TDECHash = class(TDECObject, IDECHash) {$ENDIF} strict private /// /// Raises an EDECHashException hash algorithm not initialized exception /// procedure RaiseHashNotInitialized; /// /// Returns the current value of the padding byte used to fill up data /// if necessary /// function GetPaddingByte: Byte; /// /// Changes the value of the padding byte used to fill up data /// if necessary /// /// /// New value for the padding byte /// procedure SetPaddingByte(Value: Byte); strict protected FCount : array[0..7] of UInt32; /// /// Internal processing buffer /// FBuffer : PByteArray; /// /// Size of the internal processing buffer in byte /// FBufferSize : Integer; /// /// Position the algorithm is currently at in the processing buffer /// FBufferIndex : Integer; /// /// Value used to fill up data /// FPaddingByte : Byte; /// /// Last byte of the message to be hashed if the algorithm is capable of /// processing bit sized message lengths and FFinalBitLen > 0. /// FFinalByte : UInt8; /// /// Setting this to a number of bits allows to process messages which have /// a length which is not a exact multiple of bytes. /// FFinalByteLength : UInt8; /// /// This abstract method has to be overridden by each concrete hash algorithm /// to initialize the necessary data structures. /// procedure DoInit; virtual; abstract; procedure DoTransform(Buffer: PUInt32Array); virtual; abstract; /// /// This abstract method has to be overridden by each concrete hash algorithm /// to finalize the calculation of a hash value over the data passed. /// procedure DoDone; virtual; abstract; /// /// Adds the value of 8*Add to the value (which is interpreted as an /// 8*32 bit unsigned integer array. The carry is taken care of. /// /// /// Value which is incremented /// /// /// Value (which is being multiplied by 8) by which to increment Value /// /// /// Raises an EDECHashException overflow error if the last operation has /// set the carry flag /// procedure Increment8(var Value; Add: UInt32); /// /// Raises an EDECHashException overflow error /// procedure RaiseHashOverflowError; /// /// Overwrite internally used processing buffers to make it harder to steal /// any data from memory. /// procedure SecureErase; virtual; /// /// Returns the calculated hash value /// function Digest: PByteArray; virtual; abstract; public /// /// Initialize internal fields /// constructor Create; override; /// /// Fees internal resources /// destructor Destroy; override; /// /// Generic initialization of internal data structures. Additionally the /// internal algorithm specific (because of being overridden by each /// hash algorithm) DoInit method is called. Needs to be called before /// each hash calculation. /// procedure Init; /// /// Processes one chunk of data to be hashed. /// /// /// Data on which the hash value shall be calculated on /// /// /// Size of the data in bytes /// procedure Calc(const Data; DataSize: Integer); virtual; /// /// Frees dynamically allocated buffers in a way which safeguards agains /// data stealing by other methods which afterwards might allocate this memory. /// Additionaly calls the algorithm spercific DoDone method. /// procedure Done; /// /// Returns the calculated hash value as byte array /// function DigestAsBytes: TBytes; virtual; /// /// Returns the calculated hash value as formatted Unicode string /// /// /// Optional parameter. If a formatting class is being passed the formatting /// will be applied to the returned string. Otherwise no formatting is /// being used. /// /// /// Hash value of the last performed hash calculation /// /// /// We recommend to use a formatting which results in 7 bit ASCII chars /// being returned, otherwise the conversion into the Unicode string might /// result in strange characters in the returned result. /// function DigestAsString(Format: TDECFormatClass = nil): string; /// /// Returns the calculated hash value as formatted RawByteString /// /// /// Optional parameter. If a formatting class is being passed the formatting /// will be applied to the returned string. Otherwise no formatting is /// being used. /// /// /// Hash value of the last performed hash calculation /// /// /// We recommend to use a formatting which results in 7 bit ASCII chars /// being returned, otherwise the conversion into the RawByteString might /// result in strange characters in the returned result. /// function DigestAsRawByteString(Format: TDECFormatClass = nil): RawByteString; /// /// Gives the length of the calculated hash value in byte. Needs to be /// overridden in concrete hash implementations. /// class function DigestSize: UInt32; virtual; /// /// Gives the length of the blocks the hash value is being calculated /// on in byte. Needs to be overridden in concrete hash implementations. /// class function BlockSize: UInt32; virtual; /// /// List of registered DEC classes. Key is the Identity of the class. /// class var ClassList : TDECClassList; /// /// Tries to find a class type by its name /// /// /// Name to look for in the list /// /// /// Returns the class type if found. if it could not be found a /// EDECClassNotRegisteredException will be thrown /// class function ClassByName(const Name: string): TDECHashClass; /// /// Tries to find a class type by its numeric identity DEC assigned to it. /// Useful for file headers, so they can easily encode numerically which /// cipher class was being used. /// /// /// Identity to look for /// /// /// Returns the class type of the class with the specified identity value /// or throws an EDECClassNotRegisteredException exception if no class /// with the given identity has been found /// class function ClassByIdentity(Identity: Int64): TDECHashClass; /// /// Detects whether the given hash class is one particularily suited /// for storing hashes of passwords /// /// /// true if it's a hash class specifically designed to store password /// hashes, false for ordinary hash algorithms. /// class function IsPasswordHash: Boolean; virtual; // hash calculation wrappers /// /// Calculates the hash value (digest) for a given buffer /// /// /// Untyped buffer the hash shall be calculated for /// /// /// Size of the buffer in byte /// /// /// Byte array with the calculated hash value /// function CalcBuffer(const Buffer; BufferSize: Integer): TBytes; /// /// Calculates the hash value (digest) for a given buffer /// /// /// The TBytes array the hash shall be calculated on /// /// /// Byte array with the calculated hash value /// function CalcBytes(const Data: TBytes): TBytes; /// /// Calculates the hash value (digest) for a given unicode string /// /// /// The string the hash shall be calculated on /// /// /// Formatting class from DECFormat. The formatting will be applied to the /// returned digest value. This parameter is optional. /// /// /// string with the calculated hash value /// function CalcString(const Value: string; Format: TDECFormatClass = nil): string; overload; /// /// Calculates the hash value (digest) for a given rawbytestring /// /// /// The string the hash shall be calculated on /// /// /// Formatting class from DECFormat. The formatting will be applied to the /// returned digest value. This parameter is optional. /// /// /// string with the calculated hash value /// function CalcString(const Value: RawByteString; Format: TDECFormatClass): RawByteString; overload; /// /// Calculates the hash value over a given stream of bytes /// /// /// Memory or file stream over which the hash value shall be calculated. /// The stream must be assigned. The hash value will always be calculated /// from the current position of the stream. /// /// /// Number of bytes within the stream over which to calculate the hash value /// /// /// In this byte array the calculated hash value will be returned /// /// /// Optional callback routine. It can be used to display the progress of /// the operation. /// procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes; const OnProgress:TDECProgressEvent = nil); overload; /// /// Calculates the hash value over a givens stream of bytes /// /// /// Memory or file stream over which the hash value shall be calculated. /// The stream must be assigned. The hash value will always be calculated /// from the current position of the stream. /// /// /// Number of bytes within the stream over which to calculate the hash value /// /// /// Optional formatting class. The formatting of that will be applied to /// the returned hash value. /// /// /// Optional callback routine. It can be used to display the progress of /// the operation. /// /// /// Hash value over the bytes in the stream, formatted with the formatting /// passed as format parameter, if used. /// function CalcStream(const Stream: TStream; Size: Int64; Format: TDECFormatClass = nil; const OnProgress:TDECProgressEvent = nil): RawByteString; overload; /// /// Calculates the hash value over the contents of a given file /// /// /// Path and name of the file to be processed /// /// /// Here the resulting hash value is being returned as byte array /// /// /// Optional callback. If being used the hash calculation will call it from /// time to time to return the current progress of the operation /// procedure CalcFile(const FileName: string; var HashResult: TBytes; const OnProgress:TDECProgressEvent = nil); overload; /// /// Calculates the hash value over the contents of a given file /// /// /// Path and name of the file to be processed /// /// /// Optional parameter: Formatting class. If being used the formatting is /// being applied to the returned string with the calculated hash value /// /// /// Optional callback. If being used the hash calculation will call it from /// time to time to return the current progress of the operation /// /// /// Calculated hash value as RawByteString. /// /// /// We recommend to use a formatting which results in 7 bit ASCII chars /// being returned, otherwise the conversion into the RawByteString might /// result in strange characters in the returned result. /// function CalcFile(const FileName: string; Format: TDECFormatClass = nil; const OnProgress:TDECProgressEvent = nil): RawByteString; overload; /// /// Defines the byte used in the KDF methods to padd the end of the data /// if the length of the data cannot be divided by required size for the /// hash algorithm without reminder /// property PaddingByte: Byte read GetPaddingByte write SetPaddingByte; end; /// /// Returns the passed hash class type if it is not nil. Otherwise the /// class type class set per SetDefaultHashClass is being returned. If using /// the DECHash unit THash_SHA256 is registered in the initialization, otherwise /// nil might be returned! /// /// /// Class type of a hash class like THash_SHA256. If nil is passed the one set /// as default is returned. /// /// /// Passed class type or defined default hash class type, depending on /// HashClass parameter value. /// function ValidHash(HashClass: TDECHashClass = nil): TDECHashClass; /// /// Defines which cipher class to return by ValidCipher if passing nil to that /// /// /// Class type of a hash class to return by ValidHash if passing nil to /// that one. This parameter should not be nil! /// procedure SetDefaultHashClass(HashClass: TDECHashClass); implementation resourcestring sHashNotInitialized = 'Hash must be initialized'; sRaiseHashOverflowError = 'Hash Overflow: Too many bits processed'; sHashNoDefault = 'No default hash has been registered'; var /// /// Hash class returned by ValidHash if nil is passed as parameter to it /// FDefaultHashClass: TDECHashClass = nil; function ValidHash(HashClass: TDECHashClass): TDECHashClass; begin if Assigned(HashClass) then Result := HashClass else Result := FDefaultHashClass; if not Assigned(Result) then raise EDECHashException.CreateRes(@sHashNoDefault); end; procedure SetDefaultHashClass(HashClass: TDECHashClass); begin Assert(Assigned(HashClass), 'Do not set a nil default hash class!'); FDefaultHashClass := HashClass; end; { TDECHash } constructor TDECHash.Create; begin inherited; FBufferSize := 0; FBuffer := nil; end; destructor TDECHash.Destroy; begin SecureErase; FreeMem(FBuffer, FBufferSize); inherited Destroy; end; procedure TDECHash.SecureErase; begin ProtectBuffer(Digest^, DigestSize); if FBuffer = nil then ProtectBuffer(FBuffer^, FBufferSize); end; procedure TDECHash.Init; begin FBufferIndex := 0; if (FBuffer = nil) or (UInt32(FBufferSize) <> BlockSize) then begin FBufferSize := BlockSize; // ReallocMemory instead of ReallocMem due to C++ compatibility as per 10.1 help // It is necessary to reallocate the buffer as FreeMem in destructor wouldn't // accept a nil pointer on some platforms. FBuffer := ReallocMemory(FBuffer, FBufferSize); end; FillChar(FBuffer^, FBufferSize, 0); FillChar(FCount, SizeOf(FCount), 0); DoInit; end; class function TDECHash.IsPasswordHash: Boolean; begin // has to be overwritten by the base class for password hash algorithms result := false; end; procedure TDECHash.Done; begin DoDone; end; function TDECHash.GetPaddingByte: Byte; begin Result := FPaddingByte; end; procedure TDECHash.Increment8(var Value; Add: UInt32); // Value := Value + 8 * Add // Value is array[0..7] of UInt32 { TODO -oNormanNG -cCodeReview : !!Unbedingt noch einmal prüfen, ob das wirklich so alles stimmt!! Mein Versuch der Umsetzung von Increment8 in ASM. Die Implementierung zuvor hat immer Zugriffsverletzungen ausgelöst. Vermutung: die alte Implementierung lag ursprünglich ausserhalb der Klasse und wurde später in die Klasse verschoben. Dabei verändert sich aber die Nutzung der Register, da zusätzlich der SELF-Parameter in EAX übergeben wird. Beim Schreiben nach auf Value wurde dann in die Instanz (Self) geschrieben -> peng } {$IF defined(X86ASM) or defined(X64ASM)} {$IFDEF X86ASM} // type TData = packed array[0..7] of UInt32; 8x32bit // TypeOf Param "Value" = TData // // EAX = Self // EDX = Pointer to "Value" // ECX = Value of "ADD" register; // redundant but informative asm LEA EAX,[ECX*8] // EAX := ADD * 8 SHR ECX,29 // 29bit nach rechts schieben, 3bit beiben stehen ADD [EDX].DWord[00],EAX // add [edx], eax TData(Value)[00] := TData(Value)[00] + EAX ADC [EDX].DWord[04],ECX // adc [edx+$04], ecx TData(Value)[04] := TData(Value)[04] + ECX + Carry ADC [EDX].DWord[08],0 // adc [edx+$08], 0 TData(Value)[08] := TData(Value)[08] + 0 + Carry ADC [EDX].DWord[12],0 // adc [edx+$0c], 0 TData(Value)[12] := TData(Value)[12] + 0 + Carry ADC [EDX].DWord[16],0 // adc [edx+$10], 0 TData(Value)[16] := TData(Value)[16] + 0 + Carry ADC [EDX].DWord[20],0 // adc [edx+$14], 0 TData(Value)[20] := TData(Value)[20] + 0 + Carry ADC [EDX].DWord[24],0 // adc [edx+$18], 0 TData(Value)[24] := TData(Value)[24] + 0 + Carry ADC [EDX].DWord[28],0 // adc [edx+$1c], 0 TData(Value)[28] := TData(Value)[28] + 0 + Carry JC RaiseHashOverflowError end; {$ENDIF !X86ASM} {$IFDEF X64ASM} // type TData = packed array[0..3] of UInt64; 4x64bit // TypeOf Param "Value" = TData // // RCX = Self // RDX = Pointer to "Value" // R8D = Value of "ADD" register; // redundant but informative asm SHL R8, 3 // R8 := Add * 8 the caller writes to R8D what automatically clears the high DWORD of R8 ADD QWORD PTR [RDX ], R8 // add [rdx], r8 TData(Value)[00] := TData(Value)[00] + R8 ADD QWORD PTR [RDX + 8], 0 // add [rdx+$08], 0 TData(Value)[08] := TData(Value)[08] + 0 + Carry ADD QWORD PTR [RDX + 16], 0 // add [rdx+$10], 0 TData(Value)[16] := TData(Value)[16] + 0 + Carry ADD QWORD PTR [RDX + 24], 0 // add [rdx+$18], 0 TData(Value)[24] := TData(Value)[24] + 0 + Carry JC RaiseHashOverflowError; end; {$ENDIF !X64ASM} {$ELSE PUREPASCAL} type TData = packed array[0..7] of UInt32; var HiBits: UInt32; Add8: UInt32; Carry: Boolean; procedure AddC(var Value: UInt32; const Add: UInt32; var Carry: Boolean); begin if Carry then begin Value := Value + 1; Carry := (Value = 0); // we might cause another overflow by adding the carry bit end else Carry := False; Value := Value + Add; Carry := Carry or (Value < Add); // set Carry Flag on overflow or keep it if already set end; begin HiBits := Add shr 29; // Save most significant 3 bits in case an overflow occurs Add8 := Add * 8; Carry := False; AddC(TData(Value)[0], Add8, Carry); AddC(TData(Value)[1], HiBits, Carry); AddC(TData(Value)[2], 0, Carry); AddC(TData(Value)[3], 0, Carry); AddC(TData(Value)[4], 0, Carry); AddC(TData(Value)[5], 0, Carry); AddC(TData(Value)[6], 0, Carry); AddC(TData(Value)[7], 0, Carry); if Carry then RaiseHashOverflowError; end; {$IFEND PUREPASCAL} procedure TDECHash.RaiseHashOverflowError; begin raise EDECHashException.CreateRes(@sRaiseHashOverflowError); end; procedure TDECHash.SetPaddingByte(Value: Byte); begin FPaddingByte := Value; end; procedure TDECHash.RaiseHashNotInitialized; begin raise EDECHashException.CreateRes(@sHashNotInitialized); end; procedure TDECHash.Calc(const Data; DataSize: Integer); var Remain: Integer; Value: PByte; begin if DataSize <= 0 then Exit; if not Assigned(FBuffer) then RaiseHashNotInitialized; Increment8(FCount, DataSize); Value := @TByteArray(Data)[0]; if FBufferIndex > 0 then begin Remain := FBufferSize - FBufferIndex; if DataSize < Remain then begin Move(Value^, FBuffer[FBufferIndex], DataSize); Inc(FBufferIndex, DataSize); Exit; end; Move(Value^, FBuffer[FBufferIndex], Remain); DoTransform(Pointer(FBuffer)); Dec(DataSize, Remain); Inc(Value, Remain); end; while DataSize >= FBufferSize do begin DoTransform(Pointer(Value)); Inc(Value, FBufferSize); Dec(DataSize, FBufferSize); end; Move(Value^, FBuffer^, DataSize); FBufferIndex := DataSize; end; function TDECHash.DigestAsBytes: TBytes; begin SetLength(Result, DigestSize); if DigestSize <> 0 then Move(Digest^, Result[0], DigestSize); end; function TDECHash.DigestAsRawByteString(Format: TDECFormatClass): RawByteString; begin Result := BytesToRawString(ValidFormat(Format).Encode(DigestAsBytes)); end; function TDECHash.DigestAsString(Format: TDECFormatClass): string; begin Result := StringOf(ValidFormat(Format).Encode(DigestAsBytes)); end; class function TDECHash.DigestSize: UInt32; begin // C++ does not support virtual static functions thus the base cannot be // marked 'abstract'. This is our workaround: raise EDECAbstractError.Create(GetShortClassName); end; class function TDECHash.BlockSize: UInt32; begin // C++ does not support virtual static functions thus the base cannot be // marked 'abstract'. This is our workaround: raise EDECAbstractError.Create(GetShortClassName); end; function TDECHash.CalcBuffer(const Buffer; BufferSize: Integer): TBytes; var DataPtr: PByte; begin Init; if (FFinalByteLength = 0) or (BufferSize = 0) then Calc(Buffer, BufferSize) else if (BufferSize > 0) then begin // Remember last byte as this might be required for padding for such // algorithms which have some automatic padding logic DataPtr := @Buffer; Inc(DataPtr, BufferSize - 1); FFinalByte := DataPtr^; // Last byte is incomplete so do not process normally Calc(Buffer, BufferSize-1); end; Done; Result := DigestAsBytes; end; function TDECHash.CalcBytes(const Data: TBytes): TBytes; begin SetLength(Result, 0); if Length(Data) > 0 then Result := CalcBuffer(Data[0], Length(Data)) else Result := CalcBuffer(Data, Length(Data)); end; function TDECHash.CalcString(const Value: string; Format: TDECFormatClass): string; var Size : Integer; Data : TBytes; begin Result := ''; if Length(Value) > 0 then begin {$IF CompilerVersion >= 24.0} Size := Length(Value) * SizeOf(Value[low(Value)]); Data := CalcBuffer(Value[low(Value)], Size); {$ELSE} Size := Length(Value) * SizeOf(Value[1]); Data := CalcBuffer(Value[1], Size); {$IFEND} Result := StringOf(ValidFormat(Format).Encode(Data)); end else begin SetLength(Data, 0); result := StringOf(ValidFormat(Format).Encode(CalcBuffer(Data, 0))); end; end; function TDECHash.CalcString(const Value: RawByteString; Format: TDECFormatClass): RawByteString; var Buf : TBytes; begin Result := ''; if Length(Value) > 0 then {$IF CompilerVersion >= 24.0} result := BytesToRawString( ValidFormat(Format).Encode( CalcBuffer(Value[low(Value)], Length(Value) * SizeOf(Value[low(Value)])))) {$ELSE} result := BytesToRawString( ValidFormat(Format).Encode( CalcBuffer(Value[1], Length(Value) * SizeOf(Value[1])))) {$IFEND} else begin SetLength(Buf, 0); Result := BytesToRawString(ValidFormat(Format).Encode(CalcBuffer(Buf, 0))); end; end; class function TDECHash.ClassByIdentity(Identity: Int64): TDECHashClass; begin Result := TDECHashClass(ClassList.ClassByIdentity(Identity)); end; class function TDECHash.ClassByName(const Name: string): TDECHashClass; begin Result := TDECHashClass(ClassList.ClassByName(Name)); end; procedure TDECHash.CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes; const OnProgress:TDECProgressEvent); var Buffer: TBytes; Bytes: Integer; Max, Pos: Int64; begin Assert(Assigned(Stream), 'Stream to calculate hash on is not assigned'); // Last byte is incomplete so it mustn't be processed if (FFinalByteLength > 0) then Dec(Size); Max := 0; SetLength(HashResult, 0); try Init; if StreamBufferSize <= 0 then StreamBufferSize := 8192; Pos := Stream.Position; if Size < 0 then Size := Stream.Size - Pos; Max := Pos + Size; if Assigned(OnProgress) then OnProgress(Max, 0, Started); Bytes := StreamBufferSize mod FBufferSize; if Bytes = 0 then Bytes := StreamBufferSize else Bytes := StreamBufferSize + FBufferSize - Bytes; if Bytes > Size then SetLength(Buffer, Size) else SetLength(Buffer, Bytes); while Size > 0 do begin Bytes := Length(Buffer); if Bytes > Size then Bytes := Size; Stream.ReadBuffer(Buffer[0], Bytes); Calc(Buffer[0], Bytes); Dec(Size, Bytes); Inc(Pos, Bytes); if Assigned(OnProgress) then OnProgress(Max, Pos, Processing); end; // Last byte is incomplete but algorithm may need its value for padding if (FFinalByteLength > 0) then Stream.ReadBuffer(FFinalByte, 1); Done; HashResult := DigestAsBytes; finally ProtectBytes(Buffer); if Assigned(OnProgress) then OnProgress(Max, Max, Finished); end; end; function TDECHash.CalcStream(const Stream: TStream; Size: Int64; Format: TDECFormatClass; const OnProgress:TDECProgressEvent): RawByteString; var Hash: TBytes; begin CalcStream(Stream, Size, Hash, OnProgress); Result := BytesToRawString(ValidFormat(Format).Encode(Hash)); end; procedure TDECHash.CalcFile(const FileName: string; var HashResult: TBytes; const OnProgress:TDECProgressEvent); var S: TFileStream; begin SetLength(HashResult, 0); S := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone); try CalcStream(S, S.Size, HashResult, OnProgress); finally S.Free; end; end; function TDECHash.CalcFile(const FileName: string; Format: TDECFormatClass; const OnProgress:TDECProgressEvent): RawByteString; var Hash: TBytes; begin CalcFile(FileName, Hash, OnProgress); Result := BytesToRawString(ValidFormat(Format).Encode(Hash)); end; {$IFDEF DELPHIORBCB} procedure ModuleUnload(Instance: NativeInt); var // automaticaly deregistration/releasing i: Integer; begin if TDECHash.ClassList <> nil then begin for i := TDECHash.ClassList.Count - 1 downto 0 do begin if NativeInt(FindClassHInstance(TClass(TDECHash.ClassList[i]))) = Instance then TDECHash.ClassList.Remove(TDECFormat.ClassList[i].Identity); end; end; end; {$ENDIF DELPHIORBCB} initialization // Code for packages and dynamic extension of the class registration list {$IFDEF DELPHIORBCB} AddModuleUnloadProc(ModuleUnload); {$ENDIF DELPHIORBCB} TDECHash.ClassList := TDECClassList.Create; finalization // Ensure no further instances of classes registered in the registration list // are possible through the list after this unit has been unloaded by unloding // the package this unit is in {$IFDEF DELPHIORBCB} RemoveModuleUnloadProc(ModuleUnload); {$ENDIF DELPHIORBCB} TDECHash.ClassList.Free; end.