{ *********************************************************************** } { } { Delphi / Kylix Cross-Platform Runtime Library } { System Utilities Unit } { } { Copyright (c) 1995-2004 Borland Software Corporation } { } { Copyright and license exceptions noted in source } { } { *********************************************************************** } unit SysUtils; {$H+} {$WARN SYMBOL_PLATFORM OFF} {$WARN UNSAFE_TYPE OFF} interface uses {$IFDEF MSWINDOWS} Windows, kol, {$ENDIF} {$IFDEF LINUX} Types, Libc, {$ENDIF} SysConst; const { File open modes } {$IFDEF LINUX} fmOpenRead = O_RDONLY; fmOpenWrite = O_WRONLY; fmOpenReadWrite = O_RDWR; // fmShareCompat not supported fmShareExclusive = $0010; fmShareDenyWrite = $0020; // fmShareDenyRead not supported fmShareDenyNone = $0030; {$ENDIF} {$IFDEF MSWINDOWS} fmOpenRead = $0000; fmOpenWrite = $0001; fmOpenReadWrite = $0002; fmShareCompat = $0000 platform; // DOS compatibility mode is not portable fmShareExclusive = $0010; fmShareDenyWrite = $0020; fmShareDenyRead = $0030 platform; // write-only not supported on all platforms fmShareDenyNone = $0040; {$ENDIF} { File attribute constants } faReadOnly = $00000001 platform; faHidden = $00000002 platform; faSysFile = $00000004 platform; faVolumeID = $00000008 platform deprecated; // not used in Win32 faDirectory = $00000010; faArchive = $00000020 platform; faSymLink = $00000040 platform; faAnyFile = $0000003F; { Units of time } HoursPerDay = 24; MinsPerHour = 60; SecsPerMin = 60; MSecsPerSec = 1000; MinsPerDay = HoursPerDay * MinsPerHour; SecsPerDay = MinsPerDay * SecsPerMin; MSecsPerDay = SecsPerDay * MSecsPerSec; { Days between 1/1/0001 and 12/31/1899 } DateDelta = 693594; { Days between TDateTime basis (12/31/1899) and Unix time_t basis (1/1/1970) } UnixDateDelta = 25569; type { Standard Character set type } TSysCharSet = set of Char; { Set access to an integer } TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1; { Type conversion records } WordRec = packed record case Integer of 0: (Lo, Hi: Byte); 1: (Bytes: array [0..1] of Byte); end; LongRec = packed record case Integer of 0: (Lo, Hi: Word); 1: (Words: array [0..1] of Word); 2: (Bytes: array [0..3] of Byte); end; Int64Rec = packed record case Integer of 0: (Lo, Hi: Cardinal); 1: (Cardinals: array [0..1] of Cardinal); 2: (Words: array [0..3] of Word); 3: (Bytes: array [0..7] of Byte); end; { General arrays } PByteArray = ^TByteArray; TByteArray = array[0..32767] of Byte; PWordArray = ^TWordArray; TWordArray = array[0..16383] of Word; { Generic procedure pointer } TProcedure = procedure; { Generic filename type } TFileName = type string; { Search record used by FindFirst, FindNext, and FindClose } TSearchRec = record Time: Integer; Size: Integer; Attr: Integer; Name: TFileName; ExcludeAttr: Integer; {$IFDEF MSWINDOWS} FindHandle: THandle platform; FindData: TWin32FindData platform; {$ENDIF} {$IFDEF LINUX} Mode: mode_t platform; FindHandle: Pointer platform; PathOnly: String platform; Pattern: String platform; {$ENDIF} end; { FloatToText, FloatToTextFmt, TextToFloat, and FloatToDecimal type codes } TFloatValue = (fvExtended, fvCurrency); { FloatToText format codes } TFloatFormat = (ffGeneral, ffExponent, ffFixed, ffNumber, ffCurrency); { FloatToDecimal result record } TFloatRec = packed record Exponent: Smallint; Negative: Boolean; Digits: array[0..20] of Char; end; { Date and time record } TTimeStamp = record Time: Integer; { Number of milliseconds since midnight } Date: Integer; { One plus number of days since 1/1/0001 } end; { MultiByte Character Set (MBCS) byte type } TMbcsByteType = (mbSingleByte, mbLeadByte, mbTrailByte); { System Locale information record } TSysLocale = packed record DefaultLCID: Integer; PriLangID: Integer; SubLangID: Integer; FarEast: Boolean; MiddleEast: Boolean; end; {$IFDEF MSWINDOWS} { This is used by TLanguages } TLangRec = packed record FName: string; FLCID: LCID; FExt: string; end; { This stores the languages that the system supports } TLanguages = class private FSysLangs: array of TLangRec; function LocalesCallback(LocaleID: PChar): Integer; stdcall; function GetExt(Index: Integer): string; function GetID(Index: Integer): string; function GetLCID(Index: Integer): LCID; function GetName(Index: Integer): string; function GetNameFromLocaleID(ID: LCID): string; function GetNameFromLCID(const ID: string): string; function GetCount: integer; public constructor Create; function IndexOf(ID: LCID): Integer; property Count: Integer read GetCount; property Name[Index: Integer]: string read GetName; property NameFromLocaleID[ID: LCID]: string read GetNameFromLocaleID; property NameFromLCID[const ID: string]: string read GetNameFromLCID; property ID[Index: Integer]: string read GetID; property LocaleID[Index: Integer]: LCID read GetLCID; property Ext[Index: Integer]: string read GetExt; end platform; {$ENDIF} {$IFDEF LINUX} TEraRange = record StartDate : Integer; // whole days since 12/31/1899 (TDateTime basis) EndDate : Integer; // whole days since 12/31/1899 (TDateTime basis) // Direction : Char; end; {$ENDIF} { Exceptions } Exception = class(TObject) private FMessage: string; FHelpContext: Integer; public constructor Create(const Msg: string); constructor CreateFmt(const Msg: string; const Args: array of const); constructor CreateRes(Ident: Integer); overload; constructor CreateRes(const ResStringRec: string); overload; constructor CreateResFmt(Ident: Integer; const Args: array of const); overload; constructor CreateResFmt(const ResStringRec: string; const Args: array of const); overload; constructor CreateHelp(const Msg: string; AHelpContext: Integer); constructor CreateFmtHelp(const Msg: string; const Args: array of const; AHelpContext: Integer); constructor CreateResHelp(Ident: Integer; AHelpContext: Integer); overload; constructor CreateResHelp(ResStringRec: PResStringRec; AHelpContext: Integer); overload; constructor CreateResFmtHelp(ResStringRec: PResStringRec; const Args: array of const; AHelpContext: Integer); overload; constructor CreateResFmtHelp(Ident: Integer; const Args: array of const; AHelpContext: Integer); overload; property HelpContext: Integer read FHelpContext write FHelpContext; property Message: string read FMessage write FMessage; end; ExceptClass = class of Exception; EAbort = class(Exception); EHeapException = class(Exception) private AllowFree: Boolean; public procedure FreeInstance; override; end; EOutOfMemory = class(EHeapException); EInOutError = class(Exception) public ErrorCode: Integer; end; {$IFDEF MSWINDOWS} PExceptionRecord = ^TExceptionRecord; TExceptionRecord = record ExceptionCode: Cardinal; ExceptionFlags: Cardinal; ExceptionRecord: PExceptionRecord; ExceptionAddress: Pointer; NumberParameters: Cardinal; ExceptionInformation: array[0..14] of Cardinal; end; {$ENDIF} EExternal = class(Exception) public {$IFDEF MSWINDOWS} ExceptionRecord: PExceptionRecord platform; {$ENDIF} {$IFDEF LINUX} ExceptionAddress: LongWord platform; AccessAddress: LongWord platform; SignalNumber: Integer platform; {$ENDIF} end; EExternalException = class(EExternal); EIntError = class(EExternal); EDivByZero = class(EIntError); ERangeError = class(EIntError); EIntOverflow = class(EIntError); EMathError = class(EExternal); EInvalidOp = class(EMathError); EZeroDivide = class(EMathError); EOverflow = class(EMathError); EUnderflow = class(EMathError); EInvalidPointer = class(EHeapException); EInvalidCast = class(Exception); EConvertError = class(Exception); EAccessViolation = class(EExternal); EPrivilege = class(EExternal); EStackOverflow = class(EExternal) end deprecated; EControlC = class(EExternal); {$IFDEF LINUX} EQuit = class(EExternal) end platform; {$ENDIF} {$IFDEF LINUX} ECodesetConversion = class(Exception) end platform; {$ENDIF} EVariantError = class(Exception); EPropReadOnly = class(Exception); EPropWriteOnly = class(Exception); EAssertionFailed = class(Exception); {$IFNDEF PC_MAPPED_EXCEPTIONS} EAbstractError = class(Exception) end platform; {$ENDIF} EIntfCastError = class(Exception); EInvalidContainer = class(Exception); EInvalidInsert = class(Exception); EPackageError = class(Exception); EOSError = class(Exception) public ErrorCode: DWORD; end; {$IFDEF MSWINDOWS} EWin32Error = class(EOSError) end deprecated; {$ENDIF} ESafecallException = class(Exception); {$IFDEF LINUX} { Signals External exceptions, or signals, are, by default, converted to language exceptions by the Delphi RTL. Under Linux, a Delphi application installs signal handlers to trap the raw signals, and convert them. Delphi libraries do not install handlers by default. So if you are implementing a standalone library, such as an Apache DSO, and you want to have signals converted to language exceptions that you can catch, you must install signal hooks manually, using the interfaces that the Delphi RTL provides. For most libraries, installing signal handlers is pretty straightforward. Call HookSignal(RTL_SIGDEFAULT) at initialization time, and UnhookSignal(RTL_SIGNALDEFAULT) at shutdown. This will install handlers for a set of signals that the RTL normally hooks for Delphi applications. There are some cases where the above initialization will not work properly: The proper behaviour for setting up signal handlers is to set a signal handler, and then later restore the signal handler to its previous state when you clean up. If you have two libraries lib1 and lib2, and lib1 installs a signal handler, and then lib2 installs a signal handler, those libraries have to uninstall in the proper order if they restore signal handlers, or the signal handlers can be left in an inconsistent and potentially fatal state. Not all libraries behave well with respect to installing signal handlers. To hedge against this possibility, and allow you to manage signal handlers better in the face of whatever behaviour you may find in external libraries, we provide a set of four interfaces to allow you to tailor the Delphi signal handler hooking/unhooking in the event of an emergency. These are: InquireSignal AbandonSignalHandler HookSignal UnhookSignal InquireSignal allows you to look at the state of a signal handler, so that you can find out if someone grabbed it out from under you. AbandonSignalHandler tells the RTL never to unhook a particular signal handler. This can be used if you find a case where it would be unsafe to return to the previous state of signal handling. For example, if the previous signal handler was installed by a library which has since been unloaded. HookSignal/UnhookSignal setup signal handlers that map certain signals into language exceptions. See additional notes at InquireSignal, et al, below. } const RTL_SIGINT = 0; // User interrupt (SIGINT) RTL_SIGFPE = 1; // Floating point exception (SIGFPE) RTL_SIGSEGV = 2; // Segmentation violation (SIGSEGV) RTL_SIGILL = 3; // Illegal instruction (SIGILL) RTL_SIGBUS = 4; // Bus error (SIGBUS) RTL_SIGQUIT = 5; // User interrupt (SIGQUIT) RTL_SIGLAST = RTL_SIGQUIT; // Used internally. Don't use this. RTL_SIGDEFAULT = -1; // Means all of a set of signals that the we capture // normally. This is currently all of the preceding // signals. You cannot pass this to InquireSignal. type { TSignalState is the state of a given signal handler, as returned by InquireSignal. See InquireSignal, below. } TSignalState = (ssNotHooked, ssHooked, ssOverridden); var { If DeferUserInterrupts is set, we do not raise either SIGINT or SIGQUIT as an exception, instead, we set SIGINTIssued or SIGQUITIssued when the signal arrives, and swallow the signal where the OS issued it. This gives GUI applications the chance to defer the actual handling of the signal until a time when it is safe to do so. } DeferUserInterrupts: Boolean; SIGINTIssued: Boolean; SIGQUITIssued: Boolean; {$ENDIF} {$IFDEF LINUX} const MAX_PATH = 4095; // From /usr/include/linux/limits.h PATH_MAX {$ENDIF} var { Empty string and null string pointer. These constants are provided for backwards compatibility only. } EmptyStr: string = ''; NullStr: PString = @EmptyStr; EmptyWideStr: WideString = ''; NullWideStr: PWideString = @EmptyWideStr; {$IFDEF MSWINDOWS} { Win32 platform identifier. This will be one of the following values: VER_PLATFORM_WIN32s VER_PLATFORM_WIN32_WINDOWS VER_PLATFORM_WIN32_NT See WINDOWS.PAS for the numerical values. } Win32Platform: Integer = 0; { Win32 OS version information - see TOSVersionInfo.dwMajorVersion/dwMinorVersion/dwBuildNumber } Win32MajorVersion: Integer = 0; Win32MinorVersion: Integer = 0; Win32BuildNumber: Integer = 0; { Win32 OS extra version info string - see TOSVersionInfo.szCSDVersion } Win32CSDVersion: string = ''; { Win32 OS version tester } function CheckWin32Version(AMajor: Integer; AMinor: Integer = 0): Boolean; { GetFileVersion returns the most significant 32 bits of a file's binary version number. Typically, this includes the major and minor version placed together in one 32-bit integer. It generally does not include the release or build numbers. It returns Cardinal(-1) if it failed. } function GetFileVersion(const AFileName: string): Cardinal; {$ENDIF} { Currency and date/time formatting options The initial values of these variables are fetched from the system registry using the GetLocaleInfo function in the Win32 API. The description of each variable specifies the LOCALE_XXXX constant used to fetch the initial value. CurrencyString - Defines the currency symbol used in floating-point to decimal conversions. The initial value is fetched from LOCALE_SCURRENCY. CurrencyFormat - Defines the currency symbol placement and separation used in floating-point to decimal conversions. Possible values are: 0 = '$1' 1 = '1$' 2 = '$ 1' 3 = '1 $' The initial value is fetched from LOCALE_ICURRENCY. NegCurrFormat - Defines the currency format for used in floating-point to decimal conversions of negative numbers. Possible values are: 0 = '($1)' 4 = '(1$)' 8 = '-1 $' 12 = '$ -1' 1 = '-$1' 5 = '-1$' 9 = '-$ 1' 13 = '1- $' 2 = '$-1' 6 = '1-$' 10 = '1 $-' 14 = '($ 1)' 3 = '$1-' 7 = '1$-' 11 = '$ 1-' 15 = '(1 $)' The initial value is fetched from LOCALE_INEGCURR. ThousandSeparator - The character used to separate thousands in numbers with more than three digits to the left of the decimal separator. The initial value is fetched from LOCALE_STHOUSAND. A value of #0 indicates no thousand separator character should be output even if the format string specifies thousand separators. DecimalSeparator - The character used to separate the integer part from the fractional part of a number. The initial value is fetched from LOCALE_SDECIMAL. DecimalSeparator must be a non-zero value. CurrencyDecimals - The number of digits to the right of the decimal point in a currency amount. The initial value is fetched from LOCALE_ICURRDIGITS. DateSeparator - The character used to separate the year, month, and day parts of a date value. The initial value is fetched from LOCATE_SDATE. ShortDateFormat - The format string used to convert a date value to a short string suitable for editing. For a complete description of date and time format strings, refer to the documentation for the FormatDate function. The short date format should only use the date separator character and the m, mm, d, dd, yy, and yyyy format specifiers. The initial value is fetched from LOCALE_SSHORTDATE. LongDateFormat - The format string used to convert a date value to a long string suitable for display but not for editing. For a complete description of date and time format strings, refer to the documentation for the FormatDate function. The initial value is fetched from LOCALE_SLONGDATE. TimeSeparator - The character used to separate the hour, minute, and second parts of a time value. The initial value is fetched from LOCALE_STIME. TimeAMString - The suffix string used for time values between 00:00 and 11:59 in 12-hour clock format. The initial value is fetched from LOCALE_S1159. TimePMString - The suffix string used for time values between 12:00 and 23:59 in 12-hour clock format. The initial value is fetched from LOCALE_S2359. ShortTimeFormat - The format string used to convert a time value to a short string with only hours and minutes. The default value is computed from LOCALE_ITIME and LOCALE_ITLZERO. LongTimeFormat - The format string used to convert a time value to a long string with hours, minutes, and seconds. The default value is computed from LOCALE_ITIME and LOCALE_ITLZERO. ShortMonthNames - Array of strings containing short month names. The mmm format specifier in a format string passed to FormatDate causes a short month name to be substituted. The default values are fecthed from the LOCALE_SABBREVMONTHNAME system locale entries. LongMonthNames - Array of strings containing long month names. The mmmm format specifier in a format string passed to FormatDate causes a long month name to be substituted. The default values are fecthed from the LOCALE_SMONTHNAME system locale entries. ShortDayNames - Array of strings containing short day names. The ddd format specifier in a format string passed to FormatDate causes a short day name to be substituted. The default values are fecthed from the LOCALE_SABBREVDAYNAME system locale entries. LongDayNames - Array of strings containing long day names. The dddd format specifier in a format string passed to FormatDate causes a long day name to be substituted. The default values are fecthed from the LOCALE_SDAYNAME system locale entries. ListSeparator - The character used to separate items in a list. The initial value is fetched from LOCALE_SLIST. TwoDigitYearCenturyWindow - Determines what century is added to two digit years when converting string dates to numeric dates. This value is subtracted from the current year before extracting the century. This can be used to extend the lifetime of existing applications that are inextricably tied to 2 digit year data entry. The best solution to Year 2000 (Y2k) issues is not to accept 2 digit years at all - require 4 digit years in data entry to eliminate century ambiguities. Examples: Current TwoDigitCenturyWindow Century StrToDate() of: Year Value Pivot '01/01/03' '01/01/68' '01/01/50' ------------------------------------------------------------------------- 1998 0 1900 1903 1968 1950 2002 0 2000 2003 2068 2050 1998 50 (default) 1948 2003 1968 1950 2002 50 (default) 1952 2003 1968 2050 2020 50 (default) 1970 2003 2068 2050 } var CurrencyString: string; CurrencyFormat: Byte; NegCurrFormat: Byte; ThousandSeparator: Char; DecimalSeparator: Char; CurrencyDecimals: Byte; DateSeparator: Char; ShortDateFormat: string; LongDateFormat: string; TimeSeparator: Char; TimeAMString: string; TimePMString: string; ShortTimeFormat: string; LongTimeFormat: string; ShortMonthNames: array[1..12] of string; LongMonthNames: array[1..12] of string; ShortDayNames: array[1..7] of string; LongDayNames: array[1..7] of string; SysLocale: TSysLocale; TwoDigitYearCenturyWindow: Word = 50; ListSeparator: Char; { Thread safe currency and date/time formatting The TFormatSettings record is designed to allow thread safe formatting, equivalent to the gloabal variables described above. Each of the formatting routines that use the gloabal variables have overloaded equivalents, requiring an additional parameter of type TFormatSettings. A TFormatSettings record must be populated before use. This can be done using the GetLocaleFormatSettings function, which will populate the record with values based on the given locale (using the Win32 API function GetLocaleInfo). Note that some format specifiers still require specific thread locale settings (such as period/era names). } type TFormatSettings = record CurrencyFormat: Byte; NegCurrFormat: Byte; ThousandSeparator: Char; DecimalSeparator: Char; CurrencyDecimals: Byte; DateSeparator: Char; TimeSeparator: Char; ListSeparator: Char; CurrencyString: string; ShortDateFormat: string; LongDateFormat: string; TimeAMString: string; TimePMString: string; ShortTimeFormat: string; LongTimeFormat: string; ShortMonthNames: array[1..12] of string; LongMonthNames: array[1..12] of string; ShortDayNames: array[1..7] of string; LongDayNames: array[1..7] of string; TwoDigitYearCenturyWindow: Word; end; TLocaleOptions = (loInvariantLocale, loUserLocale); const MaxEraCount = 7; var EraNames: array [1..MaxEraCount] of string; EraYearOffsets: array [1..MaxEraCount] of Integer; {$IFDEF LINUX} EraRanges : array [1..MaxEraCount] of TEraRange platform; EraYearFormats: array [1..MaxEraCount] of string platform; EraCount: Byte platform; {$ENDIF} const PathDelim = {$IFDEF MSWINDOWS} '\'; {$ELSE} '/'; {$ENDIF} DriveDelim = {$IFDEF MSWINDOWS} ':'; {$ELSE} ''; {$ENDIF} PathSep = {$IFDEF MSWINDOWS} ';'; {$ELSE} ':'; {$ENDIF} {$IFDEF MSWINDOWS} function Languages: TLanguages; {$ENDIF} { Memory management routines } { AllocMem allocates a block of the given size on the heap. Each byte in the allocated buffer is set to zero. To dispose the buffer, use the FreeMem standard procedure. } function AllocMem(Size: Cardinal): Pointer; { Exit procedure handling } { AddExitProc adds the given procedure to the run-time library's exit procedure list. When an application terminates, its exit procedures are executed in reverse order of definition, i.e. the last procedure passed to AddExitProc is the first one to get executed upon termination. } procedure AddExitProc(Proc: TProcedure); { String handling routines } { NewStr allocates a string on the heap. NewStr is provided for backwards compatibility only. } function NewStr(const S: string): PString; deprecated; { DisposeStr disposes a string pointer that was previously allocated using NewStr. DisposeStr is provided for backwards compatibility only. } procedure DisposeStr(P: PString); deprecated; { AssignStr assigns a new dynamically allocated string to the given string pointer. AssignStr is provided for backwards compatibility only. } procedure AssignStr(var P: PString; const S: string); deprecated; { AppendStr appends S to the end of Dest. AppendStr is provided for backwards compatibility only. Use "Dest := Dest + S" instead. } procedure AppendStr(var Dest: string; const S: string); deprecated; { UpperCase converts all ASCII characters in the given string to upper case. The conversion affects only 7-bit ASCII characters between 'a' and 'z'. To convert 8-bit international characters, use AnsiUpperCase. } function UpperCase(const S: string): string; overload; function UpperCase(const S: string; LocaleOptions: TLocaleOptions): string; overload; inline; { LowerCase converts all ASCII characters in the given string to lower case. The conversion affects only 7-bit ASCII characters between 'A' and 'Z'. To convert 8-bit international characters, use AnsiLowerCase. } function LowerCase(const S: string): string; overload; function LowerCase(const S: string; LocaleOptions: TLocaleOptions): string; overload; inline; { CompareStr compares S1 to S2, with case-sensitivity. The return value is less than 0 if S1 < S2, 0 if S1 = S2, or greater than 0 if S1 > S2. The compare operation is based on the 8-bit ordinal value of each character and is not affected by the current user locale. } function CompareStr(const S1, S2: string): Integer; overload; function CompareStr(const S1, S2: string; LocaleOptions: TLocaleOptions): Integer; overload; { SameStr compares S1 to S2, with case-sensitivity. Returns true if S1 and S2 are the equal, that is, if CompareStr would return 0. } function SameStr(const S1, S2: string): Boolean; overload; function SameStr(const S1, S2: string; LocaleOptions: TLocaleOptions): Boolean; overload; { CompareMem performs a binary compare of Length bytes of memory referenced by P1 to that of P2. CompareMem returns True if the memory referenced by P1 is identical to that of P2. } function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; assembler; { CompareText compares S1 to S2, without case-sensitivity. The return value is the same as for CompareStr. The compare operation is based on the 8-bit ordinal value of each character, after converting 'a'..'z' to 'A'..'Z', and is not affected by the current user locale. } function CompareText(const S1, S2: string): Integer; overload; function CompareText(const S1, S2: string; LocaleOptions: TLocaleOptions): Integer; overload; { SameText compares S1 to S2, without case-sensitivity. Returns true if S1 and S2 are the equal, that is, if CompareText would return 0. SameText has the same 8-bit limitations as CompareText } function SameText(const S1, S2: string): Boolean; overload; function SameText(const S1, S2: string; LocaleOptions: TLocaleOptions): Boolean; overload; { AnsiUpperCase converts all characters in the given string to upper case. The conversion uses the current user locale. } function AnsiUpperCase(const S: string): string; { AnsiLowerCase converts all characters in the given string to lower case. The conversion uses the current user locale. } function AnsiLowerCase(const S: string): string; { AnsiCompareStr compares S1 to S2, with case-sensitivity. The compare operation is controlled by the current user locale. The return value is the same as for CompareStr. } function AnsiCompareStr(const S1, S2: string): Integer; inline; { AnsiSameStr compares S1 to S2, with case-sensitivity. The compare operation is controlled by the current user locale. The return value is True if AnsiCompareStr would have returned 0. } function AnsiSameStr(const S1, S2: string): Boolean; inline; { AnsiCompareText compares S1 to S2, without case-sensitivity. The compare operation is controlled by the current user locale. The return value is the same as for CompareStr. } function AnsiCompareText(const S1, S2: string): Integer; inline; { AnsiSameText compares S1 to S2, without case-sensitivity. The compare operation is controlled by the current user locale. The return value is True if AnsiCompareText would have returned 0. } function AnsiSameText(const S1, S2: string): Boolean; inline; { AnsiStrComp compares S1 to S2, with case-sensitivity. The compare operation is controlled by the current user locale. The return value is the same as for CompareStr. } function AnsiStrComp(S1, S2: PChar): Integer; inline; { AnsiStrIComp compares S1 to S2, without case-sensitivity. The compare operation is controlled by the current user locale. The return value is the same as for CompareStr. } function AnsiStrIComp(S1, S2: PChar): Integer; inline; { AnsiStrLComp compares S1 to S2, with case-sensitivity, up to a maximum length of MaxLen bytes. The compare operation is controlled by the current user locale. The return value is the same as for CompareStr. } function AnsiStrLComp(S1, S2: PChar; MaxLen: Cardinal): Integer; { AnsiStrLIComp compares S1 to S2, without case-sensitivity, up to a maximum length of MaxLen bytes. The compare operation is controlled by the current user locale. The return value is the same as for CompareStr. } function AnsiStrLIComp(S1, S2: PChar; MaxLen: Cardinal): Integer; { AnsiStrLower converts all characters in the given string to lower case. The conversion uses the current user locale. } function AnsiStrLower(Str: PChar): PChar; { AnsiStrUpper converts all characters in the given string to upper case. The conversion uses the current user locale. } function AnsiStrUpper(Str: PChar): PChar; { AnsiLastChar returns a pointer to the last full character in the string. This function supports multibyte characters } function AnsiLastChar(const S: string): PChar; { AnsiStrLastChar returns a pointer to the last full character in the string. This function supports multibyte characters. } function AnsiStrLastChar(P: PChar): PChar; { WideUpperCase converts all characters in the given string to upper case. } function WideUpperCase(const S: WideString): WideString; { WideLowerCase converts all characters in the given string to lower case. } function WideLowerCase(const S: WideString): WideString; { WideCompareStr compares S1 to S2, with case-sensitivity. The return value is the same as for CompareStr. } function WideCompareStr(const S1, S2: WideString): Integer; { WideSameStr compares S1 to S2, with case-sensitivity. The return value is True if WideCompareStr would have returned 0. } function WideSameStr(const S1, S2: WideString): Boolean; inline; { WideCompareText compares S1 to S2, without case-sensitivity. The return value is the same as for CompareStr. } function WideCompareText(const S1, S2: WideString): Integer; { WideSameText compares S1 to S2, without case-sensitivity. The return value is True if WideCompareText would have returned 0. } function WideSameText(const S1, S2: WideString): Boolean; inline; { Trim trims leading and trailing spaces and control characters from the given string. } function Trim(const S: string): string; overload; function Trim(const S: WideString): WideString; overload; { TrimLeft trims leading spaces and control characters from the given string. } function TrimLeft(const S: string): string; overload; function TrimLeft(const S: WideString): WideString; overload; { TrimRight trims trailing spaces and control characters from the given string. } function TrimRight(const S: string): string; overload; function TrimRight(const S: WideString): WideString; overload; { QuotedStr returns the given string as a quoted string. A single quote character is inserted at the beginning and the end of the string, and for each single quote character in the string, another one is added. } function QuotedStr(const S: string): string; { AnsiQuotedStr returns the given string as a quoted string, using the provided Quote character. A Quote character is inserted at the beginning and end of the string, and each Quote character in the string is doubled. This function supports multibyte character strings (MBCS). } function AnsiQuotedStr(const S: string; Quote: Char): string; { AnsiExtractQuotedStr removes the Quote characters from the beginning and end of a quoted string, and reduces pairs of Quote characters within the quoted string to a single character. If the first character in Src is not the Quote character, the function returns an empty string. The function copies characters from the Src to the result string until the second solitary Quote character or the first null character in Src. The Src parameter is updated to point to the first character following the quoted string. If the Src string does not contain a matching end Quote character, the Src parameter is updated to point to the terminating null character in Src. This function supports multibyte character strings (MBCS). } function AnsiExtractQuotedStr(var Src: PChar; Quote: Char): string; { AnsiDequotedStr is a simplified version of AnsiExtractQuotedStr } function AnsiDequotedStr(const S: string; AQuote: Char): string; { AdjustLineBreaks adjusts all line breaks in the given string to the indicated style. When Style is tlbsCRLF, the function changes all CR characters not followed by LF and all LF characters not preceded by a CR into CR/LF pairs. When Style is tlbsLF, the function changes all CR/LF pairs and CR characters not followed by LF to LF characters. } function AdjustLineBreaks(const S: string; Style: TTextLineBreakStyle = {$IFDEF LINUX} tlbsLF {$ENDIF} {$IFDEF MSWINDOWS} tlbsCRLF {$ENDIF}): string; { IsValidIdent returns true if the given string is a valid identifier. An identifier is defined as a character from the set ['A'..'Z', 'a'..'z', '_'] followed by zero or more characters from the set ['A'..'Z', 'a'..'z', '0..'9', '_']. With DotNet code we need to allow dots in the names.} function IsValidIdent(const Ident: string; AllowDots: Boolean = False): Boolean; { IntToStr converts the given value to its decimal string representation. } function IntToStr(Value: Integer): string; overload; function IntToStr(Value: Int64): string; overload; { IntToHex converts the given value to a hexadecimal string representation with the minimum number of digits specified. } function IntToHex(Value: Integer; Digits: Integer): string; overload; function IntToHex(Value: Int64; Digits: Integer): string; overload; { StrToInt converts the given string to an integer value. If the string doesn't contain a valid value, an EConvertError exception is raised. } function StrToInt(const S: string): Integer; function StrToIntDef(const S: string; Default: Integer): Integer; function TryStrToInt(const S: string; out Value: Integer): Boolean; { Similar to the above functions but for Int64 instead } function StrToInt64(const S: string): Int64; function StrToInt64Def(const S: string; const Default: Int64): Int64; function TryStrToInt64(const S: string; out Value: Int64): Boolean; { StrToBool converts the given string to a boolean value. If the string doesn't contain a valid value, an EConvertError exception is raised. BoolToStr converts boolean to a string value that in turn can be converted back into a boolean. BoolToStr will always pick the first element of the TrueStrs/FalseStrs arrays. } var TrueBoolStrs: array of String; FalseBoolStrs: array of String; const DefaultTrueBoolStr = 'True'; // DO NOT LOCALIZE DefaultFalseBoolStr = 'False'; // DO NOT LOCALIZE function StrToBool(const S: string): Boolean; function StrToBoolDef(const S: string; const Default: Boolean): Boolean; function TryStrToBool(const S: string; out Value: Boolean): Boolean; function BoolToStr(B: Boolean; UseBoolStrs: Boolean = False): string; { LoadStr loads the string resource given by Ident from the application's executable file or associated resource module. If the string resource does not exist, LoadStr returns an empty string. } function LoadStr(Ident: Integer): string; { FmtLoadStr loads the string resource given by Ident from the application's executable file or associated resource module, and uses it as the format string in a call to the Format function with the given arguments. } function FmtLoadStr(Ident: Integer; const Args: array of const): string; { File management routines } { FileOpen opens the specified file using the specified access mode. The access mode value is constructed by OR-ing one of the fmOpenXXXX constants with one of the fmShareXXXX constants. If the return value is positive, the function was successful and the value is the file handle of the opened file. A return value of -1 indicates that an error occurred. } function FileOpen(const FileName: string; Mode: LongWord): Integer; { FileCreate creates a new file by the specified name. If the return value is positive, the function was successful and the value is the file handle of the new file. A return value of -1 indicates that an error occurred. On Linux, this calls FileCreate(FileName, DEFFILEMODE) to create the file with read and write access for the current user only. } function FileCreate(const FileName: string): Integer; overload; inline; { This second version of FileCreate lets you specify the access rights to put on the newly created file. The access rights parameter is ignored on Win32 } function FileCreate(const FileName: string; Rights: Integer): Integer; overload; inline; { FileRead reads Count bytes from the file given by Handle into the buffer specified by Buffer. The return value is the number of bytes actually read; it is less than Count if the end of the file was reached. The return value is -1 if an error occurred. } function FileRead(Handle: Integer; var Buffer; Count: LongWord): Integer; { FileWrite writes Count bytes to the file given by Handle from the buffer specified by Buffer. The return value is the number of bytes actually written, or -1 if an error occurred. } function FileWrite(Handle: Integer; const Buffer; Count: LongWord): Integer; { FileSeek changes the current position of the file given by Handle to be Offset bytes relative to the point given by Origin. Origin = 0 means that Offset is relative to the beginning of the file, Origin = 1 means that Offset is relative to the current position, and Origin = 2 means that Offset is relative to the end of the file. The return value is the new current position, relative to the beginning of the file, or -1 if an error occurred. } function FileSeek(Handle, Offset, Origin: Integer): Integer; overload; function FileSeek(Handle: Integer; const Offset: Int64; Origin: Integer): Int64; overload; { FileClose closes the specified file. } procedure FileClose(Handle: Integer); inline; { FileAge returns the date-and-time stamp of the specified file. The return value can be converted to a TDateTime value using the FileDateToDateTime function. The return value is -1 if the file does not exist. } function FileAge(const FileName: string): Integer; { FileExists returns a boolean value that indicates whether the specified file exists. } function FileExists(const FileName: string): Boolean; inline; { DirectoryExists returns a boolean value that indicates whether the specified directory exists (and is actually a directory) } function DirectoryExists(const Directory: string): Boolean; { ForceDirectories ensures that all the directories in a specific path exist. Any portion that does not already exist will be created. Function result indicates success of the operation. The function can fail if the current user does not have sufficient file access rights to create directories in the given path. } function ForceDirectories(Dir: string): Boolean; { FindFirst searches the directory given by Path for the first entry that matches the filename given by Path and the attributes given by Attr. The result is returned in the search record given by SearchRec. The return value is zero if the function was successful. Otherwise the return value is a system error code. After calling FindFirst, always call FindClose. FindFirst is typically used with FindNext and FindClose as follows: Result := FindFirst(Path, Attr, SearchRec); while Result = 0 do begin ProcessSearchRec(SearchRec); Result := FindNext(SearchRec); end; FindClose(SearchRec); where ProcessSearchRec represents user-defined code that processes the information in a search record. } function FindFirst(const Path: string; Attr: Integer; var F: TSearchRec): Integer; { FindNext returs the next entry that matches the name and attributes specified in a previous call to FindFirst. The search record must be one that was passed to FindFirst. The return value is zero if the function was successful. Otherwise the return value is a system error code. } function FindNext(var F: TSearchRec): Integer; { FindClose terminates a FindFirst/FindNext sequence and frees memory and system resources allocated by FindFirst. Every FindFirst/FindNext must end with a call to FindClose. } procedure FindClose(var F: TSearchRec); { FileGetDate returns the OS date-and-time stamp of the file given by Handle. The return value is -1 if the handle is invalid. The FileDateToDateTime function can be used to convert the returned value to a TDateTime value. } function FileGetDate(Handle: Integer): Integer; { FileSetDate sets the OS date-and-time stamp of the file given by FileName to the value given by Age. The DateTimeToFileDate function can be used to convert a TDateTime value to an OS date-and-time stamp. The return value is zero if the function was successful. Otherwise the return value is a system error code. } function FileSetDate(const FileName: string; Age: Integer): Integer; overload; {$IFDEF MSWINDOWS} { FileSetDate by handle is not available on Unix platforms because there is no standard way to set a file's modification time using only a file handle, and no standard way to obtain the file name of an open file handle. } function FileSetDate(Handle: Integer; Age: Integer): Integer; overload; platform; { FileGetAttr returns the file attributes of the file given by FileName. The attributes can be examined by AND-ing with the faXXXX constants defined above. A return value of -1 indicates that an error occurred. } function FileGetAttr(const FileName: string): Integer; platform; { FileSetAttr sets the file attributes of the file given by FileName to the value given by Attr. The attribute value is formed by OR-ing the appropriate faXXXX constants. The return value is zero if the function was successful. Otherwise the return value is a system error code. } function FileSetAttr(const FileName: string; Attr: Integer): Integer; platform; {$ENDIF} { FileIsReadOnly tests whether a given file is read-only for the current process and effective user id. If the file does not exist, the function returns False. (Check FileExists before calling FileIsReadOnly) This function is platform portable. } function FileIsReadOnly(const FileName: string): Boolean; inline; { FileSetReadOnly sets the read only state of a file. The file must exist and the current effective user id must be the owner of the file. On Unix systems, FileSetReadOnly attempts to set or remove all three (user, group, and other) write permissions on the file. If you want to grant partial permissions (writeable for owner but not for others), use platform specific functions such as chmod. The function returns True if the file was successfully modified, False if there was an error. This function is platform portable. } function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean; { DeleteFile deletes the file given by FileName. The return value is True if the file was successfully deleted, or False if an error occurred. } function DeleteFile(const FileName: string): Boolean; inline; { RenameFile renames the file given by OldName to the name given by NewName. The return value is True if the file was successfully renamed, or False if an error occurred. } function RenameFile(const OldName, NewName: string): Boolean; inline; { ChangeFileExt changes the extension of a filename. FileName specifies a filename with or without an extension, and Extension specifies the new extension for the filename. The new extension can be a an empty string or a period followed by up to three characters. } function ChangeFileExt(const FileName, Extension: string): string; { ExtractFilePath extracts the drive and directory parts of the given filename. The resulting string is the leftmost characters of FileName, up to and including the colon or backslash that separates the path information from the name and extension. The resulting string is empty if FileName contains no drive and directory parts. } function ExtractFilePath(const FileName: string): string; { ExtractFileDir extracts the drive and directory parts of the given filename. The resulting string is a directory name suitable for passing to SetCurrentDir, CreateDir, etc. The resulting string is empty if FileName contains no drive and directory parts. } function ExtractFileDir(const FileName: string): string; { ExtractFileDrive extracts the drive part of the given filename. For filenames with drive letters, the resulting string is ':'. For filenames with a UNC path, the resulting string is in the form '\\\'. If the given path contains neither style of filename, the result is an empty string. } function ExtractFileDrive(const FileName: string): string; { ExtractFileName extracts the name and extension parts of the given filename. The resulting string is the leftmost characters of FileName, starting with the first character after the colon or backslash that separates the path information from the name and extension. The resulting string is equal to FileName if FileName contains no drive and directory parts. } function ExtractFileName(const FileName: string): string; { ExtractFileExt extracts the extension part of the given filename. The resulting string includes the period character that separates the name and extension parts. The resulting string is empty if the given filename has no extension. } function ExtractFileExt(const FileName: string): string; { ExpandFileName expands the given filename to a fully qualified filename. The resulting string consists of a drive letter, a colon, a root relative directory path, and a filename. Embedded '.' and '..' directory references are removed. } function ExpandFileName(const FileName: string): string; { ExpandFilenameCase returns a fully qualified filename like ExpandFilename, but performs a case-insensitive filename search looking for a close match in the actual file system, differing only in uppercase versus lowercase of the letters. This is useful to convert lazy user input into useable file names, or to convert filename data created on a case-insensitive file system (Win32) to something useable on a case-sensitive file system (Linux). The MatchFound out parameter indicates what kind of match was found in the file system, and what the function result is based upon: ( in order of increasing difficulty or complexity ) mkExactMatch: Case-sensitive match. Result := ExpandFileName(FileName). mkSingleMatch: Exactly one file in the given directory path matches the given filename on a case-insensitive basis. Result := ExpandFileName(FileName as found in file system). mkAmbiguous: More than one file in the given directory path matches the given filename case-insensitively. In many cases, this should be considered an error. Result := ExpandFileName(First matching filename found). mkNone: File not found at all. Result := ExpandFileName(FileName). Note that because this function has to search the file system it may be much slower than ExpandFileName, particularly when the given filename is ambiguous or does not exist. Use ExpandFilenameCase only when you have a filename of dubious orgin - such as from user input - and you want to make a best guess before failing. } type TFilenameCaseMatch = (mkNone, mkExactMatch, mkSingleMatch, mkAmbiguous); function ExpandFileNameCase(const FileName: string; out MatchFound: TFilenameCaseMatch): string; { ExpandUNCFileName expands the given filename to a fully qualified filename. This function is the same as ExpandFileName except that it will return the drive portion of the filename in the format '\\\ if that drive is actually a network resource instead of a local resource. Like ExpandFileName, embedded '.' and '..' directory references are removed. } function ExpandUNCFileName(const FileName: string): string; { ExtractRelativePath will return a file path name relative to the given BaseName. It strips the common path dirs and adds '..\' on Windows, and '../' on Linux for each level up from the BaseName path. } function ExtractRelativePath(const BaseName, DestName: string): string; {$IFDEF MSWINDOWS} { ExtractShortPathName will convert the given filename to the short form by calling the GetShortPathName API. Will return an empty string if the file or directory specified does not exist } function ExtractShortPathName(const FileName: string): string; {$ENDIF} { FileSearch searches for the file given by Name in the list of directories given by DirList. The directory paths in DirList must be separated by PathSep chars. The search always starts with the current directory of the current drive. The returned value is a concatenation of one of the directory paths and the filename, or an empty string if the file could not be located. } function FileSearch(const Name, DirList: string): string; {$IFDEF MSWINDOWS} { DiskFree returns the number of free bytes on the specified drive number, where 0 = Current, 1 = A, 2 = B, etc. DiskFree returns -1 if the drive number is invalid. } function DiskFree(Drive: Byte): Int64; { DiskSize returns the size in bytes of the specified drive number, where 0 = Current, 1 = A, 2 = B, etc. DiskSize returns -1 if the drive number is invalid. } function DiskSize(Drive: Byte): Int64; {$ENDIF} { FileDateToDateTime converts an OS date-and-time value to a TDateTime value. The FileAge, FileGetDate, and FileSetDate routines operate on OS date-and-time values, and the Time field of a TSearchRec used by the FindFirst and FindNext functions contains an OS date-and-time value. } function FileDateToDateTime(FileDate: Integer): TDateTime; { DateTimeToFileDate converts a TDateTime value to an OS date-and-time value. The FileAge, FileGetDate, and FileSetDate routines operate on OS date-and-time values, and the Time field of a TSearchRec used by the FindFirst and FindNext functions contains an OS date-and-time value. } function DateTimeToFileDate(DateTime: TDateTime): Integer; { GetCurrentDir returns the current directory. } function GetCurrentDir: string; { SetCurrentDir sets the current directory. The return value is True if the current directory was successfully changed, or False if an error occurred. } function SetCurrentDir(const Dir: string): Boolean; { CreateDir creates a new directory. The return value is True if a new directory was successfully created, or False if an error occurred. } function CreateDir(const Dir: string): Boolean; { RemoveDir deletes an existing empty directory. The return value is True if the directory was successfully deleted, or False if an error occurred. } function RemoveDir(const Dir: string): Boolean; { PChar routines } { const params help simplify C++ code. No effect on pascal code } { StrLen returns the number of characters in Str, not counting the null terminator. } function StrLen(const Str: PChar): Cardinal; { StrEnd returns a pointer to the null character that terminates Str. } function StrEnd(const Str: PChar): PChar; { StrMove copies exactly Count characters from Source to Dest and returns Dest. Source and Dest may overlap. } function StrMove(Dest: PChar; const Source: PChar; Count: Cardinal): PChar; { StrCopy copies Source to Dest and returns Dest. } function StrCopy(Dest: PChar; const Source: PChar): PChar; { StrECopy copies Source to Dest and returns StrEnd(Dest). } function StrECopy(Dest:PChar; const Source: PChar): PChar; { StrLCopy copies at most MaxLen characters from Source to Dest and returns Dest. } function StrLCopy(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar; { StrPCopy copies the Pascal style string Source into Dest and returns Dest. } function StrPCopy(Dest: PChar; const Source: string): PChar; { StrPLCopy copies at most MaxLen characters from the Pascal style string Source into Dest and returns Dest. } function StrPLCopy(Dest: PChar; const Source: string; MaxLen: Cardinal): PChar; { StrCat appends a copy of Source to the end of Dest and returns Dest. } function StrCat(Dest: PChar; const Source: PChar): PChar; { StrLCat appends at most MaxLen - StrLen(Dest) characters from Source to the end of Dest, and returns Dest. } function StrLCat(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar; { StrComp compares Str1 to Str2. The return value is less than 0 if Str1 < Str2, 0 if Str1 = Str2, or greater than 0 if Str1 > Str2. } function StrComp(const Str1, Str2: PChar): Integer; { StrIComp compares Str1 to Str2, without case sensitivity. The return value is the same as StrComp. } function StrIComp(const Str1, Str2: PChar): Integer; { StrLComp compares Str1 to Str2, for a maximum length of MaxLen characters. The return value is the same as StrComp. } function StrLComp(const Str1, Str2: PChar; MaxLen: Cardinal): Integer; { StrLIComp compares Str1 to Str2, for a maximum length of MaxLen characters, without case sensitivity. The return value is the same as StrComp. } function StrLIComp(const Str1, Str2: PChar; MaxLen: Cardinal): Integer; { StrScan returns a pointer to the first occurrence of Chr in Str. If Chr does not occur in Str, StrScan returns NIL. The null terminator is considered to be part of the string. } function StrScan(const Str: PChar; Chr: Char): PChar; { StrRScan returns a pointer to the last occurrence of Chr in Str. If Chr does not occur in Str, StrRScan returns NIL. The null terminator is considered to be part of the string. } function StrRScan(const Str: PChar; Chr: Char): PChar; { StrPos returns a pointer to the first occurrence of Str2 in Str1. If Str2 does not occur in Str1, StrPos returns NIL. } function StrPos(const Str1, Str2: PChar): PChar; { StrUpper converts Str to upper case and returns Str. } function StrUpper(Str: PChar): PChar; { StrLower converts Str to lower case and returns Str. } function StrLower(Str: PChar): PChar; { StrPas converts Str to a Pascal style string. This function is provided for backwards compatibility only. To convert a null terminated string to a Pascal style string, use a string type cast or an assignment. } function StrPas(const Str: PChar): string; { StrAlloc allocates a buffer of the given size on the heap. The size of the allocated buffer is encoded in a four byte header that immediately preceeds the buffer. To dispose the buffer, use StrDispose. } function StrAlloc(Size: Cardinal): PChar; { StrBufSize returns the allocated size of the given buffer, not including the two byte header. } function StrBufSize(const Str: PChar): Cardinal; { StrNew allocates a copy of Str on the heap. If Str is NIL, StrNew returns NIL and doesn't allocate any heap space. Otherwise, StrNew makes a duplicate of Str, obtaining space with a call to the StrAlloc function, and returns a pointer to the duplicated string. To dispose the string, use StrDispose. } function StrNew(const Str: PChar): PChar; { StrDispose disposes a string that was previously allocated with StrAlloc or StrNew. If Str is NIL, StrDispose does nothing. } procedure StrDispose(Str: PChar); { String formatting routines } { The Format routine formats the argument list given by the Args parameter using the format string given by the Format parameter. Format strings contain two types of objects--plain characters and format specifiers. Plain characters are copied verbatim to the resulting string. Format specifiers fetch arguments from the argument list and apply formatting to them. Format specifiers have the following form: "%" [index ":"] ["-"] [width] ["." prec] type A format specifier begins with a % character. After the % come the following, in this order: - an optional argument index specifier, [index ":"] - an optional left-justification indicator, ["-"] - an optional width specifier, [width] - an optional precision specifier, ["." prec] - the conversion type character, type The following conversion characters are supported: d Decimal. The argument must be an integer value. The value is converted to a string of decimal digits. If the format string contains a precision specifier, it indicates that the resulting string must contain at least the specified number of digits; if the value has less digits, the resulting string is left-padded with zeros. u Unsigned decimal. Similar to 'd' but no sign is output. e Scientific. The argument must be a floating-point value. The value is converted to a string of the form "-d.ddd...E+ddd". The resulting string starts with a minus sign if the number is negative, and one digit always precedes the decimal point. The total number of digits in the resulting string (including the one before the decimal point) is given by the precision specifer in the format string--a default precision of 15 is assumed if no precision specifer is present. The "E" exponent character in the resulting string is always followed by a plus or minus sign and at least three digits. f Fixed. The argument must be a floating-point value. The value is converted to a string of the form "-ddd.ddd...". The resulting string starts with a minus sign if the number is negative. The number of digits after the decimal point is given by the precision specifier in the format string--a default of 2 decimal digits is assumed if no precision specifier is present. g General. The argument must be a floating-point value. The value is converted to the shortest possible decimal string using fixed or scientific format. The number of significant digits in the resulting string is given by the precision specifier in the format string--a default precision of 15 is assumed if no precision specifier is present. Trailing zeros are removed from the resulting string, and a decimal point appears only if necessary. The resulting string uses fixed point format if the number of digits to the left of the decimal point in the value is less than or equal to the specified precision, and if the value is greater than or equal to 0.00001. Otherwise the resulting string uses scientific format. n Number. The argument must be a floating-point value. The value is converted to a string of the form "-d,ddd,ddd.ddd...". The "n" format corresponds to the "f" format, except that the resulting string contains thousand separators. m Money. The argument must be a floating-point value. The value is converted to a string that represents a currency amount. The conversion is controlled by the CurrencyString, CurrencyFormat, NegCurrFormat, ThousandSeparator, DecimalSeparator, and CurrencyDecimals global variables, all of which are initialized from locale settings provided by the operating system. For example, Currency Format preferences can be set in the International section of the Windows Control Panel. If the format string contains a precision specifier, it overrides the value given by the CurrencyDecimals global variable. p Pointer. The argument must be a pointer value. The value is converted to a string of the form "XXXX:YYYY" where XXXX and YYYY are the segment and offset parts of the pointer expressed as four hexadecimal digits. s String. The argument must be a character, a string, or a PChar value. The string or character is inserted in place of the format specifier. The precision specifier, if present in the format string, specifies the maximum length of the resulting string. If the argument is a string that is longer than this maximum, the string is truncated. x Hexadecimal. The argument must be an integer value. The value is converted to a string of hexadecimal digits. If the format string contains a precision specifier, it indicates that the resulting string must contain at least the specified number of digits; if the value has less digits, the resulting string is left-padded with zeros. Conversion characters may be specified in upper case as well as in lower case--both produce the same results. For all floating-point formats, the actual characters used as decimal and thousand separators are obtained from the DecimalSeparator and ThousandSeparator global variables. Index, width, and precision specifiers can be specified directly using decimal digit string (for example "%10d"), or indirectly using an asterisk charcater (for example "%*.*f"). When using an asterisk, the next argument in the argument list (which must be an integer value) becomes the value that is actually used. For example "Format('%*.*f', [8, 2, 123.456])" is the same as "Format('%8.2f', [123.456])". A width specifier sets the minimum field width for a conversion. If the resulting string is shorter than the minimum field width, it is padded with blanks to increase the field width. The default is to right-justify the result by adding blanks in front of the value, but if the format specifier contains a left-justification indicator (a "-" character preceding the width specifier), the result is left-justified by adding blanks after the value. An index specifier sets the current argument list index to the specified value. The index of the first argument in the argument list is 0. Using index specifiers, it is possible to format the same argument multiple times. For example "Format('%d %d %0:d %d', [10, 20])" produces the string '10 20 10 20'. The Format function can be combined with other formatting functions. For example S := Format('Your total was %s on %s', [ FormatFloat('$#,##0.00;;zero', Total), FormatDateTime('mm/dd/yy', Date)]); which uses the FormatFloat and FormatDateTime functions to customize the format beyond what is possible with Format. Each of the string formatting routines that uses global variables for formatting (separators, decimals, date/time formats etc.), has an overloaded equivalent requiring a parameter of type TFormatSettings. This additional parameter provides the formatting information rather than the global variables. For more information see the notes at TFormatSettings. } function Format(const Format: string; const Args: array of const): string; overload; function Format(const Format: string; const Args: array of const; const FormatSettings: TFormatSettings): string; overload; { FmtStr formats the argument list given by Args using the format string given by Format into the string variable given by Result. For further details, see the description of the Format function. } procedure FmtStr(var Result: string; const Format: string; const Args: array of const); overload; procedure FmtStr(var Result: string; const Format: string; const Args: array of const; const FormatSettings: TFormatSettings); overload; { StrFmt formats the argument list given by Args using the format string given by Format into the buffer given by Buffer. It is up to the caller to ensure that Buffer is large enough for the resulting string. The returned value is Buffer. For further details, see the description of the Format function. } function StrFmt(Buffer, Format: PChar; const Args: array of const): PChar; overload; function StrFmt(Buffer, Format: PChar; const Args: array of const; const FormatSettings: TFormatSettings): PChar; overload; { StrLFmt formats the argument list given by Args using the format string given by Format into the buffer given by Buffer. The resulting string will contain no more than MaxBufLen characters, not including the null terminator. The returned value is Buffer. For further details, see the description of the Format function. } function StrLFmt(Buffer: PChar; MaxBufLen: Cardinal; Format: PChar; const Args: array of const): PChar; overload; function StrLFmt(Buffer: PChar; MaxBufLen: Cardinal; Format: PChar; const Args: array of const; const FormatSettings: TFormatSettings): PChar; overload; { FormatBuf formats the argument list given by Args using the format string given by Format and FmtLen into the buffer given by Buffer and BufLen. The Format parameter is a reference to a buffer containing FmtLen characters, and the Buffer parameter is a reference to a buffer of BufLen characters. The returned value is the number of characters actually stored in Buffer. The returned value is always less than or equal to BufLen. For further details, see the description of the Format function. } function FormatBuf(var Buffer; BufLen: Cardinal; const Format; FmtLen: Cardinal; const Args: array of const): Cardinal; overload; function FormatBuf(var Buffer; BufLen: Cardinal; const Format; FmtLen: Cardinal; const Args: array of const; const FormatSettings: TFormatSettings): Cardinal; overload; { The WideFormat routine formats the argument list given by the Args parameter using the format WideString given by the Format parameter. This routine is the WideString equivalent of Format. For further details, see the description of the Format function. } function WideFormat(const Format: WideString; const Args: array of const): WideString; overload; function WideFormat(const Format: WideString; const Args: array of const; const FormatSettings: TFormatSettings): WideString; overload; { WideFmtStr formats the argument list given by Args using the format WideString given by Format into the WideString variable given by Result. For further details, see the description of the Format function. } procedure WideFmtStr(var Result: WideString; const Format: WideString; const Args: array of const); overload; procedure WideFmtStr(var Result: WideString; const Format: WideString; const Args: array of const; const FormatSettings: TFormatSettings); overload; { WideFormatBuf formats the argument list given by Args using the format string given by Format and FmtLen into the buffer given by Buffer and BufLen. The Format parameter is a reference to a buffer containing FmtLen UNICODE characters (WideChar), and the Buffer parameter is a reference to a buffer of BufLen UNICODE characters (WideChar). The return value is the number of UNICODE characters actually stored in Buffer. The return value is always less than or equal to BufLen. For further details, see the description of the Format function. Important: BufLen, FmtLen and the return result are always the number of UNICODE characters, *not* the number of bytes. To calculate the number of bytes multiply them by SizeOf(WideChar). } function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format; FmtLen: Cardinal; const Args: array of const): Cardinal; overload; function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format; FmtLen: Cardinal; const Args: array of const; const FormatSettings: TFormatSettings): Cardinal; overload; { Floating point conversion routines } { Each of the floating point conversion routines that uses global variables for formatting (separators, decimals, etc.), has an overloaded equivalent requiring a parameter of type TFormatSettings. This additional parameter provides the formatting information rather than the global variables. For more information see the notes at TFormatSettings. } { FloatToStr converts the floating-point value given by Value to its string representation. The conversion uses general number format with 15 significant digits. For further details, see the description of the FloatToStrF function. } function FloatToStr(Value: Extended): string; overload; function FloatToStr(Value: Extended; const FormatSettings: TFormatSettings): string; overload; { CurrToStr converts the currency value given by Value to its string representation. The conversion uses general number format. For further details, see the description of the CurrToStrF function. } function CurrToStr(Value: Currency): string; overload; function CurrToStr(Value: Currency; const FormatSettings: TFormatSettings): string; overload; { FloatToCurr will range validate a value to make sure it falls within the acceptable currency range } const MinCurrency: Currency = -922337203685477.5807 {$IFDEF LINUX} + 1 {$ENDIF}; //!! overflow? MaxCurrency: Currency = 922337203685477.5807 {$IFDEF LINUX} - 1 {$ENDIF}; //!! overflow? function FloatToCurr(const Value: Extended): Currency; function TryFloatToCurr(const Value: Extended; out AResult: Currency): Boolean; { FloatToStrF converts the floating-point value given by Value to its string representation. The Format parameter controls the format of the resulting string. The Precision parameter specifies the precision of the given value. It should be 7 or less for values of type Single, 15 or less for values of type Double, and 18 or less for values of type Extended. The meaning of the Digits parameter depends on the particular format selected. The possible values of the Format parameter, and the meaning of each, are described below. ffGeneral - General number format. The value is converted to the shortest possible decimal string using fixed or scientific format. Trailing zeros are removed from the resulting string, and a decimal point appears only if necessary. The resulting string uses fixed point format if the number of digits to the left of the decimal point in the value is less than or equal to the specified precision, and if the value is greater than or equal to 0.00001. Otherwise the resulting string uses scientific format, and the Digits parameter specifies the minimum number of digits in the exponent (between 0 and 4). ffExponent - Scientific format. The value is converted to a string of the form "-d.ddd...E+dddd". The resulting string starts with a minus sign if the number is negative, and one digit always precedes the decimal point. The total number of digits in the resulting string (including the one before the decimal point) is given by the Precision parameter. The "E" exponent character in the resulting string is always followed by a plus or minus sign and up to four digits. The Digits parameter specifies the minimum number of digits in the exponent (between 0 and 4). ffFixed - Fixed point format. The value is converted to a string of the form "-ddd.ddd...". The resulting string starts with a minus sign if the number is negative, and at least one digit always precedes the decimal point. The number of digits after the decimal point is given by the Digits parameter--it must be between 0 and 18. If the number of digits to the left of the decimal point is greater than the specified precision, the resulting value will use scientific format. ffNumber - Number format. The value is converted to a string of the form "-d,ddd,ddd.ddd...". The ffNumber format corresponds to the ffFixed format, except that the resulting string contains thousand separators. ffCurrency - Currency format. The value is converted to a string that represents a currency amount. The conversion is controlled by the CurrencyString, CurrencyFormat, NegCurrFormat, ThousandSeparator, and DecimalSeparator global variables, all of which are initialized from locale settings provided by the operating system. For example, Currency Format preferences can be set in the International section of the Windows Control Panel. The number of digits after the decimal point is given by the Digits parameter--it must be between 0 and 18. For all formats, the actual characters used as decimal and thousand separators are obtained from the DecimalSeparator and ThousandSeparator global variables. If the given value is a NAN (not-a-number), the resulting string is 'NAN'. If the given value is positive infinity, the resulting string is 'INF'. If the given value is negative infinity, the resulting string is '-INF'. } function FloatToStrF(Value: Extended; Format: TFloatFormat; Precision, Digits: Integer): string; overload; function FloatToStrF(Value: Extended; Format: TFloatFormat; Precision, Digits: Integer; const FormatSettings: TFormatSettings): string; overload; { CurrToStrF converts the currency value given by Value to its string representation. A call to CurrToStrF corresponds to a call to FloatToStrF with an implied precision of 19 digits. } function CurrToStrF(Value: Currency; Format: TFloatFormat; Digits: Integer): string; overload; function CurrToStrF(Value: Currency; Format: TFloatFormat; Digits: Integer; const FormatSettings: TFormatSettings): string; overload; { FloatToText converts the given floating-point value to its decimal representation using the specified format, precision, and digits. The Value parameter must be a variable of type Extended or Currency, as indicated by the ValueType parameter. The resulting string of characters is stored in the given buffer, and the returned value is the number of characters stored. The resulting string is not null-terminated. For further details, see the description of the FloatToStrF function. } function FloatToText(BufferArg: PChar; const Value; ValueType: TFloatValue; Format: TFloatFormat; Precision, Digits: Integer): Integer; overload; function FloatToText(BufferArg: PChar; const Value; ValueType: TFloatValue; Format: TFloatFormat; Precision, Digits: Integer; const FormatSettings: TFormatSettings): Integer; overload; { FormatFloat formats the floating-point value given by Value using the format string given by Format. The following format specifiers are supported in the format string: 0 Digit placeholder. If the value being formatted has a digit in the position where the '0' appears in the format string, then that digit is copied to the output string. Otherwise, a '0' is stored in that position in the output string. # Digit placeholder. If the value being formatted has a digit in the position where the '#' appears in the format string, then that digit is copied to the output string. Otherwise, nothing is stored in that position in the output string. . Decimal point. The first '.' character in the format string determines the location of the decimal separator in the formatted value; any additional '.' characters are ignored. The actual character used as a the decimal separator in the output string is determined by the DecimalSeparator global variable, which is initialized from locale settings obtained from the operating system. , Thousand separator. If the format string contains one or more ',' characters, the output will have thousand separators inserted between each group of three digits to the left of the decimal point. The placement and number of ',' characters in the format string does not affect the output, except to indicate that thousand separators are wanted. The actual character used as a the thousand separator in the output is determined by the ThousandSeparator global variable, which is initialized from locale settings obtained from the operating system. E+ Scientific notation. If any of the strings 'E+', 'E-', 'e+', or 'e-' E- are contained in the format string, the number is formatted using e+ scientific notation. A group of up to four '0' characters can e- immediately follow the 'E+', 'E-', 'e+', or 'e-' to determine the minimum number of digits in the exponent. The 'E+' and 'e+' formats cause a plus sign to be output for positive exponents and a minus sign to be output for negative exponents. The 'E-' and 'e-' formats output a sign character only for negative exponents. 'xx' Characters enclosed in single or double quotes are output as-is, and "xx" do not affect formatting. ; Separates sections for positive, negative, and zero numbers in the format string. The locations of the leftmost '0' before the decimal point in the format string and the rightmost '0' after the decimal point in the format string determine the range of digits that are always present in the output string. The number being formatted is always rounded to as many decimal places as there are digit placeholders ('0' or '#') to the right of the decimal point. If the format string contains no decimal point, the value being formatted is rounded to the nearest whole number. If the number being formatted has more digits to the left of the decimal separator than there are digit placeholders to the left of the '.' character in the format string, the extra digits are output before the first digit placeholder. To allow different formats for positive, negative, and zero values, the format string can contain between one and three sections separated by semicolons. One section - The format string applies to all values. Two sections - The first section applies to positive values and zeros, and the second section applies to negative values. Three sections - The first section applies to positive values, the second applies to negative values, and the third applies to zeros. If the section for negative values or the section for zero values is empty, that is if there is nothing between the semicolons that delimit the section, the section for positive values is used instead. If the section for positive values is empty, or if the entire format string is empty, the value is formatted using general floating-point formatting with 15 significant digits, corresponding to a call to FloatToStrF with the ffGeneral format. General floating-point formatting is also used if the value has more than 18 digits to the left of the decimal point and the format string does not specify scientific notation. The table below shows some sample formats and the results produced when the formats are applied to different values: Format string 1234 -1234 0.5 0 ----------------------------------------------------------------------- 1234 -1234 0.5 0 0 1234 -1234 1 0 0.00 1234.00 -1234.00 0.50 0.00 #.## 1234 -1234 .5 #,##0.00 1,234.00 -1,234.00 0.50 0.00 #,##0.00;(#,##0.00) 1,234.00 (1,234.00) 0.50 0.00 #,##0.00;;Zero 1,234.00 -1,234.00 0.50 Zero 0.000E+00 1.234E+03 -1.234E+03 5.000E-01 0.000E+00 #.###E-0 1.234E3 -1.234E3 5E-1 0E0 ----------------------------------------------------------------------- } function FormatFloat(const Format: string; Value: Extended): string; overload; function FormatFloat(const Format: string; Value: Extended; const FormatSettings: TFormatSettings): string; overload; { FormatCurr formats the currency value given by Value using the format string given by Format. For further details, see the description of the FormatFloat function. } function FormatCurr(const Format: string; Value: Currency): string; overload; function FormatCurr(const Format: string; Value: Currency; const FormatSettings: TFormatSettings): string; overload; { FloatToTextFmt converts the given floating-point value to its decimal representation using the specified format. The Value parameter must be a variable of type Extended or Currency, as indicated by the ValueType parameter. The resulting string of characters is stored in the given buffer, and the returned value is the number of characters stored. The resulting string is not null-terminated. For further details, see the description of the FormatFloat function. } function FloatToTextFmt(Buf: PChar; const Value; ValueType: TFloatValue; Format: PChar): Integer; overload; function FloatToTextFmt(Buf: PChar; const Value; ValueType: TFloatValue; Format: PChar; const FormatSettings: TFormatSettings): Integer; overload; { StrToFloat converts the given string to a floating-point value. The string must consist of an optional sign (+ or -), a string of digits with an optional decimal point, and an optional 'E' or 'e' followed by a signed integer. Leading and trailing blanks in the string are ignored. The DecimalSeparator global variable defines the character that must be used as a decimal point. Thousand separators and currency symbols are not allowed in the string. If the string doesn't contain a valid value, an EConvertError exception is raised. } function StrToFloat(const S: string): Extended; overload; function StrToFloat(const S: string; const FormatSettings: TFormatSettings): Extended; overload; function StrToFloatDef(const S: string; const Default: Extended): Extended; overload; function StrToFloatDef(const S: string; const Default: Extended; const FormatSettings: TFormatSettings): Extended; overload; function TryStrToFloat(const S: string; out Value: Extended): Boolean; overload; function TryStrToFloat(const S: string; out Value: Extended; const FormatSettings: TFormatSettings): Boolean; overload; function TryStrToFloat(const S: string; out Value: Double): Boolean; overload; function TryStrToFloat(const S: string; out Value: Double; const FormatSettings: TFormatSettings): Boolean; overload; function TryStrToFloat(const S: string; out Value: Single): Boolean; overload; function TryStrToFloat(const S: string; out Value: Single; const FormatSettings: TFormatSettings): Boolean; overload; { StrToCurr converts the given string to a currency value. For further details, see the description of the StrToFloat function. } function StrToCurr(const S: string): Currency; overload; function StrToCurr(const S: string; const FormatSettings: TFormatSettings): Currency; overload; function StrToCurrDef(const S: string; const Default: Currency): Currency; overload; function StrToCurrDef(const S: string; const Default: Currency; const FormatSettings: TFormatSettings): Currency; overload; function TryStrToCurr(const S: string; out Value: Currency): Boolean; overload; function TryStrToCurr(const S: string; out Value: Currency; const FormatSettings: TFormatSettings): Boolean; overload; { TextToFloat converts the null-terminated string given by Buffer to a floating-point value which is returned in the variable given by Value. The Value parameter must be a variable of type Extended or Currency, as indicated by the ValueType parameter. The return value is True if the conversion was successful, or False if the string is not a valid floating-point value. For further details, see the description of the StrToFloat function. } function TextToFloat(Buffer: PChar; var Value; ValueType: TFloatValue): Boolean; overload; function TextToFloat(Buffer: PChar; var Value; ValueType: TFloatValue; const FormatSettings: TFormatSettings): Boolean; overload; { FloatToDecimal converts a floating-point value to a decimal representation that is suited for further formatting. The Value parameter must be a variable of type Extended or Currency, as indicated by the ValueType parameter. For values of type Extended, the Precision parameter specifies the requested number of significant digits in the result--the allowed range is 1..18. For values of type Currency, the Precision parameter is ignored, and the implied precision of the conversion is 19 digits. The Decimals parameter specifies the requested maximum number of digits to the left of the decimal point in the result. Precision and Decimals together control how the result is rounded. To produce a result that always has a given number of significant digits regardless of the magnitude of the number, specify 9999 for the Decimals parameter. The result of the conversion is stored in the specified TFloatRec record as follows: Exponent - Contains the magnitude of the number, i.e. the number of significant digits to the right of the decimal point. The Exponent field is negative if the absolute value of the number is less than one. If the number is a NAN (not-a-number), Exponent is set to -32768. If the number is INF or -INF (positive or negative infinity), Exponent is set to 32767. Negative - True if the number is negative, False if the number is zero or positive. Digits - Contains up to 18 (for type Extended) or 19 (for type Currency) significant digits followed by a null terminator. The implied decimal point (if any) is not stored in Digits. Trailing zeros are removed, and if the resulting number is zero, NAN, or INF, Digits contains nothing but the null terminator. } procedure FloatToDecimal(var Result: TFloatRec; const Value; ValueType: TFloatValue; Precision, Decimals: Integer); { Date/time support routines } function DateTimeToTimeStamp(DateTime: TDateTime): TTimeStamp; function TimeStampToDateTime(const TimeStamp: TTimeStamp): TDateTime; function MSecsToTimeStamp(MSecs: Comp): TTimeStamp; function TimeStampToMSecs(const TimeStamp: TTimeStamp): Comp; { EncodeDate encodes the given year, month, and day into a TDateTime value. The year must be between 1 and 9999, the month must be between 1 and 12, and the day must be between 1 and N, where N is the number of days in the specified month. If the specified values are not within range, an EConvertError exception is raised. The resulting value is the number of days between 12/30/1899 and the given date. } function EncodeDate(Year, Month, Day: Word): TDateTime; { EncodeTime encodes the given hour, minute, second, and millisecond into a TDateTime value. The hour must be between 0 and 23, the minute must be between 0 and 59, the second must be between 0 and 59, and the millisecond must be between 0 and 999. If the specified values are not within range, an EConvertError exception is raised. The resulting value is a number between 0 (inclusive) and 1 (not inclusive) that indicates the fractional part of a day given by the specified time. The value 0 corresponds to midnight, 0.5 corresponds to noon, 0.75 corresponds to 6:00 pm, etc. } function EncodeTime(Hour, Min, Sec, MSec: Word): TDateTime; { Instead of generating errors the following variations of EncodeDate and EncodeTime simply return False if the parameters given are not valid. Other than that, these functions are functionally the same as the above functions. } function TryEncodeDate(Year, Month, Day: Word; out Date: TDateTime): Boolean; function TryEncodeTime(Hour, Min, Sec, MSec: Word; out Time: TDateTime): Boolean; { DecodeDate decodes the integral (date) part of the given TDateTime value into its corresponding year, month, and day. If the given TDateTime value is less than or equal to zero, the year, month, and day return parameters are all set to zero. } procedure DecodeDate(const DateTime: TDateTime; var Year, Month, Day: Word); { This variation of DecodeDate works similarly to the above function but returns more information. The result value of this function indicates whether the year decoded is a leap year or not. } function DecodeDateFully(const DateTime: TDateTime; var Year, Month, Day, DOW: Word): Boolean; {$IFDEF LINUX} function InternalDecodeDate(const DateTime: TDateTime; var Year, Month, Day, DOW: Word): Boolean; {$ENDIF} { DecodeTime decodes the fractional (time) part of the given TDateTime value into its corresponding hour, minute, second, and millisecond. } procedure DecodeTime(const DateTime: TDateTime; var Hour, Min, Sec, MSec: Word); {$IFDEF MSWINDOWS} { DateTimeToSystemTime converts a date and time from Delphi's TDateTime format into the Win32 API's TSystemTime format. } procedure DateTimeToSystemTime(const DateTime: TDateTime; var SystemTime: TSystemTime); { SystemTimeToDateTime converts a date and time from the Win32 API's TSystemTime format into Delphi's TDateTime format. } function SystemTimeToDateTime(const SystemTime: TSystemTime): TDateTime; {$ENDIF} { DayOfWeek returns the day of the week of the given date. The result is an integer between 1 and 7, corresponding to Sunday through Saturday. This function is not ISO 8601 compliant, for that see the DateUtils unit. } function DayOfWeek(const DateTime: TDateTime): Word; { Date returns the current date. } function Date: TDateTime; { Time returns the current time. } function Time: TDateTime; {$IFDEF LINUX} { clashes with Time in , use GetTime instead } {$EXTERNALSYM Time} {$ENDIF} function GetTime: TDateTime; { Now returns the current date and time, corresponding to Date + Time. } function Now: TDateTime; { Current year returns the year portion of the date returned by Now } function CurrentYear: Word; { IncMonth returns Date shifted by the specified number of months. NumberOfMonths parameter can be negative, to return a date N months ago. If the input day of month is greater than the last day of the resulting month, the day is set to the last day of the resulting month. Input time of day is copied to the DateTime result. } function IncMonth(const DateTime: TDateTime; NumberOfMonths: Integer = 1): TDateTime; { Optimized version of IncMonth that works with years, months and days directly. See above comments for more detail as to what happens to the day when incrementing months } procedure IncAMonth(var Year, Month, Day: Word; NumberOfMonths: Integer = 1); { ReplaceTime replaces the time portion of the DateTime parameter with the given time value, adjusting the signs as needed if the date is prior to 1900 (Date value less than zero) } procedure ReplaceTime(var DateTime: TDateTime; const NewTime: TDateTime); { ReplaceDate replaces the date portion of the DateTime parameter with the given date value, adjusting as needed for negative dates } procedure ReplaceDate(var DateTime: TDateTime; const NewDate: TDateTime); { IsLeapYear determines whether the given year is a leap year. } function IsLeapYear(Year: Word): Boolean; type PDayTable = ^TDayTable; TDayTable = array[1..12] of Word; { The MonthDays array can be used to quickly find the number of days in a month: MonthDays[IsLeapYear(Y), M] } const MonthDays: array [Boolean] of TDayTable = ((31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)); { Each of the date/time formatting routines that uses global variables for formatting (separators, decimals, etc.), has an overloaded equivalent requiring a parameter of type TFormatSettings. This additional parameter provides the formatting information rather than the global variables. For more information see the note at TFormatSettings. } { DateToStr converts the date part of the given TDateTime value to a string. The conversion uses the format specified by the ShortDateFormat global variable. } function DateToStr(const DateTime: TDateTime): string; overload; inline; function DateToStr(const DateTime: TDateTime; const FormatSettings: TFormatSettings): string; overload; inline; { TimeToStr converts the time part of the given TDateTime value to a string. The conversion uses the format specified by the LongTimeFormat global variable. } function TimeToStr(const DateTime: TDateTime): string; overload; inline; function TimeToStr(const DateTime: TDateTime; const FormatSettings: TFormatSettings): string; overload; inline; { DateTimeToStr converts the given date and time to a string. The resulting string consists of a date and time formatted using the ShortDateFormat and LongTimeFormat global variables. Time information is included in the resulting string only if the fractional part of the given date and time value is non-zero. } function DateTimeToStr(const DateTime: TDateTime): string; overload; inline; function DateTimeToStr(const DateTime: TDateTime; const FormatSettings: TFormatSettings): string; overload; inline; { StrToDate converts the given string to a date value. The string must consist of two or three numbers, separated by the character defined by the DateSeparator global variable. The order for month, day, and year is determined by the ShortDateFormat global variable--possible combinations are m/d/y, d/m/y, and y/m/d. If the string contains only two numbers, it is interpreted as a date (m/d or d/m) in the current year. Year values between 0 and 99 are assumed to be in the current century. If the given string does not contain a valid date, an EConvertError exception is raised. } function StrToDate(const S: string): TDateTime; overload; function StrToDate(const S: string; const FormatSettings: TFormatSettings): TDateTime; overload; function StrToDateDef(const S: string; const Default: TDateTime): TDateTime; overload; function StrToDateDef(const S: string; const Default: TDateTime; const FormatSettings: TFormatSettings): TDateTime; overload; function TryStrToDate(const S: string; out Value: TDateTime): Boolean; overload; function TryStrToDate(const S: string; out Value: TDateTime; const FormatSettings: TFormatSettings): Boolean; overload; { StrToTime converts the given string to a time value. The string must consist of two or three numbers, separated by the character defined by the TimeSeparator global variable, optionally followed by an AM or PM indicator. The numbers represent hour, minute, and (optionally) second, in that order. If the time is followed by AM or PM, it is assumed to be in 12-hour clock format. If no AM or PM indicator is included, the time is assumed to be in 24-hour clock format. If the given string does not contain a valid time, an EConvertError exception is raised. } function StrToTime(const S: string): TDateTime; overload; function StrToTime(const S: string; const FormatSettings: TFormatSettings): TDateTime; overload; function StrToTimeDef(const S: string; const Default: TDateTime): TDateTime; overload; function StrToTimeDef(const S: string; const Default: TDateTime; const FormatSettings: TFormatSettings): TDateTime; overload; function TryStrToTime(const S: string; out Value: TDateTime): Boolean; overload; function TryStrToTime(const S: string; out Value: TDateTime; const FormatSettings: TFormatSettings): Boolean; overload; { StrToDateTime converts the given string to a date and time value. The string must contain a date optionally followed by a time. The date and time parts of the string must follow the formats described for the StrToDate and StrToTime functions. } function StrToDateTime(const S: string): TDateTime; overload; function StrToDateTime(const S: string; const FormatSettings: TFormatSettings): TDateTime; overload; function StrToDateTimeDef(const S: string; const Default: TDateTime): TDateTime; overload; function StrToDateTimeDef(const S: string; const Default: TDateTime; const FormatSettings: TFormatSettings): TDateTime; overload; function TryStrToDateTime(const S: string; out Value: TDateTime): Boolean; overload; function TryStrToDateTime(const S: string; out Value: TDateTime; const FormatSettings: TFormatSettings): Boolean; overload; { FormatDateTime formats the date-and-time value given by DateTime using the format given by Format. The following format specifiers are supported: c Displays the date using the format given by the ShortDateFormat global variable, followed by the time using the format given by the LongTimeFormat global variable. The time is not displayed if the fractional part of the DateTime value is zero. d Displays the day as a number without a leading zero (1-31). dd Displays the day as a number with a leading zero (01-31). ddd Displays the day as an abbreviation (Sun-Sat) using the strings given by the ShortDayNames global variable. dddd Displays the day as a full name (Sunday-Saturday) using the strings given by the LongDayNames global variable. ddddd Displays the date using the format given by the ShortDateFormat global variable. dddddd Displays the date using the format given by the LongDateFormat global variable. g Displays the period/era as an abbreviation (Japanese and Taiwanese locales only). gg Displays the period/era as a full name. e Displays the year in the current period/era as a number without a leading zero (Japanese, Korean and Taiwanese locales only). ee Displays the year in the current period/era as a number with a leading zero (Japanese, Korean and Taiwanese locales only). m Displays the month as a number without a leading zero (1-12). If the m specifier immediately follows an h or hh specifier, the minute rather than the month is displayed. mm Displays the month as a number with a leading zero (01-12). If the mm specifier immediately follows an h or hh specifier, the minute rather than the month is displayed. mmm Displays the month as an abbreviation (Jan-Dec) using the strings given by the ShortMonthNames global variable. mmmm Displays the month as a full name (January-December) using the strings given by the LongMonthNames global variable. yy Displays the year as a two-digit number (00-99). yyyy Displays the year as a four-digit number (0000-9999). h Displays the hour without a leading zero (0-23). hh Displays the hour with a leading zero (00-23). n Displays the minute without a leading zero (0-59). nn Displays the minute with a leading zero (00-59). s Displays the second without a leading zero (0-59). ss Displays the second with a leading zero (00-59). z Displays the millisecond without a leading zero (0-999). zzz Displays the millisecond with a leading zero (000-999). t Displays the time using the format given by the ShortTimeFormat global variable. tt Displays the time using the format given by the LongTimeFormat global variable. am/pm Uses the 12-hour clock for the preceding h or hh specifier, and displays 'am' for any hour before noon, and 'pm' for any hour after noon. The am/pm specifier can use lower, upper, or mixed case, and the result is displayed accordingly. a/p Uses the 12-hour clock for the preceding h or hh specifier, and displays 'a' for any hour before noon, and 'p' for any hour after noon. The a/p specifier can use lower, upper, or mixed case, and the result is displayed accordingly. ampm Uses the 12-hour clock for the preceding h or hh specifier, and displays the contents of the TimeAMString global variable for any hour before noon, and the contents of the TimePMString global variable for any hour after noon. / Displays the date separator character given by the DateSeparator global variable. : Displays the time separator character given by the TimeSeparator global variable. 'xx' Characters enclosed in single or double quotes are displayed as-is, "xx" and do not affect formatting. Format specifiers may be written in upper case as well as in lower case letters--both produce the same result. If the string given by the Format parameter is empty, the date and time value is formatted as if a 'c' format specifier had been given. The following example: S := FormatDateTime('"The meeting is on" dddd, mmmm d, yyyy, ' + '"at" hh:mm AM/PM', StrToDateTime('2/15/95 10:30am')); assigns 'The meeting is on Wednesday, February 15, 1995 at 10:30 AM' to the string variable S. } function FormatDateTime(const Format: string; DateTime: TDateTime): string; overload; inline; function FormatDateTime(const Format: string; DateTime: TDateTime; const FormatSettings: TFormatSettings): string; overload; { DateTimeToString converts the date and time value given by DateTime using the format string given by Format into the string variable given by Result. For further details, see the description of the FormatDateTime function. } procedure DateTimeToString(var Result: string; const Format: string; DateTime: TDateTime); overload; procedure DateTimeToString(var Result: string; const Format: string; DateTime: TDateTime; const FormatSettings: TFormatSettings); overload; { FloatToDateTime will range validate a value to make sure it falls within the acceptable date range } const MinDateTime: TDateTime = -657434.0; { 01/01/0100 12:00:00.000 AM } MaxDateTime: TDateTime = 2958465.99999; { 12/31/9999 11:59:59.999 PM } function FloatToDateTime(const Value: Extended): TDateTime; function TryFloatToDateTime(const Value: Extended; out AResult: TDateTime): Boolean; { System error messages } function SysErrorMessage(ErrorCode: Integer): string; { Initialization file support } function GetLocaleStr(Locale, LocaleType: Integer; const Default: string): string; platform; function GetLocaleChar(Locale, LocaleType: Integer; Default: Char): Char; platform; { GetFormatSettings resets all locale-specific variables (date, time, number, currency formats, system locale) to the values provided by the operating system. } procedure GetFormatSettings; { GetLocaleFormatSettings loads locale-specific variables (date, time, number, currency formats) with values provided by the operating system for the specified locale (LCID). The values are stored in the FormatSettings record. } {$IFDEF MSWINDOWS} procedure GetLocaleFormatSettings(LCID: Integer; var FormatSettings: TFormatSettings); {$ENDIF} { Exception handling routines } {$IFDEF LINUX} { InquireSignal is used to determine the state of an OS signal handler. Pass it one of the RTL_SIG* constants, and it will return a TSignalState which will tell you if the signal has been hooked, not hooked, or overriden by some other module. You can use this function to determine if some other module has hijacked your signal handlers, should you wish to reinstall your own. This is a risky proposition under Linux, and is only recommended as a last resort. Do not pass RTL_SIGDEFAULT to this function. } function InquireSignal(RtlSigNum: Integer): TSignalState; { AbandonSignalHandler tells the RTL to leave a signal handler in place, even if we believe that we hooked it at startup time. Once you have called AbandonSignalHandler with a specific signal number, neither UnhookSignal nor the RTL will restore any previous signal handler under any condition. } procedure AbandonSignalHandler(RtlSigNum: Integer); { HookSignal is used to hook individual signals, or an RTL-defined default set of signals. It does not test whether a signal has already been hooked, so it should be used in conjunction with InquireSignal. It is exposed to enable users to hook signals in standalone libraries, or in the event that an external module hijacks the RTL installed signal handlers. Pass RTL_SIGDEFAULT if you want to hook all the signals that the RTL normally hooks at startup time. } procedure HookSignal(RtlSigNum: Integer); { UnhookSignal is used to remove signal handlers installed by HookSignal. It can remove individual signal handlers, or the RTL-defined default set of signals. If OnlyIfHooked is True, then we will only unhook the signal if the signal handler has been hooked, and has not since been overriden by some foreign handler. } procedure UnhookSignal(RtlSigNum: Integer; OnlyIfHooked: Boolean = True); { HookOSExceptions is used internally by thread support. DON'T call this function yourself. } procedure HookOSExceptions; { MapSignal is used internally as well. It maps a signal and associated context to an internal value that represents the type of Exception class to raise. } function MapSignal(SigNum: Integer; Context: PSigContext): LongWord; { SignalConverter is used internally to properly reinit the FPU and properly raise an external OS exception object. DON'T call this function yourself. } procedure SignalConverter(ExceptionEIP: LongWord; FaultAddr: LongWord; ErrorCode: LongWord); { See the comment at the threadvar declarations for these below. The access to these has been implemented through getter/setter functions because you cannot use threadvars across packages. } procedure SetSafeCallExceptionMsg(const Msg: String); procedure SetSafeCallExceptionAddr(Addr: Pointer); function GetSafeCallExceptionMsg: String; function GetSafeCallExceptionAddr: Pointer; { HookOSExceptionsProc is used internally and cannot be used in a conventional manner. DON'T ever set this variable. } var HookOSExceptionsProc: procedure = nil platform deprecated; { LoadLibrary / FreeLibrary are defined here only for convenience. On Linux, they map directly to dlopen / dlclose. Note that module loading semantics on Linux are not identical to Windows. } function LoadLibrary(ModuleName: PChar): HMODULE; function FreeLibrary(Module: HMODULE): LongBool; { GetProcAddress does what it implies. It performs the same function as the like named function under Windows. dlsym does not quite have the same sematics as GetProcAddress as it will return the address of a symbol in another module if it was not found in the given HMODULE. This function will verify that the 'Proc' is actually found within the 'Module', and if not returns nil } function GetProcAddress(Module: HMODULE; Proc: PChar): Pointer; { Given a module name, this function will return the module handle. There is no direct equivalent in Linux so this function provides that capability. Also note, this function is specific to glibc. } function GetModuleHandle(ModuleName: PChar): HMODULE; { This function works just like GetModuleHandle, except it will look for a module that matches the given base package name. For example, given the base package name 'package', the actual module name is, by default, 'bplpackage.so'. This function will search for the string 'package' within the module name. } function GetPackageModuleHandle(PackageName: PChar): HMODULE; {$ENDIF} { In Linux, the parameter to sleep() is in whole seconds. In Windows, the parameter is in milliseconds. To ease headaches, we implement a version of sleep here for Linux that takes milliseconds and calls a Linux system function with sub-second resolution. This maps directly to the Windows API on Windows. } procedure Sleep(milliseconds: Cardinal);{$IFDEF MSWINDOWS} stdcall; {$ENDIF} {$IFDEF MSWINDOWS} (*$EXTERNALSYM Sleep*) {$ENDIF} function GetModuleName(Module: HMODULE): string; function ExceptionErrorMessage(ExceptObject: TObject; ExceptAddr: Pointer; Buffer: PChar; Size: Integer): Integer; procedure ShowException(ExceptObject: TObject; ExceptAddr: Pointer); procedure Abort; procedure OutOfMemoryError; procedure Beep; { MBCS functions } { LeadBytes is a char set that indicates which char values are lead bytes in multibyte character sets (Japanese, Chinese, etc). This set is always empty for western locales. } var LeadBytes: set of Char = []; (*$EXTERNALSYM LeadBytes*) (*$HPPEMIT 'namespace Sysutils {'*) (*$HPPEMIT 'extern PACKAGE System::Set LeadBytes;'*) (*$HPPEMIT '} // namespace Sysutils'*) { ByteType indicates what kind of byte exists at the Index'th byte in S. Western locales always return mbSingleByte. Far East multibyte locales may also return mbLeadByte, indicating the byte is the first in a multibyte character sequence, and mbTrailByte, indicating that the byte is one of a sequence of bytes following a lead byte. One or more trail bytes can follow a lead byte, depending on locale charset encoding and OS platform. Parameters are assumed to be valid. } function ByteType(const S: string; Index: Integer): TMbcsByteType; { StrByteType works the same as ByteType, but on null-terminated PChar strings } function StrByteType(Str: PChar; Index: Cardinal): TMbcsByteType; { ByteToCharLen returns the character length of a MBCS string, scanning the string for up to MaxLen bytes. In multibyte character sets, the number of characters in a string may be less than the number of bytes. } function ByteToCharLen(const S: string; MaxLen: Integer): Integer; { CharToByteLen returns the byte length of a MBCS string, scanning the string for up to MaxLen characters. } function CharToByteLen(const S: string; MaxLen: Integer): Integer; { ByteToCharIndex returns the 1-based character index of the Index'th byte in a MBCS string. Returns zero if Index is out of range: (Index <= 0) or (Index > Length(S)) } function ByteToCharIndex(const S: string; Index: Integer): Integer; { CharToByteIndex returns the 1-based byte index of the Index'th character in a MBCS string. Returns zero if Index or Result are out of range: (Index <= 0) or (Index > Length(S)) or (Result would be > Length(S)) } function CharToByteIndex(const S: string; Index: Integer): Integer; { StrCharLength returns the number of bytes required by the first character in Str. In Windows, multibyte characters can be up to two bytes in length. In Linux, multibyte characters can be up to six bytes in length (UTF-8). } function StrCharLength(const Str: PChar): Integer; { StrNextChar returns a pointer to the first byte of the character following the character pointed to by Str. } function StrNextChar(const Str: PChar): PChar; { CharLength returns the number of bytes required by the character starting at bytes S[Index]. } function CharLength(const S: String; Index: Integer): Integer; { NextCharIndex returns the byte index of the first byte of the character following the character starting at S[Index]. } function NextCharIndex(const S: String; Index: Integer): Integer; { IsPathDelimiter returns True if the character at byte S[Index] is a PathDelimiter ('\' or '/'), and it is not a MBCS lead or trail byte. } function IsPathDelimiter(const S: string; Index: Integer): Boolean; { IsDelimiter returns True if the character at byte S[Index] matches any character in the Delimiters string, and the character is not a MBCS lead or trail byte. S may contain multibyte characters; Delimiters must contain only single byte characters. } function IsDelimiter(const Delimiters, S: string; Index: Integer): Boolean; { IncludeTrailingPathDelimiter returns the path with a PathDelimiter ('/' or '\') at the end. This function is MBCS enabled. } function IncludeTrailingPathDelimiter(const S: string): string; { IncludeTrailingBackslash is the old name for IncludeTrailingPathDelimiter. } function IncludeTrailingBackslash(const S: string): string; platform; inline; { ExcludeTrailingPathDelimiter returns the path without a PathDelimiter ('\' or '/') at the end. This function is MBCS enabled. } function ExcludeTrailingPathDelimiter(const S: string): string; { ExcludeTrailingBackslash is the old name for ExcludeTrailingPathDelimiter. } function ExcludeTrailingBackslash(const S: string): string; platform; inline; { LastDelimiter returns the byte index in S of the rightmost whole character that matches any character in Delimiters (except null (#0)). S may contain multibyte characters; Delimiters must contain only single byte non-null characters. Example: LastDelimiter('\.:', 'c:\filename.ext') returns 12. } function LastDelimiter(const Delimiters, S: string): Integer; { AnsiCompareFileName supports DOS file name comparison idiosyncracies in Far East locales (Zenkaku) on Windows. In non-MBCS locales on Windows, AnsiCompareFileName is identical to AnsiCompareText (case insensitive). On Linux, AnsiCompareFileName is identical to AnsiCompareStr (case sensitive). For general purpose file name comparisions, you should use this function instead of AnsiCompareText. } function AnsiCompareFileName(const S1, S2: string): Integer; inline; function SameFileName(const S1, S2: string): Boolean; inline; { AnsiLowerCaseFileName supports lowercase conversion idiosyncracies of DOS file names in Far East locales (Zenkaku). In non-MBCS locales, AnsiLowerCaseFileName is identical to AnsiLowerCase. } function AnsiLowerCaseFileName(const S: string): string; { AnsiUpperCaseFileName supports uppercase conversion idiosyncracies of DOS file names in Far East locales (Zenkaku). In non-MBCS locales, AnsiUpperCaseFileName is identical to AnsiUpperCase. } function AnsiUpperCaseFileName(const S: string): string; { AnsiPos: Same as Pos but supports MBCS strings } function AnsiPos(const Substr, S: string): Integer; { AnsiStrPos: Same as StrPos but supports MBCS strings } function AnsiStrPos(Str, SubStr: PChar): PChar; { AnsiStrRScan: Same as StrRScan but supports MBCS strings } function AnsiStrRScan(Str: PChar; Chr: Char): PChar; { AnsiStrScan: Same as StrScan but supports MBCS strings } function AnsiStrScan(Str: PChar; Chr: Char): PChar; { StringReplace replaces occurances of with in a given string. Assumes the string may contain Multibyte characters } type TReplaceFlags = set of (rfReplaceAll, rfIgnoreCase); function StringReplace(const S, OldPattern, NewPattern: string; Flags: TReplaceFlags): string; { WrapText will scan a string for BreakChars and insert the BreakStr at the last BreakChar position before MaxCol. Will not insert a break into an embedded quoted string (both ''' and '"' supported) } function WrapText(const Line, BreakStr: string; const BreakChars: TSysCharSet; MaxCol: Integer): string; overload; function WrapText(const Line: string; MaxCol: Integer = 45): string; overload; { FindCmdLineSwitch determines whether the string in the Switch parameter was passed as a command line argument to the application. SwitchChars identifies valid argument-delimiter characters (i.e., "-" and "/" are common delimiters). The IgnoreCase paramter controls whether a case-sensistive or case-insensitive search is performed. } const SwitchChars = {$IFDEF MSWINDOWS} ['/','-']; {$ENDIF} {$IFDEF LINUX} ['-']; {$ENDIF} function FindCmdLineSwitch(const Switch: string; const Chars: TSysCharSet; IgnoreCase: Boolean): Boolean; overload; { These versions of FindCmdLineSwitch are convenient for writing portable code. The characters that are valid to indicate command line switches vary on different platforms. For example, '/' cannot be used as a switch char on Linux because '/' is the path delimiter. } { This version uses SwitchChars defined above, and IgnoreCase False. } function FindCmdLineSwitch(const Switch: string): Boolean; overload; { This version uses SwitchChars defined above. } function FindCmdLineSwitch(const Switch: string; IgnoreCase: Boolean): Boolean; overload; { FreeAndNil frees the given TObject instance and sets the variable reference to nil. Be careful to only pass TObjects to this routine. } procedure FreeAndNil(var Obj); { Interface support routines } function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean; overload; function Supports(const Instance: TObject; const IID: TGUID; out Intf): Boolean; overload; function Supports(const Instance: IInterface; const IID: TGUID): Boolean; overload; function Supports(const Instance: TObject; const IID: TGUID): Boolean; overload; function Supports(const AClass: TClass; const IID: TGUID): Boolean; overload; function CreateGUID(out Guid: TGUID): HResult; {$IFDEF MSWINDOWS} stdcall; {$ENDIF} function StringToGUID(const S: string): TGUID; function GUIDToString(const GUID: TGUID): string; function IsEqualGUID(const guid1, guid2: TGUID): Boolean; {$IFDEF MSWINDOWS} stdcall; {$EXTERNALSYM IsEqualGUID} {$ENDIF} { Package support routines } { Package Info flags } const pfNeverBuild = $00000001; pfDesignOnly = $00000002; pfRunOnly = $00000004; pfIgnoreDupUnits = $00000008; pfModuleTypeMask = $C0000000; pfExeModule = $00000000; pfPackageModule = $40000000; pfProducerMask = $0C000000; pfV3Produced = $00000000; pfProducerUndefined = $04000000; pfBCB4Produced = $08000000; pfDelphi4Produced = $0C000000; pfLibraryModule = $80000000; { Unit info flags } const ufMainUnit = $01; ufPackageUnit = $02; ufWeakUnit = $04; ufOrgWeakUnit = $08; ufImplicitUnit = $10; ufWeakPackageUnit = ufPackageUnit or ufWeakUnit; {$IFDEF LINUX} var PkgLoadingMode: Integer = RTLD_LAZY; {$ENDIF} { Procedure type of the callback given to GetPackageInfo. Name is the actual name of the package element. If IsUnit is True then Name is the name of a contained unit; a required package if False. Param is the value passed to GetPackageInfo } type TNameType = (ntContainsUnit, ntRequiresPackage, ntDcpBpiName); TPackageInfoProc = procedure (const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer); { LoadPackage loads a given package DLL, checks for duplicate units and calls the initialization blocks of all the contained units } function LoadPackage(const Name: string): HMODULE; { UnloadPackage does the opposite of LoadPackage by calling the finalization blocks of all contained units, then unloading the package DLL } procedure UnloadPackage(Module: HMODULE); { GetPackageInfo accesses the given package's info table and enumerates all the contained units and required packages } procedure GetPackageInfo(Module: HMODULE; Param: Pointer; var Flags: Integer; InfoProc: TPackageInfoProc); { GetPackageDescription loads the description resource from the package library. If the description resource does not exist, an empty string is returned. } function GetPackageDescription(ModuleName: PChar): string; { InitializePackage validates and initializes the given package DLL } procedure InitializePackage(Module: HMODULE); { FinalizePackage finalizes the given package DLL } procedure FinalizePackage(Module: HMODULE); { RaiseLastOSError calls GetLastError to retrieve the code for the last occuring error in a call to an OS or system library function. If GetLastError returns an error code, RaiseLastOSError raises an EOSError exception with the error code and a system-provided message associated with with error. } procedure RaiseLastOSError; overload; procedure RaiseLastOSError(LastError: Integer); overload; {$IFDEF MSWINDOWS} procedure RaiseLastWin32Error; deprecated; // use RaiseLastOSError { Win32Check is used to check the return value of a Win32 API function } { which returns a BOOL to indicate success. If the Win32 API function } { returns False (indicating failure), Win32Check calls RaiseLastOSError } { to raise an exception. If the Win32 API function returns True, } { Win32Check returns True. } function Win32Check(RetVal: BOOL): BOOL; platform; {$ENDIF} { Termination procedure support } type TTerminateProc = function: Boolean; { Call AddTerminateProc to add a terminate procedure to the system list of } { termination procedures. Delphi will call all of the function in the } { termination procedure list before an application terminates. The user- } { defined TermProc function should return True if the application can } { safely terminate or False if the application cannot safely terminate. } { If one of the functions in the termination procedure list returns False, } { the application will not terminate. } procedure AddTerminateProc(TermProc: TTerminateProc); { CallTerminateProcs is called by VCL when an application is about to } { terminate. It returns True only if all of the functions in the } { system's terminate procedure list return True. This function is } { intended only to be called by Delphi, and it should not be called } { directly. } function CallTerminateProcs: Boolean; function GDAL: LongWord; procedure RCS; procedure RPR; { HexDisplayPrefix contains the prefix to display on hexadecimal values - '$' for Pascal syntax, '0x' for C++ syntax. This is for display only - this does not affect the string-to-integer conversion routines. } var HexDisplayPrefix: string = '$'; {$IFDEF MSWINDOWS} { The GetDiskFreeSpace Win32 API does not support partitions larger than 2GB under Win95. A new Win32 function, GetDiskFreeSpaceEx, supports partitions larger than 2GB but only exists on Win NT 4.0 and Win95 OSR2. The GetDiskFreeSpaceEx function pointer variable below will be initialized at startup to point to either the actual OS API function if it exists on the system, or to an internal Delphi function if it does not. When running on Win95 pre-OSR2, the output of this function will still be limited to the 2GB range reported by Win95, but at least you don't have to worry about which API function to call in code you write. } var GetDiskFreeSpaceEx: function (Directory: PChar; var FreeAvailable, TotalSpace: TLargeInteger; TotalFree: PLargeInteger): Bool stdcall = nil; { SafeLoadLibrary calls LoadLibrary, disabling normal Win32 error message popup dialogs if the requested file can't be loaded. SafeLoadLibrary also preserves the current FPU control word (precision, exception masks) across the LoadLibrary call (in case the DLL you're loading hammers the FPU control word in its initialization, as many MS DLLs do)} function SafeLoadLibrary(const FileName: string; ErrorMode: UINT = SEM_NOOPENFILEERRORBOX): HMODULE; {$ENDIF} {$IFDEF LINUX} { SafeLoadLibrary calls LoadLibrary preserves the current FPU control word (precision, exception masks) across the LoadLibrary call (in case the shared object you're loading hammers the FPU control word in its initialization, as many MS DLLs do) } function SafeLoadLibrary(const FileName: string; Dummy: LongWord = 0): HMODULE; {$ENDIF} { Thread synchronization } { IReadWriteSync is an abstract interface for general read/write synchronization. Some implementations may allow simultaneous readers, but writers always have exclusive locks. Worst case is that this class behaves identical to a TRTLCriticalSection - that is, read and write locks block all other threads. } type IReadWriteSync = interface ['{7B108C52-1D8F-4CDB-9CDF-57E071193D3F}'] procedure BeginRead; procedure EndRead; function BeginWrite: Boolean; procedure EndWrite; end; TSimpleRWSync = class(TInterfacedObject, IReadWriteSync) private FLock: TRTLCriticalSection; public constructor Create; destructor Destroy; override; procedure BeginRead; procedure EndRead; function BeginWrite: Boolean; procedure EndWrite; end; { TThreadLocalCounter This class implements a lightweight non-blocking thread local storage mechanism specifically built for tracking per-thread recursion counts in TMultiReadExclusiveWriteSynchronizer. This class is intended for Delphi RTL internal use only. In the future it may be generalized and "hardened" for general application use, but until then leave it alone. Rules of Use: The tls object must be opened to gain access to the thread-specific data structure. If a threadinfo block does not exist for the current thread, Open will allocate one. Every call to Open must be matched with a call to Close. The pointer returned by Open is invalid after the matching call to Close (or Delete). The thread info structure is unique to each thread. Once you have it, it's yours. You don't need to guard against concurrent access to the thread data by multiple threads - your thread is the only thread that will ever have access to the structure that Open returns. The thread info structure is allocated and owned by the tls object. If you put allocated pointers in the thread info make sure you free them before you delete the threadinfo node. When thread data is no longer needed, call the Delete method on the pointer. This must be done between calls to Open and Close. You should not use the thread data after calling Delete. Important: Do not keep the tls object open for long periods of time. In particular, be careful not to wait on a thread synchronization event or critical section while you have the tls object open. It's much better to open and close the tls object before and after the blocking event than to leave the tls object open while waiting. Implementation Notes: The main purpose of this storage class is to provide thread-local storage without using limited / problematic OS tls slots and without requiring expensive blocking thread synchronization. This class performs no blocking waits or spin loops! (except for memory allocation) Thread info is kept in linked lists to facilitate non-blocking threading techniques. A hash table indexed by a hash of the current thread ID reduces linear search times. When a node is deleted, its thread ID is stripped and its Active field is set to zero, meaning it is available to be recycled for other threads. Nodes are never removed from the live list or freed while the class is in use. All nodes are freed when the class is destroyed. Nodes are only inserted at the front of the list (each list in the hash table). The linked list management relies heavily on InterlockedExchange to perform atomic node pointer replacements. There are brief windows of time where the linked list may be circular while a two-step insertion takes place. During that brief window, other threads traversing the lists may see the same node more than once more than once. (pun!) This is fine for what this implementation needs. Don't do anything silly like try to count the nodes during a traversal. } type PThreadInfo = ^TThreadInfo; TThreadInfo = record Next: PThreadInfo; ThreadID: Cardinal; Active: Integer; RecursionCount: Cardinal; end; TThreadLocalCounter = class private FHashTable: array [0..15] of PThreadInfo; function HashIndex: Byte; function Recycle: PThreadInfo; public destructor Destroy; override; procedure Open(var Thread: PThreadInfo); procedure Delete(var Thread: PThreadInfo); procedure Close(var Thread: PThreadInfo); end; {$IFDEF MSWINDOWS} { TMultiReadExclusiveWriteSynchronizer minimizes thread serialization to gain read access to a resource shared among threads while still providing complete exclusivity to callers needing write access to the shared resource. (multithread shared reads, single thread exclusive write) Read locks are allowed while owning a write lock. Read locks can be promoted to write locks within the same thread. (BeginRead, BeginWrite, EndWrite, EndRead) Note: Other threads have an opportunity to modify the protected resource when you call BeginWrite before you are granted the write lock, even if you already have a read lock open. Best policy is not to retain any info about the protected resource (such as count or size) across a write lock. Always reacquire samples of the protected resource after acquiring or releasing a write lock. The function result of BeginWrite indicates whether another thread got the write lock while the current thread was waiting for the write lock. Return value of True means that the write lock was acquired without any intervening modifications by other threads. Return value of False means another thread got the write lock while you were waiting, so the resource protected by the MREWS object should be considered modified. Any samples of the protected resource should be discarded. In general, it's better to just always reacquire samples of the protected resource after obtaining a write lock. The boolean result of BeginWrite and the RevisionLevel property help cases where reacquiring the samples is computationally expensive or time consuming. RevisionLevel changes each time a write lock is granted. You can test RevisionLevel for equality with a previously sampled value of the property to determine if a write lock has been granted, implying that the protected resource may be changed from its state when the original RevisionLevel value was sampled. Do not rely on the sequentiality of the current RevisionLevel implementation (it will wrap around to zero when it tops out). Do not perform greater than / less than comparisons on RevisionLevel values. RevisionLevel indicates only the stability of the protected resource since your original sample. It should not be used to calculate how many revisions have been made. } type TMultiReadExclusiveWriteSynchronizer = class(TInterfacedObject, IReadWriteSync) private FSentinel: Integer; FReadSignal: THandle; FWriteSignal: THandle; FWaitRecycle: Cardinal; FWriteRecursionCount: Cardinal; tls: TThreadLocalCounter; FWriterID: Cardinal; FRevisionLevel: Cardinal; procedure BlockReaders; procedure UnblockReaders; procedure UnblockOneWriter; procedure WaitForReadSignal; procedure WaitForWriteSignal; {$IFDEF DEBUG_MREWS} procedure Debug(const Msg: string); {$ENDIF} public constructor Create; destructor Destroy; override; procedure BeginRead; procedure EndRead; function BeginWrite: Boolean; procedure EndWrite; property RevisionLevel: Cardinal read FRevisionLevel; end; {$ELSE} type TMultiReadExclusiveWriteSynchronizer = TSimpleRWSync; {$ENDIF} type TMREWSync = TMultiReadExclusiveWriteSynchronizer; // short form function GetEnvironmentVariable(const Name: string): string; overload; {$IFDEF LINUX} function InterlockedIncrement(var I: Integer): Integer; function InterlockedDecrement(var I: Integer): Integer; function InterlockedExchange(var A: Integer; B: Integer): Integer; function InterlockedExchangeAdd(var A: Integer; B: Integer): Integer; {$ENDIF} implementation {$IFDEF LINUX} { Exceptions raised in methods that are safecall will be filtered through the virtual method SafeCallException on the class. The implementation of this method under Linux has the option of setting the following thread vars: SafeCallExceptionMsg, SafeCallExceptionAddr. If these are set, then the implementation of SafeCallError here will reraise a generic exception based on these. One might consider actually having the SafeCallException implementation store off the exception object itself, but this raises the issue that the exception object might have to live a long time (if an external application calls a Delphi safecall method). Since an arbitrary exception object could be holding large resources hostage, we hold only the string and address as a hedge. } threadvar SafeCallExceptionMsg: String; SafeCallExceptionAddr: Pointer; procedure SetSafeCallExceptionMsg(const Msg: String); begin SafeCallExceptionMsg := Msg; end; procedure SetSafeCallExceptionAddr(Addr: Pointer); begin SafeCallExceptionAddr := Addr; end; function GetSafeCallExceptionMsg: String; begin Result := SafeCallExceptionMsg; end; function GetSafeCallExceptionAddr: Pointer; begin Result := SafeCallExceptionAddr; end; {$ENDIF} { Utility routines } procedure DivMod(Dividend: Integer; Divisor: Word; var Result, Remainder: Word); asm PUSH EBX MOV EBX,EDX MOV EDX,EAX SHR EDX,16 DIV BX MOV EBX,Remainder MOV [ECX],AX MOV [EBX],DX POP EBX end; {$IFDEF PIC} function GetGOT: Pointer; export; begin asm MOV Result,EBX end; end; {$ENDIF} procedure ConvertError(const ResString: string); local; begin raise EConvertError.Create(ResString); end; procedure ConvertErrorFmt(const ResString: string; const Args: array of const); local; begin raise EConvertError.CreateFmt(ResString, Args); end; {$IFDEF MSWINDOWS} {$EXTERNALSYM CoCreateGuid} function CoCreateGuid(out guid: TGUID): HResult; stdcall; external 'ole32.dll' name 'CoCreateGuid'; function CreateGUID(out Guid: TGUID): HResult; begin Result := CoCreateGuid(Guid); end; //function CreateGUID; external 'ole32.dll' name 'CoCreateGuid'; {$ENDIF} {$IFDEF LINUX} { CreateGUID } { libuuid.so implements the tricky code to create GUIDs using the MAC address of the network adapter plus other flavor bits. libuuid.so is currently distributed with the ext2 file system package, but does not depend upon the ext2 file system libraries. Ideally, libuuid.so should be distributed separately. If you do not have libuuid.so.1 on your Linux distribution, you can extract the library from the e2fsprogs RPM. Note: Do not use the generic uuid_generate function in libuuid.so. In the current implementation (e2fsprogs-1.19), uuid_generate gives preference to generating guids entirely from random number streams over generating guids based on the NIC MAC address. No matter how "random" a random number generator is, it will never produce guids that can be guaranteed unique across all systems on the planet. MAC-address based guids are guaranteed unique because the MAC address of the NIC is guaranteed unique by the manufacturer. For this reason, we call uuid_generate_time instead of the generic uuid_generate. uuid_generate_time constructs the guid using the MAC address, and falls back to randomness if no NIC can be found. } var libuuidHandle: Pointer; uuid_generate_time: procedure (out Guid: TGUID) cdecl; function CreateGUID(out Guid: TGUID): HResult; const E_NOTIMPL = HRESULT($80004001); begin Result := E_NOTIMPL; if libuuidHandle = nil then begin libuuidHandle := dlopen('libuuid.so.1', RTLD_LAZY); if libuuidHandle = nil then Exit; uuid_generate_time := dlsym(libuuidHandle, 'uuid_generate_time'); if @uuid_generate_time = nil then Exit; end; uuid_generate_time(Guid); Result := 0; end; {$ENDIF} {$IFDEF MSWINDOWS} function StringFromCLSID(const clsid: TGUID; out psz: PWideChar): HResult; stdcall; external 'ole32.dll' name 'StringFromCLSID'; procedure CoTaskMemFree(pv: Pointer); stdcall; external 'ole32.dll' name 'CoTaskMemFree'; function CLSIDFromString(psz: PWideChar; out clsid: TGUID): HResult; stdcall; external 'ole32.dll' name 'CLSIDFromString'; {$ENDIF MSWINDOWS} function StringToGUID(const S: string): TGUID; {$IFDEF MSWINDOWS} begin if not Succeeded(CLSIDFromString(PWideChar(WideString(S)), Result)) then ConvertErrorFmt(SInvalidGUID, [s]); end; {$ENDIF} {$IFDEF LINUX} procedure InvalidGUID; begin ConvertErrorFmt(@SInvalidGUID, [s]); end; function HexChar(c: Char): Byte; begin case c of '0'..'9': Result := Byte(c) - Byte('0'); 'a'..'f': Result := (Byte(c) - Byte('a')) + 10; 'A'..'F': Result := (Byte(c) - Byte('A')) + 10; else InvalidGUID; Result := 0; end; end; function HexByte(p: PChar): Char; begin Result := Char((HexChar(p[0]) shl 4) + HexChar(p[1])); end; var i: Integer; src, dest: PChar; begin if ((Length(S) <> 38) or (s[1] <> '{')) then InvalidGUID; dest := @Result; src := PChar(s); Inc(src); for i := 0 to 3 do dest[i] := HexByte(src+(3-i)*2); Inc(src, 8); Inc(dest, 4); if src[0] <> '-' then InvalidGUID; Inc(src); for i := 0 to 1 do begin dest^ := HexByte(src+2); Inc(dest); dest^ := HexByte(src); Inc(dest); Inc(src, 4); if src[0] <> '-' then InvalidGUID; inc(src); end; dest^ := HexByte(src); Inc(dest); Inc(src, 2); dest^ := HexByte(src); Inc(dest); Inc(src, 2); if src[0] <> '-' then InvalidGUID; Inc(src); for i := 0 to 5 do begin dest^ := HexByte(src); Inc(dest); Inc(src, 2); end; end; {$ENDIF LINUX} {$IFDEF MSWINDOWS} function GUIDToString(const GUID: TGUID): string; var P: PWideChar; begin if not Succeeded(StringFromCLSID(GUID, P)) then ConvertError(SInvalidGUID); Result := P; CoTaskMemFree(P); end; {$ENDIF} {$IFDEF LINUX} function GUIDToString(const GUID: TGUID): string; begin SetLength(Result, 38); StrLFmt(PChar(Result), 38,'{%.8x-%.4x-%.4x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x}', // do not localize [GUID.D1, GUID.D2, GUID.D3, GUID.D4[0], GUID.D4[1], GUID.D4[2], GUID.D4[3], GUID.D4[4], GUID.D4[5], GUID.D4[6], GUID.D4[7]]); end; {$ENDIF} {$IFDEF MSWINDOWS} function IsEqualGUID; external 'ole32.dll' name 'IsEqualGUID'; {$ENDIF MSWINDOWS} {$IFDEF LINUX} function IsEqualGUID(const guid1, guid2: TGUID): Boolean; var a, b: PIntegerArray; begin a := PIntegerArray(@guid1); b := PIntegerArray(@guid2); Result := (a^[0] = b^[0]) and (a^[1] = b^[1]) and (a^[2] = b^[2]) and (a^[3] = b^[3]); end; {$ENDIF LINUX} { Memory management routines } function AllocMem(Size: Cardinal): Pointer; begin GetMem(Result, Size); FillChar(Result^, Size, 0); end; { Exit procedure handling } type PExitProcInfo = ^TExitProcInfo; TExitProcInfo = record Next: PExitProcInfo; SaveExit: Pointer; Proc: TProcedure; end; var ExitProcList: PExitProcInfo = nil; procedure DoExitProc; var P: PExitProcInfo; Proc: TProcedure; begin P := ExitProcList; ExitProcList := P^.Next; ExitProc := P^.SaveExit; Proc := P^.Proc; Dispose(P); Proc; end; procedure AddExitProc(Proc: TProcedure); var P: PExitProcInfo; begin New(P); P^.Next := ExitProcList; P^.SaveExit := ExitProc; P^.Proc := Proc; ExitProcList := P; ExitProc := @DoExitProc; end; { String handling routines } function NewStr(const S: string): PString; begin if S = '' then Result := NullStr else begin New(Result); Result^ := S; end; end; procedure DisposeStr(P: PString); begin if (P <> nil) and (P^ <> '') then Dispose(P); end; procedure AssignStr(var P: PString; const S: string); var Temp: PString; begin Temp := P; P := NewStr(S); DisposeStr(Temp); end; procedure AppendStr(var Dest: string; const S: string); begin Dest := Dest + S; end; function UpperCase(const S: string): string; var Ch: Char; L: Integer; Source, Dest: PChar; begin L := Length(S); SetLength(Result, L); Source := Pointer(S); Dest := Pointer(Result); while L <> 0 do begin Ch := Source^; if (Ch >= 'a') and (Ch <= 'z') then Dec(Ch, 32); Dest^ := Ch; Inc(Source); Inc(Dest); Dec(L); end; end; function UpperCase(const S: string; LocaleOptions: TLocaleOptions): string; begin if LocaleOptions = loUserLocale then Result := AnsiUpperCase(S) else Result := UpperCase(S); end; function LowerCase(const S: string): string; var Ch: Char; L: Integer; Source, Dest: PChar; begin L := Length(S); SetLength(Result, L); Source := Pointer(S); Dest := Pointer(Result); while L <> 0 do begin Ch := Source^; if (Ch >= 'A') and (Ch <= 'Z') then Inc(Ch, 32); Dest^ := Ch; Inc(Source); Inc(Dest); Dec(L); end; end; function LowerCase(const S: string; LocaleOptions: TLocaleOptions): string; begin if LocaleOptions = loUserLocale then Result := AnsiLowerCase(S) else Result := LowerCase(S); end; function CompareStr(const S1, S2: string): Integer; assembler; asm PUSH ESI PUSH EDI MOV ESI,EAX MOV EDI,EDX OR EAX,EAX JE @@1 MOV EAX,[EAX-4] @@1: OR EDX,EDX JE @@2 MOV EDX,[EDX-4] @@2: MOV ECX,EAX CMP ECX,EDX JBE @@3 MOV ECX,EDX @@3: CMP ECX,ECX REPE CMPSB JE @@4 MOVZX EAX,BYTE PTR [ESI-1] MOVZX EDX,BYTE PTR [EDI-1] @@4: SUB EAX,EDX POP EDI POP ESI end; function CompareStr(const S1, S2: string; LocaleOptions: TLocaleOptions): Integer; begin if LocaleOptions = loUserLocale then Result := AnsiCompareStr(S1, S2) else Result := CompareStr(S1, S2); end; function SameStr(const S1, S2: string): Boolean; asm CMP EAX,EDX JZ @1 OR EAX,EAX JZ @2 OR EDX,EDX JZ @3 MOV ECX,[EAX-4] CMP ECX,[EDX-4] JNE @3 CALL CompareStr TEST EAX,EAX JNZ @3 @1: MOV AL,1 @2: RET @3: XOR EAX,EAX end; function SameStr(const S1, S2: string; LocaleOptions: TLocaleOptions): Boolean; begin if LocaleOptions = loUserLocale then Result := AnsiSameStr(S1, S2) else Result := SameStr(S1, S2); end; function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; assembler; asm PUSH ESI PUSH EDI MOV ESI,P1 MOV EDI,P2 MOV EDX,ECX XOR EAX,EAX AND EDX,3 SAR ECX,2 JS @@1 // Negative Length implies identity. REPE CMPSD JNE @@2 MOV ECX,EDX REPE CMPSB JNE @@2 @@1: INC EAX @@2: POP EDI POP ESI end; (* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The implementation of function CompareText is subject to the * Mozilla Public License Version 1.1 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Fastcode * * The Initial Developer of the Original Code is * Fastcode * * Portions created by the Initial Developer are Copyright (C) 2002-2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): John O'Harrow * * ***** END LICENSE BLOCK ***** *) function CompareText(const S1, S2: string): Integer; asm TEST EAX, EAX JNZ @@CheckS2 TEST EDX, EDX JZ @@Ret MOV EAX, [EDX-4] NEG EAX @@Ret: RET @@CheckS2: TEST EDX, EDX JNZ @@Compare MOV EAX, [EAX-4] RET @@Compare: PUSH EBX PUSH EBP PUSH ESI MOV EBP, [EAX-4] // length(S1) MOV EBX, [EDX-4] // length(S2) SUB EBP, EBX // Result if All Compared Characters Match SBB ECX, ECX AND ECX, EBP ADD ECX, EBX // min(length(S1),length(S2)) = Compare Length LEA ESI, [EAX+ECX] // Last Compare Position in S1 ADD EDX, ECX // Last Compare Position in S2 NEG ECX JZ @@SetResult // Exit if Smallest Length = 0 @@Loop: // Load Next 2 Chars from S1 and S2 // May Include Null Terminator} MOVZX EAX, WORD PTR [ESI+ECX] MOVZX EBX, WORD PTR [EDX+ECX] CMP EAX, EBX JE @@Next // Next 2 Chars Match CMP AL, BL JE @@SecondPair // First Char Matches MOV AH, 0 MOV BH, 0 CMP AL, 'a' JL @@UC1 CMP AL, 'z' JG @@UC1 SUB EAX, 'a'-'A' @@UC1: CMP BL, 'a' JL @@UC2 CMP BL, 'z' JG @@UC2 SUB EBX, 'a'-'A' @@UC2: SUB EAX, EBX // Compare Both Uppercase Chars JNE @@Done // Exit with Result in EAX if Not Equal MOVZX EAX, WORD PTR [ESI+ECX] // Reload Same 2 Chars from S1 MOVZX EBX, WORD PTR [EDX+ECX] // Reload Same 2 Chars from S2 CMP AH, BH JE @@Next // Second Char Matches @@SecondPair: SHR EAX, 8 SHR EBX, 8 CMP AL, 'a' JL @@UC3 CMP AL, 'z' JG @@UC3 SUB EAX, 'a'-'A' @@UC3: CMP BL, 'a' JL @@UC4 CMP BL, 'z' JG @@UC4 SUB EBX, 'a'-'A' @@UC4: SUB EAX, EBX // Compare Both Uppercase Chars JNE @@Done // Exit with Result in EAX if Not Equal @@Next: ADD ECX, 2 JL @@Loop // Loop until All required Chars Compared @@SetResult: MOV EAX, EBP // All Matched, Set Result from Lengths @@Done: POP ESI POP EBP POP EBX end; function CompareText(const S1, S2: string; LocaleOptions: TLocaleOptions): Integer; begin if LocaleOptions = loUserLocale then Result := AnsiCompareText(S1, S2) else Result := CompareText(S1, S2); end; function SameText(const S1, S2: string): Boolean; assembler; asm CMP EAX,EDX JZ @1 OR EAX,EAX JZ @2 OR EDX,EDX JZ @3 MOV ECX,[EAX-4] CMP ECX,[EDX-4] JNE @3 CALL CompareText TEST EAX,EAX JNZ @3 @1: MOV AL,1 @2: RET @3: XOR EAX,EAX end; function SameText(const S1, S2: string; LocaleOptions: TLocaleOptions): Boolean; begin if LocaleOptions = loUserLocale then Result := AnsiSameText(S1, S2) else Result := SameText(S1, S2); end; function AnsiUpperCase(const S: string): string; {$IFDEF MSWINDOWS} var Len: Integer; begin Len := Length(S); SetString(Result, PChar(S), Len); if Len > 0 then CharUpperBuff(Pointer(Result), Len); end; {$ENDIF} {$IFDEF LINUX} begin Result := WideUpperCase(S); end; {$ENDIF} function AnsiLowerCase(const S: string): string; {$IFDEF MSWINDOWS} var Len: Integer; begin Len := Length(S); SetString(Result, PChar(S), Len); if Len > 0 then CharLowerBuff(Pointer(Result), Len); end; {$ENDIF} {$IFDEF LINUX} begin Result := WideLowerCase(S); end; {$ENDIF} function AnsiCompareStr(const S1, S2: string): Integer; begin {$IFDEF MSWINDOWS} Result := CompareString(LOCALE_USER_DEFAULT, 0, PChar(S1), Length(S1), PChar(S2), Length(S2)) - 2; {$ENDIF} {$IFDEF LINUX} // glibc 2.1.2 / 2.1.3 implementations of strcoll() and strxfrm() // have severe capacity limits. Comparing two 100k strings may // exhaust the stack and kill the process. // Fixed in glibc 2.1.91 and later. Result := strcoll(PChar(S1), PChar(S2)); {$ENDIF} end; function AnsiSameStr(const S1, S2: string): Boolean; begin Result := AnsiCompareStr(S1, S2) = 0; end; function AnsiCompareText(const S1, S2: string): Integer; begin {$IFDEF MSWINDOWS} Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PChar(S1), Length(S1), PChar(S2), Length(S2)) - 2; {$ENDIF} {$IFDEF LINUX} Result := WideCompareText(S1, S2); {$ENDIF} end; function AnsiSameText(const S1, S2: string): Boolean; begin Result := AnsiCompareText(S1, S2) = 0; end; function AnsiStrComp(S1, S2: PChar): Integer; begin {$IFDEF MSWINDOWS} Result := CompareString(LOCALE_USER_DEFAULT, 0, S1, -1, S2, -1) - 2; {$ENDIF} {$IFDEF LINUX} Result := strcoll(S1, S2); {$ENDIF} end; function AnsiStrIComp(S1, S2: PChar): Integer; begin {$IFDEF MSWINDOWS} Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, S1, -1, S2, -1) - 2; {$ENDIF} {$IFDEF LINUX} Result := AnsiCompareText(S1, S2); {$ENDIF} end; // StrLenLimit: Scan Src for a null terminator up to MaxLen bytes function StrLenLimit(Src: PChar; MaxLen: Cardinal): Cardinal; begin if Src = nil then begin Result := 0; Exit; end; Result := MaxLen; while (Src^ <> #0) and (Result > 0) do begin Inc(Src); Dec(Result); end; Result := MaxLen - Result; end; { StrBufLimit: Return a pointer to a buffer that contains no more than MaxLen bytes of Src, avoiding heap allocation if possible. If clipped Src length is less than MaxLen, return Src. Allocated = False. If clipped Src length is less than StaticBufLen, return StaticBuf with a copy of Src. Allocated = False. Otherwise, return a heap allocated buffer with a copy of Src. Allocated = True. } function StrBufLimit(Src: PChar; MaxLen: Cardinal; StaticBuf: PChar; StaticBufLen: Cardinal; var Allocated: Boolean): PChar; var Len: Cardinal; begin Len := StrLenLimit(Src, MaxLen); Allocated := False; if Len < MaxLen then Result := Src else begin if Len < StaticBufLen then Result := StaticBuf else begin GetMem(Result, Len+1); Allocated := True; end; Move(Src^, Result^, Len); Result[Len] := #0; end; end; function InternalAnsiStrLComp(S1, S2: PChar; MaxLen: Cardinal; CaseSensitive: Boolean): Integer; var Buf1, Buf2: array [0..4095] of Char; P1, P2: PChar; Allocated1, Allocated2: Boolean; begin // glibc has no length-limited strcoll! P1 := nil; P2 := nil; Allocated1 := False; Allocated2 := False; try P1 := StrBufLimit(S1, MaxLen, Buf1, High(Buf1), Allocated1); P2 := StrBufLimit(S2, MaxLen, Buf2, High(Buf2), Allocated2); if CaseSensitive then Result := AnsiStrComp(P1, P2) else Result := AnsiStrIComp(P1, P2); finally if Allocated1 then FreeMem(P1); if Allocated2 then FreeMem(P2); end; end; function AnsiStrLComp(S1, S2: PChar; MaxLen: Cardinal): Integer; {$IFDEF MSWINDOWS} begin Result := CompareString(LOCALE_USER_DEFAULT, 0, S1, MaxLen, S2, MaxLen) - 2; end; {$ENDIF} {$IFDEF LINUX} begin Result := InternalAnsiStrLComp(S1, S2, MaxLen, True); end; {$ENDIF} function AnsiStrLIComp(S1, S2: PChar; MaxLen: Cardinal): Integer; begin {$IFDEF MSWINDOWS} Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, S1, MaxLen, S2, MaxLen) - 2; {$ENDIF} {$IFDEF LINUX} Result := InternalAnsiStrLComp(S1, S2, MaxLen, False); {$ENDIF} end; function AnsiStrLower(Str: PChar): PChar; {$IFDEF MSWINDOWS} begin CharLower(Str); Result := Str; end; {$ENDIF} {$IFDEF LINUX} var Temp: WideString; Squish: AnsiString; I: Integer; begin Temp := Str; // expand and copy multibyte to widechar for I := 1 to Length(Temp) do Temp[I] := WideChar(towlower(UCS4Char(Temp[I]))); Squish := Temp; // reduce and copy widechar to multibyte if Cardinal(Length(Squish)) > StrLen(Str) then raise ERangeError.CreateRes(@SRangeError); Move(Squish[1], Str^, Length(Squish)); Result := Str; end; {$ENDIF} function AnsiStrUpper(Str: PChar): PChar; {$IFDEF MSWINDOWS} begin CharUpper(Str); Result := Str; end; {$ENDIF} {$IFDEF LINUX} var Temp: WideString; Squish: AnsiString; I: Integer; begin Temp := Str; // expand and copy multibyte to widechar for I := 1 to Length(Temp) do Temp[I] := WideChar(towupper(UCS4Char(Temp[I]))); Squish := Temp; // reduce and copy widechar to multibyte if Cardinal(Length(Squish)) > StrLen(Str) then raise ERangeError.CreateRes(@SRangeError); Move(Squish[1], Str^, Length(Squish)); Result := Str; end; {$ENDIF} function WideUpperCase(const S: WideString): WideString; {$IFDEF MSWINDOWS} var Len: Integer; begin Len := Length(S); SetString(Result, PWideChar(S), Len); if Len > 0 then CharUpperBuffW(Pointer(Result), Len); end; {$ENDIF} {$IFDEF LINUX} var I: Integer; P: PWideChar; begin SetLength(Result, Length(S)); P := @Result[1]; for I := 1 to Length(S) do P[I-1] := WideChar(towupper(UCS4Char(S[I]))); end; {$ENDIF} function WideLowerCase(const S: WideString): WideString; {$IFDEF MSWINDOWS} var Len: Integer; begin Len := Length(S); SetString(Result, PWideChar(S), Len); if Len > 0 then CharLowerBuffW(Pointer(Result), Len); end; {$ENDIF} {$IFDEF LINUX} var I: Integer; P: PWideChar; begin SetLength(Result, Length(S)); P := @Result[1]; for I := 1 to Length(S) do P[I-1] := WideChar(towlower(UCS4Char(S[I]))); end; {$ENDIF} {$IFDEF MSWINDOWS} function DumbItDownFor95(const S1, S2: WideString; CmpFlags: Integer): Integer; var a1, a2: AnsiString; begin a1 := s1; a2 := s2; Result := CompareStringA(LOCALE_USER_DEFAULT, CmpFlags, PChar(a1), Length(a1), PChar(a2), Length(a2)) - 2; end; {$ENDIF} function WideCompareStr(const S1, S2: WideString): Integer; {$IFDEF MSWINDOWS} begin SetLastError(0); Result := CompareStringW(LOCALE_USER_DEFAULT, 0, PWideChar(S1), Length(S1), PWideChar(S2), Length(S2)) - 2; case GetLastError of 0: ; ERROR_CALL_NOT_IMPLEMENTED: Result := DumbItDownFor95(S1, S2, 0); else RaiseLastOSError; end; end; {$ENDIF} {$IFDEF LINUX} var UCS4_S1, UCS4_S2: UCS4String; begin UCS4_S1 := WideStringToUCS4String(S1); UCS4_S2 := WideStringToUCS4String(S2); // glibc 2.1.2 / 2.1.3 implementations of wcscoll() and wcsxfrm() // have severe capacity limits. Comparing two 100k strings may // exhaust the stack and kill the process. // Fixed in glibc 2.1.91 and later. SetLastError(0); Result := wcscoll(PUCS4Chars(UCS4_S1), PUCS4Chars(UCS4_S2)); if GetLastError <> 0 then RaiseLastOSError; end; {$ENDIF} function WideSameStr(const S1, S2: WideString): Boolean; begin Result := WideCompareStr(S1, S2) = 0; end; function WideCompareText(const S1, S2: WideString): Integer; begin {$IFDEF MSWINDOWS} SetLastError(0); Result := CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PWideChar(S1), Length(S1), PWideChar(S2), Length(S2)) - 2; case GetLastError of 0: ; ERROR_CALL_NOT_IMPLEMENTED: Result := DumbItDownFor95(S1, S2, NORM_IGNORECASE); else RaiseLastOSError; end; {$ENDIF} {$IFDEF LINUX} Result := WideCompareStr(WideUpperCase(S1), WideUpperCase(S2)); {$ENDIF} end; function WideSameText(const S1, S2: WideString): Boolean; begin Result := WideCompareText(S1, S2) = 0; end; function Trim(const S: string): string; var I, L: Integer; begin L := Length(S); I := 1; while (I <= L) and (S[I] <= ' ') do Inc(I); if I > L then Result := '' else begin while S[L] <= ' ' do Dec(L); Result := Copy(S, I, L - I + 1); end; end; function Trim(const S: WideString): WideString; var I, L: Integer; begin L := Length(S); I := 1; while (I <= L) and (S[I] <= ' ') do Inc(I); if I > L then Result := '' else begin while S[L] <= ' ' do Dec(L); Result := Copy(S, I, L - I + 1); end; end; function TrimLeft(const S: string): string; var I, L: Integer; begin L := Length(S); I := 1; while (I <= L) and (S[I] <= ' ') do Inc(I); Result := Copy(S, I, Maxint); end; function TrimLeft(const S: WideString): WideString; var I, L: Integer; begin L := Length(S); I := 1; while (I <= L) and (S[I] <= ' ') do Inc(I); Result := Copy(S, I, Maxint); end; function TrimRight(const S: string): string; var I: Integer; begin I := Length(S); while (I > 0) and (S[I] <= ' ') do Dec(I); Result := Copy(S, 1, I); end; function TrimRight(const S: WideString): WideString; var I: Integer; begin I := Length(S); while (I > 0) and (S[I] <= ' ') do Dec(I); Result := Copy(S, 1, I); end; function QuotedStr(const S: string): string; var I: Integer; begin Result := S; for I := Length(Result) downto 1 do if Result[I] = '''' then Insert('''', Result, I); Result := '''' + Result + ''''; end; function AnsiQuotedStr(const S: string; Quote: Char): string; var P, Src, Dest: PChar; AddCount: Integer; begin AddCount := 0; P := AnsiStrScan(PChar(S), Quote); while P <> nil do begin Inc(P); Inc(AddCount); P := AnsiStrScan(P, Quote); end; if AddCount = 0 then begin Result := Quote + S + Quote; Exit; end; SetLength(Result, Length(S) + AddCount + 2); Dest := Pointer(Result); Dest^ := Quote; Inc(Dest); Src := Pointer(S); P := AnsiStrScan(Src, Quote); repeat Inc(P); Move(Src^, Dest^, P - Src); Inc(Dest, P - Src); Dest^ := Quote; Inc(Dest); Src := P; P := AnsiStrScan(Src, Quote); until P = nil; P := StrEnd(Src); Move(Src^, Dest^, P - Src); Inc(Dest, P - Src); Dest^ := Quote; end; function AnsiExtractQuotedStr(var Src: PChar; Quote: Char): string; var P, Dest: PChar; DropCount: Integer; begin Result := ''; if (Src = nil) or (Src^ <> Quote) then Exit; Inc(Src); DropCount := 1; P := Src; Src := AnsiStrScan(Src, Quote); while Src <> nil do // count adjacent pairs of quote chars begin Inc(Src); if Src^ <> Quote then Break; Inc(Src); Inc(DropCount); Src := AnsiStrScan(Src, Quote); end; if Src = nil then Src := StrEnd(P); if ((Src - P) <= 1) then Exit; if DropCount = 1 then SetString(Result, P, Src - P - 1) else begin SetLength(Result, Src - P - DropCount); Dest := PChar(Result); Src := AnsiStrScan(P, Quote); while Src <> nil do begin Inc(Src); if Src^ <> Quote then Break; Move(P^, Dest^, Src - P); Inc(Dest, Src - P); Inc(Src); P := Src; Src := AnsiStrScan(Src, Quote); end; if Src = nil then Src := StrEnd(P); Move(P^, Dest^, Src - P - 1); end; end; function AnsiDequotedStr(const S: string; AQuote: Char): string; var LText: PChar; begin LText := PChar(S); Result := AnsiExtractQuotedStr(LText, AQuote); if Result = '' then Result := S; end; function AdjustLineBreaks(const S: string; Style: TTextLineBreakStyle): string; var Source, SourceEnd, Dest: PChar; DestLen: Integer; L: Integer; begin Source := Pointer(S); SourceEnd := Source + Length(S); DestLen := Length(S); while Source < SourceEnd do begin case Source^ of #10: if Style = tlbsCRLF then Inc(DestLen); #13: if Style = tlbsCRLF then if Source[1] = #10 then Inc(Source) else Inc(DestLen) else if Source[1] = #10 then Dec(DestLen); else if Source^ in LeadBytes then begin Source := StrNextChar(Source); continue; end; end; Inc(Source); end; if DestLen = Length(Source) then Result := S else begin Source := Pointer(S); SetString(Result, nil, DestLen); Dest := Pointer(Result); while Source < SourceEnd do case Source^ of #10: begin if Style = tlbsCRLF then begin Dest^ := #13; Inc(Dest); end; Dest^ := #10; Inc(Dest); Inc(Source); end; #13: begin if Style = tlbsCRLF then begin Dest^ := #13; Inc(Dest); end; Dest^ := #10; Inc(Dest); Inc(Source); if Source^ = #10 then Inc(Source); end; else if Source^ in LeadBytes then begin L := StrCharLength(Source); Move(Source^, Dest^, L); Inc(Dest, L); Inc(Source, L); continue; end; Dest^ := Source^; Inc(Dest); Inc(Source); end; end; end; function IsValidIdent(const Ident: string; AllowDots: Boolean): Boolean; const Alpha = ['A'..'Z', 'a'..'z', '_']; AlphaNumeric = Alpha + ['0'..'9']; AlphaNumericDot = AlphaNumeric + ['.']; var I: Integer; begin Result := False; if (Length(Ident) = 0) or not (Ident[1] in Alpha) then Exit; if AllowDots then for I := 2 to Length(Ident) do begin if not (Ident[I] in AlphaNumericDot) then Exit end else for I := 2 to Length(Ident) do if not (Ident[I] in AlphaNumeric) then Exit; Result := True; end; procedure CvtInt; { IN: EAX: The integer value to be converted to text ESI: Ptr to the right-hand side of the output buffer: LEA ESI, StrBuf[16] ECX: Base for conversion: 0 for signed decimal, 10 or 16 for unsigned EDX: Precision: zero padded minimum field width OUT: ESI: Ptr to start of converted text (not start of buffer) ECX: Length of converted text } asm OR CL,CL JNZ @CvtLoop @C1: OR EAX,EAX JNS @C2 NEG EAX CALL @C2 MOV AL,'-' INC ECX DEC ESI MOV [ESI],AL RET @C2: MOV ECX,10 @CvtLoop: PUSH EDX PUSH ESI @D1: XOR EDX,EDX DIV ECX DEC ESI ADD DL,'0' CMP DL,'0'+10 JB @D2 ADD DL,('A'-'0')-10 @D2: MOV [ESI],DL OR EAX,EAX JNE @D1 POP ECX POP EDX SUB ECX,ESI SUB EDX,ECX JBE @D5 ADD ECX,EDX MOV AL,'0' SUB ESI,EDX JMP @z @zloop: MOV [ESI+EDX],AL @z: DEC EDX JNZ @zloop MOV [ESI],AL @D5: end; procedure CvtIntW; { IN: EAX: The integer value to be converted to text ESI: Ptr to the right-hand side of the widechar output buffer: LEA ESI, WStrBuf[32] ECX: Base for conversion: 0 for signed decimal, 10 or 16 for unsigned EDX: Precision: zero padded minimum field width OUT: ESI: Ptr to start of converted widechar text (not start of buffer) ECX: Character length of converted text } asm OR CL,CL JNZ @CvtLoop @C1: OR EAX,EAX JNS @C2 NEG EAX CALL @C2 MOV AX,'-' MOV [ESI-2],AX SUB ESI, 2 INC ECX RET @C2: MOV ECX,10 @CvtLoop: PUSH EDX PUSH ESI @D1: XOR EDX,EDX DIV ECX ADD DX,'0' SUB ESI,2 CMP DX,'0'+10 JB @D2 ADD DX,('A'-'0')-10 @D2: MOV [ESI],DX OR EAX,EAX JNE @D1 POP ECX POP EDX SUB ECX,ESI SHR ECX, 1 SUB EDX,ECX JBE @D5 ADD ECX,EDX SUB ESI,EDX MOV AX,'0' SUB ESI,EDX JMP @z @zloop: MOV [ESI+EDX*2],AX @z: DEC EDX JNZ @zloop MOV [ESI],AX @D5: end; function IntToStr(Value: Integer): string; // FmtStr(Result, '%d', [Value]); asm PUSH ESI MOV ESI, ESP SUB ESP, 16 XOR ECX, ECX // base: 0 for signed decimal PUSH EDX // result ptr XOR EDX, EDX // zero filled field width: 0 for no leading zeros CALL CvtInt MOV EDX, ESI POP EAX // result ptr CALL System.@LStrFromPCharLen ADD ESP, 16 POP ESI end; procedure CvtInt64W; { IN: EAX: Address of the int64 value to be converted to text ESI: Ptr to the right-hand side of the widechar output buffer: LEA ESI, WStrBuf[32] ECX: Base for conversion: 10 or 16 EDX: Precision: zero padded minimum field width OUT: ESI: Ptr to start of converted widechar text (not start of buffer) ECX: Character length of converted text } asm OR CL, CL JNZ @start MOV ECX, 10 TEST [EAX + 4], $80000000 JZ @start PUSH [EAX + 4] PUSH [EAX] MOV EAX, ESP NEG [ESP] // negate the value ADC [ESP + 4],0 NEG [ESP + 4] CALL @start INC ECX MOV [ESI-2].Word, '-' SUB ESI, 2 ADD ESP, 8 JMP @done @start: PUSH ESI SUB ESP, 4 FNSTCW [ESP+2].Word // save FNSTCW [ESP].Word // scratch OR [ESP].Word, $0F00 // trunc toward zero, full precision FLDCW [ESP].Word MOV [ESP].Word, CX FLD1 TEST [EAX + 4], $80000000 // test for negative JZ @ld1 // FPU doesn't understand unsigned ints PUSH [EAX + 4] // copy value before modifying PUSH [EAX] AND [ESP + 4], $7FFFFFFF // clear the sign bit PUSH $7FFFFFFF PUSH $FFFFFFFF FILD [ESP + 8].QWord // load value FILD [ESP].QWord FADD ST(0), ST(2) // Add 1. Produces unsigned $80000000 in ST(0) FADDP ST(1), ST(0) // Add $80000000 to value to replace the sign bit ADD ESP, 16 JMP @ld2 @ld1: FILD [EAX].QWord // value @ld2: FILD [ESP].Word // base FLD ST(1) @loop: SUB ESI, 2 FPREM // accumulator mod base FISTP [ESI].Word FDIV ST(1), ST(0) // accumulator := acumulator / base MOV AX, [ESI].Word // overlap long division op with int ops ADD AX, '0' CMP AX, '0'+10 JB @store ADD AX, ('A'-'0')-10 @store: MOV [ESI].Word, AX FLD ST(1) // copy accumulator FCOM ST(3) // if accumulator >= 1.0 then loop FSTSW AX SAHF JAE @loop FLDCW [ESP+2].Word ADD ESP,4 FFREE ST(3) FFREE ST(2) FFREE ST(1); FFREE ST(0); @zeropad: POP ECX // original ESI SUB ECX,ESI SHR ECX, 1 // ECX = char length of converted string OR EDX,EDX JS @done SUB EDX,ECX JBE @done // output longer than field width = no pad SUB ESI,EDX MOV AX,'0' SUB ESI,EDX ADD ECX,EDX JMP @z @zloop: MOV [ESI+EDX*2].Word,AX @z: DEC EDX JNZ @zloop MOV [ESI].Word,AX @done: end; procedure CvtInt64; { IN: EAX: Address of the int64 value to be converted to text ESI: Ptr to the right-hand side of the output buffer: LEA ESI, StrBuf[32] ECX: Base for conversion: 0 for signed decimal, or 10 or 16 for unsigned EDX: Precision: zero padded minimum field width OUT: ESI: Ptr to start of converted text (not start of buffer) ECX: Byte length of converted text } asm OR CL, CL JNZ @start // CL = 0 => signed integer conversion MOV ECX, 10 TEST [EAX + 4], $80000000 JZ @start PUSH [EAX + 4] PUSH [EAX] MOV EAX, ESP NEG [ESP] // negate the value ADC [ESP + 4],0 NEG [ESP + 4] CALL @start // perform unsigned conversion MOV [ESI-1].Byte, '-' // tack on the negative sign DEC ESI INC ECX ADD ESP, 8 RET @start: // perform unsigned conversion PUSH ESI SUB ESP, 4 FNSTCW [ESP+2].Word // save FNSTCW [ESP].Word // scratch OR [ESP].Word, $0F00 // trunc toward zero, full precision FLDCW [ESP].Word MOV [ESP].Word, CX FLD1 TEST [EAX + 4], $80000000 // test for negative JZ @ld1 // FPU doesn't understand unsigned ints PUSH [EAX + 4] // copy value before modifying PUSH [EAX] AND [ESP + 4], $7FFFFFFF // clear the sign bit PUSH $7FFFFFFF PUSH $FFFFFFFF FILD [ESP + 8].QWord // load value FILD [ESP].QWord FADD ST(0), ST(2) // Add 1. Produces unsigned $80000000 in ST(0) FADDP ST(1), ST(0) // Add $80000000 to value to replace the sign bit ADD ESP, 16 JMP @ld2 @ld1: FILD [EAX].QWord // value @ld2: FILD [ESP].Word // base FLD ST(1) @loop: DEC ESI FPREM // accumulator mod base FISTP [ESP].Word FDIV ST(1), ST(0) // accumulator := acumulator / base MOV AL, [ESP].Byte // overlap long FPU division op with int ops ADD AL, '0' CMP AL, '0'+10 JB @store ADD AL, ('A'-'0')-10 @store: MOV [ESI].Byte, AL FLD ST(1) // copy accumulator FCOM ST(3) // if accumulator >= 1.0 then loop FSTSW AX SAHF JAE @loop FLDCW [ESP+2].Word ADD ESP,4 FFREE ST(3) FFREE ST(2) FFREE ST(1); FFREE ST(0); POP ECX // original ESI SUB ECX, ESI // ECX = length of converted string SUB EDX,ECX JBE @done // output longer than field width = no pad SUB ESI,EDX MOV AL,'0' ADD ECX,EDX JMP @z @zloop: MOV [ESI+EDX].Byte,AL @z: DEC EDX JNZ @zloop MOV [ESI].Byte,AL @done: end; function IntToStr(Value: Int64): string; // FmtStr(Result, '%d', [Value]); asm PUSH ESI MOV ESI, ESP SUB ESP, 32 // 32 chars XOR ECX, ECX // base 10 signed PUSH EAX // result ptr XOR EDX, EDX // zero filled field width: 0 for no leading zeros LEA EAX, Value; CALL CvtInt64 MOV EDX, ESI POP EAX // result ptr CALL System.@LStrFromPCharLen ADD ESP, 32 POP ESI end; function IntToHex(Value: Integer; Digits: Integer): string; // FmtStr(Result, '%.*x', [Digits, Value]); asm CMP EDX, 32 // Digits < buffer length? JBE @A1 XOR EDX, EDX @A1: PUSH ESI MOV ESI, ESP SUB ESP, 32 PUSH ECX // result ptr MOV ECX, 16 // base 16 EDX = Digits = field width CALL CvtInt MOV EDX, ESI POP EAX // result ptr CALL System.@LStrFromPCharLen ADD ESP, 32 POP ESI end; function IntToHex(Value: Int64; Digits: Integer): string; // FmtStr(Result, '%.*x', [Digits, Value]); asm CMP EAX, 32 // Digits < buffer length? JLE @A1 XOR EAX, EAX @A1: PUSH ESI MOV ESI, ESP SUB ESP, 32 // 32 chars MOV ECX, 16 // base 16 PUSH EDX // result ptr MOV EDX, EAX // zero filled field width: 0 for no leading zeros LEA EAX, Value; CALL CvtInt64 MOV EDX, ESI POP EAX // result ptr CALL System.@LStrFromPCharLen ADD ESP, 32 POP ESI end; function StrToInt(const S: string): Integer; var E: Integer; begin Val(S, Result, E); if E <> 0 then ConvertErrorFmt(SInvalidInteger, [S]); end; function StrToIntDef(const S: string; Default: Integer): Integer; var E: Integer; begin Val(S, Result, E); if E <> 0 then Result := Default; end; function TryStrToInt(const S: string; out Value: Integer): Boolean; var E: Integer; begin Val(S, Value, E); Result := E = 0; end; function StrToInt64(const S: string): Int64; var E: Integer; begin Val(S, Result, E); if E <> 0 then ConvertErrorFmt(SInvalidInteger, [S]); end; function StrToInt64Def(const S: string; const Default: Int64): Int64; var E: Integer; begin Val(S, Result, E); if E <> 0 then Result := Default; end; function TryStrToInt64(const S: string; out Value: Int64): Boolean; var E: Integer; begin Val(S, Value, E); Result := E = 0; end; procedure VerifyBoolStrArray; begin if Length(TrueBoolStrs) = 0 then begin SetLength(TrueBoolStrs, 1); TrueBoolStrs[0] := DefaultTrueBoolStr; end; if Length(FalseBoolStrs) = 0 then begin SetLength(FalseBoolStrs, 1); FalseBoolStrs[0] := DefaultFalseBoolStr; end; end; function StrToBool(const S: string): Boolean; begin if not TryStrToBool(S, Result) then ConvertErrorFmt(SInvalidBoolean, [S]); end; function StrToBoolDef(const S: string; const Default: Boolean): Boolean; begin if not TryStrToBool(S, Result) then Result := Default; end; function TryStrToBool(const S: string; out Value: Boolean): Boolean; function CompareWith(const aArray: array of string): Boolean; var I: Integer; begin Result := False; for I := Low(aArray) to High(aArray) do if AnsiSameText(S, aArray[I]) then begin Result := True; Break; end; end; var LResult: Extended; begin Result := TryStrToFloat(S, LResult); if Result then Value := LResult <> 0 else begin VerifyBoolStrArray; Result := CompareWith(TrueBoolStrs); if Result then Value := True else begin Result := CompareWith(FalseBoolStrs); if Result then Value := False; end; end; end; function BoolToStr(B: Boolean; UseBoolStrs: Boolean = False): string; const cSimpleBoolStrs: array [boolean] of String = ('0', '-1'); begin if UseBoolStrs then begin VerifyBoolStrArray; if B then Result := TrueBoolStrs[0] else Result := FalseBoolStrs[0]; end else Result := cSimpleBoolStrs[B]; end; type PStrData = ^TStrData; TStrData = record Ident: Integer; Str: string; end; function EnumStringModules(Instance: Longint; Data: Pointer): Boolean; {$IFDEF MSWINDOWS} var Buffer: array [0..1023] of char; begin with PStrData(Data)^ do begin SetString(Str, Buffer, LoadString(Instance, Ident, Buffer, sizeof(Buffer))); Result := Str = ''; end; end; {$ENDIF} {$IFDEF LINUX} var rs: TResStringRec; Module: HModule; begin Module := Instance; rs.Module := @Module; with PStrData(Data)^ do begin rs.Identifier := Ident; Str := LoadResString(@rs); Result := Str = ''; end; end; {$ENDIF} function FindStringResource(Ident: Integer): string; var StrData: TStrData; begin StrData.Ident := Ident; StrData.Str := ''; EnumResourceModules(EnumStringModules, @StrData); Result := StrData.Str; end; function LoadStr(Ident: Integer): string; begin Result := FindStringResource(Ident); end; function FmtLoadStr(Ident: Integer; const Args: array of const): string; begin FmtStr(Result, FindStringResource(Ident), Args); end; { File management routines } function FileOpen(const FileName: string; Mode: LongWord): Integer; {$IFDEF MSWINDOWS} const AccessMode: array[0..2] of LongWord = ( GENERIC_READ, GENERIC_WRITE, GENERIC_READ or GENERIC_WRITE); ShareMode: array[0..4] of LongWord = ( 0, 0, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE); begin Result := -1; if ((Mode and 3) <= fmOpenReadWrite) and ((Mode and $F0) <= fmShareDenyNone) then Result := Integer(CreateFile(PChar(FileName), AccessMode[Mode and 3], ShareMode[(Mode and $F0) shr 4], nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); end; {$ENDIF} {$IFDEF LINUX} const ShareMode: array[0..fmShareDenyNone shr 4] of Byte = ( 0, //No share mode specified F_WRLCK, //fmShareExclusive F_RDLCK, //fmShareDenyWrite 0); //fmShareDenyNone var FileHandle, Tvar: Integer; LockVar: TFlock; smode: Byte; begin Result := -1; if FileExists(FileName) and ((Mode and 3) <= fmOpenReadWrite) and ((Mode and $F0) <= fmShareDenyNone) then begin FileHandle := open(PChar(FileName), (Mode and 3), FileAccessRights); if FileHandle = -1 then Exit; smode := Mode and $F0 shr 4; if ShareMode[smode] <> 0 then begin with LockVar do begin l_whence := SEEK_SET; l_start := 0; l_len := 0; l_type := ShareMode[smode]; end; Tvar := fcntl(FileHandle, F_SETLK, LockVar); if Tvar = -1 then begin __close(FileHandle); Exit; end; end; Result := FileHandle; end; end; {$ENDIF} function FileCreate(const FileName: string): Integer; {$IFDEF MSWINDOWS} begin Result := Integer(CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)); end; {$ENDIF} {$IFDEF LINUX} begin Result := FileCreate(FileName, FileAccessRights); end; {$ENDIF} function FileCreate(const FileName: string; Rights: Integer): Integer; {$IFDEF MSWINDOWS} begin Result := FileCreate(FileName); end; {$ENDIF} {$IFDEF LINUX} begin Result := Integer(open(PChar(FileName), O_RDWR or O_CREAT or O_TRUNC, Rights)); end; {$ENDIF} function FileRead(Handle: Integer; var Buffer; Count: LongWord): Integer; begin {$IFDEF MSWINDOWS} if not ReadFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then Result := -1; {$ENDIF} {$IFDEF LINUX} Result := __read(Handle, Buffer, Count); {$ENDIF} end; function FileWrite(Handle: Integer; const Buffer; Count: LongWord): Integer; begin {$IFDEF MSWINDOWS} if not WriteFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then Result := -1; {$ENDIF} {$IFDEF LINUX} Result := __write(Handle, Buffer, Count); {$ENDIF} end; function FileSeek(Handle, Offset, Origin: Integer): Integer; begin {$IFDEF MSWINDOWS} Result := SetFilePointer(THandle(Handle), Offset, nil, Origin); {$ENDIF} {$IFDEF LINUX} Result := __lseek(Handle, Offset, Origin); {$ENDIF} end; function FileSeek(Handle: Integer; const Offset: Int64; Origin: Integer): Int64; {$IFDEF MSWINDOWS} begin Result := Offset; Int64Rec(Result).Lo := SetFilePointer(THandle(Handle), Int64Rec(Result).Lo, @Int64Rec(Result).Hi, Origin); end; {$ENDIF} {$IFDEF LINUX} var Temp: Integer; begin Temp := Offset; // allow for range-checking Result := FileSeek(Handle, Temp, Origin); end; {$ENDIF} procedure FileClose(Handle: Integer); begin {$IFDEF MSWINDOWS} CloseHandle(THandle(Handle)); {$ENDIF} {$IFDEF LINUX} __close(Handle); // No need to unlock since all locks are released on close. {$ENDIF} end; function FileAge(const FileName: string): Integer; {$IFDEF MSWINDOWS} var Handle: THandle; FindData: TWin32FindData; LocalFileTime: TFileTime; begin Handle := FindFirstFile(PChar(FileName), FindData); if Handle <> INVALID_HANDLE_VALUE then begin Windows.FindClose(Handle); if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then begin FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime); if FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi, LongRec(Result).Lo) then Exit; end; end; Result := -1; end; {$ENDIF} {$IFDEF LINUX} var st: TStatBuf; begin if stat(PChar(FileName), st) = 0 then Result := st.st_mtime else Result := -1; end; {$ENDIF} function FileExists(const FileName: string): Boolean; {$IFDEF MSWINDOWS} begin Result := FileAge(FileName) <> -1; end; {$ENDIF} {$IFDEF LINUX} begin Result := euidaccess(PChar(FileName), F_OK) = 0; end; {$ENDIF} function DirectoryExists(const Directory: string): Boolean; {$IFDEF LINUX} var st: TStatBuf; begin if stat(PChar(Directory), st) = 0 then Result := S_ISDIR(st.st_mode) else Result := False; end; {$ENDIF} {$IFDEF MSWINDOWS} var Code: Integer; begin Code := GetFileAttributes(PChar(Directory)); Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0); end; {$ENDIF} function ForceDirectories(Dir: string): Boolean; var E: EInOutError; begin Result := True; if Dir = '' then begin E := EInOutError.CreateRes(SCannotCreateDir); E.ErrorCode := 3; raise E; end; Dir := ExcludeTrailingPathDelimiter(Dir); {$IFDEF MSWINDOWS} if (Length(Dir) < 3) or DirectoryExists(Dir) or (ExtractFilePath(Dir) = Dir) then Exit; // avoid 'xyz:\' problem. {$ENDIF} {$IFDEF LINUX} if (Dir = '') or DirectoryExists(Dir) then Exit; {$ENDIF} Result := ForceDirectories(ExtractFilePath(Dir)) and CreateDir(Dir); end; function FileGetDate(Handle: Integer): Integer; {$IFDEF MSWINDOWS} var FileTime, LocalFileTime: TFileTime; begin if GetFileTime(THandle(Handle), nil, nil, @FileTime) and FileTimeToLocalFileTime(FileTime, LocalFileTime) and FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi, LongRec(Result).Lo) then Exit; Result := -1; end; {$ENDIF} {$IFDEF LINUX} var st: TStatBuf; begin if fstat(Handle, st) = 0 then Result := st.st_mtime else Result := -1; end; {$ENDIF} function FileSetDate(const FileName: string; Age: Integer): Integer; {$IFDEF MSWINDOWS} var f: THandle; begin f := FileOpen(FileName, fmOpenWrite); if f = THandle(-1) then Result := GetLastError else begin Result := FileSetDate(f, Age); FileClose(f); end; end; {$ENDIF} {$IFDEF LINUX} var ut: TUTimeBuffer; begin Result := 0; ut.actime := Age; ut.modtime := Age; if utime(PChar(FileName), @ut) = -1 then Result := GetLastError; end; {$ENDIF} {$IFDEF MSWINDOWS} function FileSetDate(Handle: Integer; Age: Integer): Integer; var LocalFileTime, FileTime: TFileTime; begin Result := 0; if DosDateTimeToFileTime(LongRec(Age).Hi, LongRec(Age).Lo, LocalFileTime) and LocalFileTimeToFileTime(LocalFileTime, FileTime) and SetFileTime(Handle, nil, nil, @FileTime) then Exit; Result := GetLastError; end; function FileGetAttr(const FileName: string): Integer; begin Result := GetFileAttributes(PChar(FileName)); end; function FileSetAttr(const FileName: string; Attr: Integer): Integer; begin Result := 0; if not SetFileAttributes(PChar(FileName), Attr) then Result := GetLastError; end; {$ENDIF} function FileIsReadOnly(const FileName: string): Boolean; begin {$IFDEF MSWINDOWS} Result := (GetFileAttributes(PChar(FileName)) and faReadOnly) <> 0; {$ENDIF} {$IFDEF LINUX} Result := (euidaccess(PChar(FileName), R_OK) = 0) and (euidaccess(PChar(FileName), W_OK) <> 0); {$ENDIF} end; function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean; {$IFDEF MSWINDOWS} var Flags: Integer; begin Result := False; Flags := GetFileAttributes(PChar(FileName)); if Flags = -1 then Exit; if ReadOnly then Flags := Flags or faReadOnly else Flags := Flags and not faReadOnly; Result := SetFileAttributes(PChar(FileName), Flags); end; {$ENDIF} {$IFDEF LINUX} var st: TStatBuf; Flags: Integer; begin Result := False; if stat(PChar(FileName), st) <> 0 then Exit; if ReadOnly then Flags := st.st_mode and not (S_IWUSR or S_IWGRP or S_IWOTH) else Flags := st.st_mode or (S_IWUSR or S_IWGRP or S_IWOTH); Result := chmod(PChar(FileName), Flags) = 0; end; {$ENDIF} function FindMatchingFile(var F: TSearchRec): Integer; {$IFDEF MSWINDOWS} var LocalFileTime: TFileTime; begin with F do begin while FindData.dwFileAttributes and ExcludeAttr <> 0 do if not FindNextFile(FindHandle, FindData) then begin Result := GetLastError; Exit; end; FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime); FileTimeToDosDateTime(LocalFileTime, LongRec(Time).Hi, LongRec(Time).Lo); Size := FindData.nFileSizeLow; Attr := FindData.dwFileAttributes; Name := FindData.cFileName; end; Result := 0; end; {$ENDIF} {$IFDEF LINUX} var PtrDirEnt: PDirEnt; Scratch: TDirEnt; StatBuf: TStatBuf; LinkStatBuf: TStatBuf; FName: string; Attr: Integer; Mode: mode_t; begin Result := -1; PtrDirEnt := nil; if readdir_r(F.FindHandle, @Scratch, PtrDirEnt) <> 0 then Exit; while PtrDirEnt <> nil do begin if fnmatch(PChar(F.Pattern), PtrDirEnt.d_name, 0) = 0 then begin // F.PathOnly must include trailing backslash FName := F.PathOnly + string(PtrDirEnt.d_name); if lstat(PChar(FName), StatBuf) = 0 then begin Attr := 0; Mode := StatBuf.st_mode; if S_ISDIR(Mode) then Attr := Attr or faDirectory else if not S_ISREG(Mode) then // directories shouldn't be treated as system files begin if S_ISLNK(Mode) then begin Attr := Attr or faSymLink; if (stat(PChar(FName), LinkStatBuf) = 0) and S_ISDIR(LinkStatBuf.st_mode) then Attr := Attr or faDirectory end; Attr := Attr or faSysFile; end; if (PtrDirEnt.d_name[0] = '.') and (PtrDirEnt.d_name[1] <> #0) then begin if not ((PtrDirEnt.d_name[1] = '.') and (PtrDirEnt.d_name[2] = #0)) then Attr := Attr or faHidden; end; if euidaccess(PChar(FName), W_OK) <> 0 then Attr := Attr or faReadOnly; if Attr and F.ExcludeAttr = 0 then begin F.Size := StatBuf.st_size; F.Attr := Attr; F.Mode := StatBuf.st_mode; F.Name := PtrDirEnt.d_name; F.Time := StatBuf.st_mtime; Result := 0; Break; end; end; end; Result := -1; if readdir_r(F.FindHandle, @Scratch, PtrDirEnt) <> 0 then Break; end // End of While end; {$ENDIF} function FindFirst(const Path: string; Attr: Integer; var F: TSearchRec): Integer; const faSpecial = faHidden or faSysFile or faDirectory; {$IFDEF MSWINDOWS} begin F.ExcludeAttr := not Attr and faSpecial; F.FindHandle := FindFirstFile(PChar(Path), F.FindData); if F.FindHandle <> INVALID_HANDLE_VALUE then begin Result := FindMatchingFile(F); if Result <> 0 then FindClose(F); end else Result := GetLastError; end; {$ENDIF} {$IFDEF LINUX} begin F.ExcludeAttr := not Attr and faSpecial; F.PathOnly := ExtractFilePath(Path); F.Pattern := ExtractFileName(Path); if F.PathOnly = '' then F.PathOnly := IncludeTrailingPathDelimiter(GetCurrentDir); F.FindHandle := opendir(PChar(F.PathOnly)); if F.FindHandle <> nil then begin Result := FindMatchingFile(F); if Result <> 0 then FindClose(F); end else Result:= GetLastError; end; {$ENDIF} function FindNext(var F: TSearchRec): Integer; begin {$IFDEF MSWINDOWS} if FindNextFile(F.FindHandle, F.FindData) then Result := FindMatchingFile(F) else Result := GetLastError; {$ENDIF} {$IFDEF LINUX} Result := FindMatchingFile(F); {$ENDIF} end; procedure FindClose(var F: TSearchRec); begin {$IFDEF MSWINDOWS} if F.FindHandle <> INVALID_HANDLE_VALUE then begin Windows.FindClose(F.FindHandle); F.FindHandle := INVALID_HANDLE_VALUE; end; {$ENDIF} {$IFDEF LINUX} if F.FindHandle <> nil then begin closedir(F.FindHandle); F.FindHandle := nil; end; {$ENDIF} end; function DeleteFile(const FileName: string): Boolean; begin {$IFDEF MSWINDOWS} Result := Windows.DeleteFile(PChar(FileName)); {$ENDIF} {$IFDEF LINUX} Result := unlink(PChar(FileName)) <> -1; {$ENDIF} end; function RenameFile(const OldName, NewName: string): Boolean; begin {$IFDEF MSWINDOWS} Result := MoveFile(PChar(OldName), PChar(NewName)); {$ENDIF} {$IFDEF LINUX} Result := __rename(PChar(OldName), PChar(NewName)) = 0; {$ENDIF} end; function AnsiStrLastChar(P: PChar): PChar; var LastByte: Integer; begin LastByte := StrLen(P) - 1; Result := @P[LastByte]; {$IFDEF MSWINDOWS} if StrByteType(P, LastByte) = mbTrailByte then Dec(Result); {$ENDIF} {$IFDEF LINUX} while StrByteType(P, Result - P) = mbTrailByte do Dec(Result); {$ENDIF} end; function AnsiLastChar(const S: string): PChar; var LastByte: Integer; begin LastByte := Length(S); if LastByte <> 0 then begin while ByteType(S, LastByte) = mbTrailByte do Dec(LastByte); Result := @S[LastByte]; end else Result := nil; end; function LastDelimiter(const Delimiters, S: string): Integer; var P: PChar; begin Result := Length(S); P := PChar(Delimiters); while Result > 0 do begin if (S[Result] <> #0) and (StrScan(P, S[Result]) <> nil) then {$IFDEF MSWINDOWS} if (ByteType(S, Result) = mbTrailByte) then Dec(Result) else Exit; {$ENDIF} {$IFDEF LINUX} begin if (ByteType(S, Result) <> mbTrailByte) then Exit; Dec(Result); while ByteType(S, Result) = mbTrailByte do Dec(Result); end; {$ENDIF} Dec(Result); end; end; function ChangeFileExt(const FileName, Extension: string): string; var I: Integer; begin I := LastDelimiter('.' + PathDelim + DriveDelim,Filename); if (I = 0) or (FileName[I] <> '.') then I := MaxInt; Result := Copy(FileName, 1, I - 1) + Extension; end; function ExtractFilePath(const FileName: string): string; var I: Integer; begin I := LastDelimiter(PathDelim + DriveDelim, FileName); Result := Copy(FileName, 1, I); end; function ExtractFileDir(const FileName: string): string; var I: Integer; begin I := LastDelimiter(PathDelim + DriveDelim, Filename); if (I > 1) and (FileName[I] = PathDelim) and (not IsDelimiter( PathDelim + DriveDelim, FileName, I-1)) then Dec(I); Result := Copy(FileName, 1, I); end; function ExtractFileDrive(const FileName: string): string; {$IFDEF MSWINDOWS} var I, J: Integer; begin if (Length(FileName) >= 2) and (FileName[2] = DriveDelim) then Result := Copy(FileName, 1, 2) else if (Length(FileName) >= 2) and (FileName[1] = PathDelim) and (FileName[2] = PathDelim) then begin J := 0; I := 3; While (I < Length(FileName)) and (J < 2) do begin if FileName[I] = PathDelim then Inc(J); if J < 2 then Inc(I); end; if FileName[I] = PathDelim then Dec(I); Result := Copy(FileName, 1, I); end else Result := ''; end; {$ENDIF} {$IFDEF LINUX} begin Result := ''; // Linux doesn't support drive letters end; {$ENDIF} function ExtractFileName(const FileName: string): string; var I: Integer; begin I := LastDelimiter(PathDelim + DriveDelim, FileName); Result := Copy(FileName, I + 1, MaxInt); end; function ExtractFileExt(const FileName: string): string; var I: Integer; begin I := LastDelimiter('.' + PathDelim + DriveDelim, FileName); if (I > 0) and (FileName[I] = '.') then Result := Copy(FileName, I, MaxInt) else Result := ''; end; function ExpandFileName(const FileName: string): string; {$IFDEF MSWINDOWS} var FName: PChar; Buffer: array[0..MAX_PATH - 1] of Char; begin SetString(Result, Buffer, GetFullPathName(PChar(FileName), SizeOf(Buffer), Buffer, FName)); end; {$ENDIF} {$IFDEF LINUX} function ExpandTilde(const InString: string): string; var W: wordexp_t; SpacePos: Integer; PostSpaceStr: string; begin Result := InString; SpacePos := AnsiPos(' ', Result); // only expand stuff up to the first space in the filename if SpacePos > 0 then // then just add the space and the rest of the string PostSpaceStr := Copy(Result, SpacePos, Length(Result) - (SpacePos-1)); case wordexp(PChar(Result), W, WRDE_NOCMD) of 0: // success begin Result := PChar(W.we_wordv^); wordfree(W); end; WRDE_NOSPACE: // error, but W may be partially allocated wordfree(W); end; if PostSpaceStr <> '' then Result := Result + PostSpaceStr; end; var I, J: Integer; LastWasPathDelim: Boolean; TempName: string; begin Result := ''; if Length(Filename) = 0 then Exit; if FileName[1] = PathDelim then TempName := FileName else begin TempName := FileName; if FileName[1] = '~' then TempName := ExpandTilde(TempName) else TempName := IncludeTrailingPathDelimiter(GetCurrentDir) + TempName; end; I := 1; J := 1; LastWasPathDelim := False; while I <= Length(TempName) do begin case TempName[I] of PathDelim: if J < I then begin // Check for consecutive 'PathDelim' characters and skip them if present if (I = 1) or (TempName[I - 1] <> PathDelim) then Result := Result + Copy(TempName, J, I - J); J := I; // Set a flag indicating that we just processed a path delimiter LastWasPathDelim := True; end; '.': begin // If the last character was a path delimiter then this '.' is // possibly a relative path modifier if LastWasPathDelim then begin // Check if the path ends in a '.' if I < Length(TempName) then begin // If the next character is another '.' then this may be a relative path // except if there is another '.' after that one. In this case simply // treat this as just another filename. if (TempName[I + 1] = '.') and ((I + 1 >= Length(TempName)) or (TempName[I + 2] <> '.')) then begin // Don't attempt to backup past the Root dir if Length(Result) > 1 then // For the purpose of this excercise, treat the last dir as a // filename so we can use this function to remove it Result := ExtractFilePath(ExcludeTrailingPathDelimiter(Result)); J := I; end // Simply skip over and ignore any 'current dir' constrcucts, './' // or the remaining './' from a ../ constrcut. else if TempName[I + 1] = PathDelim then begin Result := IncludeTrailingPathDelimiter(Result); if TempName[I] in LeadBytes then Inc(I, StrCharLength(@TempName[I])) else Inc(I); J := I + 1; end else // If any of the above tests fail, then this is not a 'current dir' or // 'parent dir' construct so just clear the state and continue. LastWasPathDelim := False; end else begin // Don't let the expanded path end in a 'PathDelim' character Result := ExcludeTrailingPathDelimiter(Result); J := I + 1; end; end; end; else LastWasPathDelim := False; end; if TempName[I] in LeadBytes then Inc(I, StrCharLength(@TempName[I])) else Inc(I); end; // This will finally append what is left if (I - J > 1) or (TempName[I] <> PathDelim) then Result := Result + Copy(TempName, J, I - J); end; {$ENDIF} function ExpandFileNameCase(const FileName: string; out MatchFound: TFilenameCaseMatch): string; var SR: TSearchRec; FullPath, Name: string; Temp: Integer; FoundOne: Boolean; {$IFDEF LINUX} Scans: Byte; FirstLetter, TestLetter: string; {$ENDIF} begin Result := ExpandFileName(FileName); FullPath := ExtractFilePath(Result); Name := ExtractFileName(Result); MatchFound := mkNone; // if FullPath is not the root directory (portable) if not SameFileName(FullPath, IncludeTrailingPathDelimiter(ExtractFileDrive(FullPath))) then begin // Does the path need case-sensitive work? Temp := FindFirst(FullPath, faAnyFile, SR); FindClose(SR); // close search before going recursive if Temp <> 0 then begin FullPath := ExcludeTrailingPathDelimiter(FullPath); FullPath := ExpandFileNameCase(FullPath, MatchFound); if MatchFound = mkNone then Exit; // if we can't find the path, we certainly can't find the file! FullPath := IncludeTrailingPathDelimiter(FullPath); end; end; // Path is validated / adjusted. Now for the file itself try if FindFirst(FullPath + Name, faAnyFile, SR)= 0 then // exact match on filename begin if not (MatchFound in [mkSingleMatch, mkAmbiguous]) then // path might have been inexact MatchFound := mkExactMatch; Result := FullPath + SR.Name; Exit; end; finally FindClose(SR); end; FoundOne := False; // Windows should never get to here except for file-not-found {$IFDEF LINUX} { Scan the directory. To minimize the number of filenames tested, scan the directory using upper/lowercase first letter + wildcard. This results in two scans of the directory (particularly on Linux) but vastly reduces the number of times we have to perform an expensive locale-charset case-insensitive string compare. } // First, scan for lowercase first letter FirstLetter := AnsiLowerCase(Name[1]); for Scans := 0 to 1 do begin Temp := FindFirst(FullPath + FirstLetter + '*', faAnyFile, SR); while Temp = 0 do begin if AnsiSameText(SR.Name, Name) then begin if FoundOne then begin // this is the second match MatchFound := mkAmbiguous; Exit; end else begin FoundOne := True; Result := FullPath + SR.Name; end; end; Temp := FindNext(SR); end; FindClose(SR); TestLetter := AnsiUpperCase(Name[1]); if TestLetter = FirstLetter then Break; FirstLetter := TestLetter; end; {$ENDIF} if MatchFound <> mkAmbiguous then begin if FoundOne then MatchFound := mkSingleMatch else MatchFound := mkNone; end; end; {$IFDEF MSWINDOWS} function GetUniversalName(const FileName: string): string; type PNetResourceArray = ^TNetResourceArray; TNetResourceArray = array[0..MaxInt div SizeOf(TNetResource) - 1] of TNetResource; var I, BufSize, NetResult: Integer; Count, Size: LongWord; Drive: Char; NetHandle: THandle; NetResources: PNetResourceArray; RemoteNameInfo: array[0..1023] of Byte; begin Result := FileName; if (Win32Platform <> VER_PLATFORM_WIN32_WINDOWS) or (Win32MajorVersion > 4) then begin Size := SizeOf(RemoteNameInfo); if WNetGetUniversalName(PChar(FileName), UNIVERSAL_NAME_INFO_LEVEL, @RemoteNameInfo, Size) <> NO_ERROR then Exit; Result := PRemoteNameInfo(@RemoteNameInfo).lpUniversalName; end else begin { The following works around a bug in WNetGetUniversalName under Windows 95 } Drive := UpCase(FileName[1]); if (Drive < 'A') or (Drive > 'Z') or (Length(FileName) < 3) or (FileName[2] <> ':') or (FileName[3] <> '\') then Exit; if WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_DISK, 0, nil, NetHandle) <> NO_ERROR then Exit; try BufSize := 50 * SizeOf(TNetResource); GetMem(NetResources, BufSize); try while True do begin Count := $FFFFFFFF; Size := BufSize; NetResult := WNetEnumResource(NetHandle, Count, NetResources, Size); if NetResult = ERROR_MORE_DATA then begin BufSize := Size; ReallocMem(NetResources, BufSize); Continue; end; if NetResult <> NO_ERROR then Exit; for I := 0 to Count - 1 do with NetResources^[I] do if (lpLocalName <> nil) and (Drive = UpCase(lpLocalName[0])) then begin Result := lpRemoteName + Copy(FileName, 3, Length(FileName) - 2); Exit; end; end; finally FreeMem(NetResources, BufSize); end; finally WNetCloseEnum(NetHandle); end; end; end; function ExpandUNCFileName(const FileName: string): string; begin { First get the local resource version of the file name } Result := ExpandFileName(FileName); if (Length(Result) >= 3) and (Result[2] = ':') and (Upcase(Result[1]) >= 'A') and (Upcase(Result[1]) <= 'Z') then Result := GetUniversalName(Result); end; {$ENDIF} {$IFDEF LINUX} function ExpandUNCFileName(const FileName: string): string; begin Result := ExpandFileName(FileName); end; {$ENDIF} function ExtractRelativePath(const BaseName, DestName: string): string; var BasePath, DestPath: string; BaseLead, DestLead: PChar; BasePtr, DestPtr: PChar; function ExtractFilePathNoDrive(const FileName: string): string; begin Result := ExtractFilePath(FileName); Delete(Result, 1, Length(ExtractFileDrive(FileName))); end; function Next(var Lead: PChar): PChar; begin Result := Lead; if Result = nil then Exit; Lead := AnsiStrScan(Lead, PathDelim); if Lead <> nil then begin Lead^ := #0; Inc(Lead); end; end; begin if SameFilename(ExtractFileDrive(BaseName), ExtractFileDrive(DestName)) then begin BasePath := ExtractFilePathNoDrive(BaseName); UniqueString(BasePath); DestPath := ExtractFilePathNoDrive(DestName); UniqueString(DestPath); BaseLead := Pointer(BasePath); BasePtr := Next(BaseLead); DestLead := Pointer(DestPath); DestPtr := Next(DestLead); while (BasePtr <> nil) and (DestPtr <> nil) and SameFilename(BasePtr, DestPtr) do begin BasePtr := Next(BaseLead); DestPtr := Next(DestLead); end; Result := ''; while BaseLead <> nil do begin Result := Result + '..' + PathDelim; { Do not localize } Next(BaseLead); end; if (DestPtr <> nil) and (DestPtr^ <> #0) then Result := Result + DestPtr + PathDelim; if DestLead <> nil then Result := Result + DestLead; // destlead already has a trailing backslash Result := Result + ExtractFileName(DestName); end else Result := DestName; end; {$IFDEF MSWINDOWS} function ExtractShortPathName(const FileName: string): string; var Buffer: array[0..MAX_PATH - 1] of Char; begin SetString(Result, Buffer, GetShortPathName(PChar(FileName), Buffer, SizeOf(Buffer))); end; {$ENDIF} function FileSearch(const Name, DirList: string): string; var I, P, L: Integer; C: Char; begin Result := Name; P := 1; L := Length(DirList); while True do begin if FileExists(Result) then Exit; while (P <= L) and (DirList[P] = PathSep) do Inc(P); if P > L then Break; I := P; while (P <= L) and (DirList[P] <> PathSep) do begin if DirList[P] in LeadBytes then P := NextCharIndex(DirList, P) else Inc(P); end; Result := Copy(DirList, I, P - I); C := AnsiLastChar(Result)^; if (C <> DriveDelim) and (C <> PathDelim) then Result := Result + PathDelim; Result := Result + Name; end; Result := ''; end; {$IFDEF MSWINDOWS} // This function is used if the OS doesn't support GetDiskFreeSpaceEx function BackfillGetDiskFreeSpaceEx(Directory: PChar; var FreeAvailable, TotalSpace: TLargeInteger; TotalFree: PLargeInteger): Bool; stdcall; var SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters: LongWord; Temp: Int64; Dir: PChar; begin if Directory <> nil then Dir := Directory else Dir := nil; Result := GetDiskFreeSpaceA(Dir, SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters); Temp := SectorsPerCluster * BytesPerSector; FreeAvailable := Temp * FreeClusters; TotalSpace := Temp * TotalClusters; end; function InternalGetDiskSpace(Drive: Byte; var TotalSpace, FreeSpaceAvailable: Int64): Bool; var RootPath: array[0..4] of Char; RootPtr: PChar; begin RootPtr := nil; if Drive > 0 then begin RootPath[0] := Char(Drive + $40); RootPath[1] := ':'; RootPath[2] := '\'; RootPath[3] := #0; RootPtr := RootPath; end; Result := GetDiskFreeSpaceEx(RootPtr, FreeSpaceAvailable, TotalSpace, nil); end; function DiskFree(Drive: Byte): Int64; var TotalSpace: Int64; begin if not InternalGetDiskSpace(Drive, TotalSpace, Result) then Result := -1; end; function DiskSize(Drive: Byte): Int64; var FreeSpace: Int64; begin if not InternalGetDiskSpace(Drive, Result, FreeSpace) then Result := -1; end; {$ENDIF} function FileDateToDateTime(FileDate: Integer): TDateTime; {$IFDEF MSWINDOWS} begin Result := EncodeDate( LongRec(FileDate).Hi shr 9 + 1980, LongRec(FileDate).Hi shr 5 and 15, LongRec(FileDate).Hi and 31) + EncodeTime( LongRec(FileDate).Lo shr 11, LongRec(FileDate).Lo shr 5 and 63, LongRec(FileDate).Lo and 31 shl 1, 0); end; {$ENDIF} {$IFDEF LINUX} var UT: TUnixTime; begin localtime_r(@FileDate, UT); Result := EncodeDate(UT.tm_year + 1900, UT.tm_mon + 1, UT.tm_mday) + EncodeTime(UT.tm_hour, UT.tm_min, UT.tm_sec, 0); end; {$ENDIF} function DateTimeToFileDate(DateTime: TDateTime): Integer; {$IFDEF MSWINDOWS} var Year, Month, Day, Hour, Min, Sec, MSec: Word; begin DecodeDate(DateTime, Year, Month, Day); if (Year < 1980) or (Year > 2107) then Result := 0 else begin DecodeTime(DateTime, Hour, Min, Sec, MSec); LongRec(Result).Lo := (Sec shr 1) or (Min shl 5) or (Hour shl 11); LongRec(Result).Hi := Day or (Month shl 5) or ((Year - 1980) shl 9); end; end; {$ENDIF} {$IFDEF LINUX} var tm: TUnixTime; Year, Month, Day, Hour, Min, Sec, MSec: Word; begin DecodeDate(DateTime, Year, Month, Day); { Valid range for 32 bit Unix time_t: 1970 through 2038 } if (Year < 1970) or (Year > 2038) then Result := 0 else begin DecodeTime(DateTime, Hour, Min, Sec, MSec); FillChar(tm, sizeof(tm), 0); with tm do begin tm_sec := Sec; tm_min := Min; tm_hour := Hour; tm_mday := Day; tm_mon := Month - 1; tm_year := Year - 1900; tm_isdst := -1; end; Result := mktime(tm); end; end; {$ENDIF} function GetCurrentDir: string; begin GetDir(0, Result); end; function SetCurrentDir(const Dir: string): Boolean; begin {$IFDEF MSWINDOWS} Result := SetCurrentDirectory(PChar(Dir)); {$ENDIF} {$IFDEF LINUX} Result := __chdir(PChar(Dir)) = 0; {$ENDIF} end; function CreateDir(const Dir: string): Boolean; begin {$IFDEF MSWINDOWS} Result := CreateDirectory(PChar(Dir), nil); {$ENDIF} {$IFDEF LINUX} Result := __mkdir(PChar(Dir), mode_t(-1)) = 0; {$ENDIF} end; function RemoveDir(const Dir: string): Boolean; begin {$IFDEF MSWINDOWS} Result := RemoveDirectory(PChar(Dir)); {$ENDIF} {$IFDEF LINUX} Result := __rmdir(PChar(Dir)) = 0; {$ENDIF} end; { PChar routines } function StrLen(const Str: PChar): Cardinal; assembler; asm MOV EDX,EDI MOV EDI,EAX MOV ECX,0FFFFFFFFH XOR AL,AL REPNE SCASB MOV EAX,0FFFFFFFEH SUB EAX,ECX MOV EDI,EDX end; function StrEnd(const Str: PChar): PChar; assembler; asm MOV EDX,EDI MOV EDI,EAX MOV ECX,0FFFFFFFFH XOR AL,AL REPNE SCASB LEA EAX,[EDI-1] MOV EDI,EDX end; function StrMove(Dest: PChar; const Source: PChar; Count: Cardinal): PChar; begin Result := Dest; Move(Source^, Dest^, Count); end; function StrCopy(Dest: PChar; const Source: PChar): PChar; asm PUSH EDI PUSH ESI MOV ESI,EAX MOV EDI,EDX MOV ECX,0FFFFFFFFH XOR AL,AL REPNE SCASB NOT ECX MOV EDI,ESI MOV ESI,EDX MOV EDX,ECX MOV EAX,EDI SHR ECX,2 REP MOVSD MOV ECX,EDX AND ECX,3 REP MOVSB POP ESI POP EDI end; function StrECopy(Dest: PChar; const Source: PChar): PChar; assembler; asm PUSH EDI PUSH ESI MOV ESI,EAX MOV EDI,EDX MOV ECX,0FFFFFFFFH XOR AL,AL REPNE SCASB NOT ECX MOV EDI,ESI MOV ESI,EDX MOV EDX,ECX SHR ECX,2 REP MOVSD MOV ECX,EDX AND ECX,3 REP MOVSB LEA EAX,[EDI-1] POP ESI POP EDI end; function StrLCopy(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar; assembler; asm PUSH EDI PUSH ESI PUSH EBX MOV ESI,EAX MOV EDI,EDX MOV EBX,ECX XOR AL,AL TEST ECX,ECX JZ @@1 REPNE SCASB JNE @@1 INC ECX @@1: SUB EBX,ECX MOV EDI,ESI MOV ESI,EDX MOV EDX,EDI MOV ECX,EBX SHR ECX,2 REP MOVSD MOV ECX,EBX AND ECX,3 REP MOVSB STOSB MOV EAX,EDX POP EBX POP ESI POP EDI end; function StrPCopy(Dest: PChar; const Source: string): PChar; begin Result := StrLCopy(Dest, PChar(Source), Length(Source)); end; function StrPLCopy(Dest: PChar; const Source: string; MaxLen: Cardinal): PChar; begin Result := StrLCopy(Dest, PChar(Source), MaxLen); end; function StrCat(Dest: PChar; const Source: PChar): PChar; begin StrCopy(StrEnd(Dest), Source); Result := Dest; end; function StrLCat(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar; assembler; asm PUSH EDI PUSH ESI PUSH EBX MOV EDI,Dest MOV ESI,Source MOV EBX,MaxLen CALL StrEnd MOV ECX,EDI ADD ECX,EBX SUB ECX,EAX JBE @@1 MOV EDX,ESI CALL StrLCopy @@1: MOV EAX,EDI POP EBX POP ESI POP EDI end; function StrComp(const Str1, Str2: PChar): Integer; assembler; asm PUSH EDI PUSH ESI MOV EDI,EDX MOV ESI,EAX MOV ECX,0FFFFFFFFH XOR EAX,EAX REPNE SCASB NOT ECX MOV EDI,EDX XOR EDX,EDX REPE CMPSB MOV AL,[ESI-1] MOV DL,[EDI-1] SUB EAX,EDX POP ESI POP EDI end; function StrIComp(const Str1, Str2: PChar): Integer; assembler; asm PUSH EDI PUSH ESI MOV EDI,EDX MOV ESI,EAX MOV ECX,0FFFFFFFFH XOR EAX,EAX REPNE SCASB NOT ECX MOV EDI,EDX XOR EDX,EDX @@1: REPE CMPSB JE @@4 MOV AL,[ESI-1] CMP AL,'a' JB @@2 CMP AL,'z' JA @@2 SUB AL,20H @@2: MOV DL,[EDI-1] CMP DL,'a' JB @@3 CMP DL,'z' JA @@3 SUB DL,20H @@3: SUB EAX,EDX JE @@1 @@4: POP ESI POP EDI end; function StrLComp(const Str1, Str2: PChar; MaxLen: Cardinal): Integer; assembler; asm PUSH EDI PUSH ESI PUSH EBX MOV EDI,EDX MOV ESI,EAX MOV EBX,ECX XOR EAX,EAX OR ECX,ECX JE @@1 REPNE SCASB SUB EBX,ECX MOV ECX,EBX MOV EDI,EDX XOR EDX,EDX REPE CMPSB MOV AL,[ESI-1] MOV DL,[EDI-1] SUB EAX,EDX @@1: POP EBX POP ESI POP EDI end; function StrLIComp(const Str1, Str2: PChar; MaxLen: Cardinal): Integer; assembler; asm PUSH EDI PUSH ESI PUSH EBX MOV EDI,EDX MOV ESI,EAX MOV EBX,ECX XOR EAX,EAX OR ECX,ECX JE @@4 REPNE SCASB SUB EBX,ECX MOV ECX,EBX MOV EDI,EDX XOR EDX,EDX @@1: REPE CMPSB JE @@4 MOV AL,[ESI-1] CMP AL,'a' JB @@2 CMP AL,'z' JA @@2 SUB AL,20H @@2: MOV DL,[EDI-1] CMP DL,'a' JB @@3 CMP DL,'z' JA @@3 SUB DL,20H @@3: SUB EAX,EDX JE @@1 @@4: POP EBX POP ESI POP EDI end; function StrScan(const Str: PChar; Chr: Char): PChar; begin Result := Str; while Result^ <> Chr do begin if Result^ = #0 then begin Result := nil; Exit; end; Inc(Result); end; end; function StrRScan(const Str: PChar; Chr: Char): PChar; var MostRecentFound: PChar; begin if Chr = #0 then Result := StrEnd(Str) else begin Result := nil; MostRecentFound := Str; while True do begin while MostRecentFound^ <> Chr do begin if MostRecentFound^ = #0 then Exit; Inc(MostRecentFound); end; Result := MostRecentFound; Inc(MostRecentFound); end; end; end; function StrPos(const Str1, Str2: PChar): PChar; assembler; asm PUSH EDI PUSH ESI PUSH EBX OR EAX,EAX JE @@2 OR EDX,EDX JE @@2 MOV EBX,EAX MOV EDI,EDX XOR AL,AL MOV ECX,0FFFFFFFFH REPNE SCASB NOT ECX DEC ECX JE @@2 MOV ESI,ECX MOV EDI,EBX MOV ECX,0FFFFFFFFH REPNE SCASB NOT ECX SUB ECX,ESI JBE @@2 MOV EDI,EBX LEA EBX,[ESI-1] @@1: MOV ESI,EDX LODSB REPNE SCASB JNE @@2 MOV EAX,ECX PUSH EDI MOV ECX,EBX REPE CMPSB POP EDI MOV ECX,EAX JNE @@1 LEA EAX,[EDI-1] JMP @@3 @@2: XOR EAX,EAX @@3: POP EBX POP ESI POP EDI end; function StrUpper(Str: PChar): PChar; assembler; asm PUSH ESI MOV ESI,Str MOV EDX,Str @@1: LODSB OR AL,AL JE @@2 CMP AL,'a' JB @@1 CMP AL,'z' JA @@1 SUB AL,20H MOV [ESI-1],AL JMP @@1 @@2: XCHG EAX,EDX POP ESI end; function StrLower(Str: PChar): PChar; assembler; asm PUSH ESI MOV ESI,Str MOV EDX,Str @@1: LODSB OR AL,AL JE @@2 CMP AL,'A' JB @@1 CMP AL,'Z' JA @@1 ADD AL,20H MOV [ESI-1],AL JMP @@1 @@2: XCHG EAX,EDX POP ESI end; function StrPas(const Str: PChar): string; begin Result := Str; end; function StrAlloc(Size: Cardinal): PChar; begin Inc(Size, SizeOf(Cardinal)); GetMem(Result, Size); Cardinal(Pointer(Result)^) := Size; Inc(Result, SizeOf(Cardinal)); end; function StrBufSize(const Str: PChar): Cardinal; var P: PChar; begin P := Str; Dec(P, SizeOf(Cardinal)); Result := Cardinal(Pointer(P)^) - SizeOf(Cardinal); end; function StrNew(const Str: PChar): PChar; var Size: Cardinal; begin if Str = nil then Result := nil else begin Size := StrLen(Str) + 1; Result := StrMove(StrAlloc(Size), Str, Size); end; end; procedure StrDispose(Str: PChar); begin if Str <> nil then begin Dec(Str, SizeOf(Cardinal)); FreeMem(Str, Cardinal(Pointer(Str)^)); end; end; { String formatting routines } procedure FormatError(ErrorCode: Integer; Format: PChar; FmtLen: Cardinal); const FormatErrorStrs: array[0..1] of string = ( SInvalidFormat, SArgumentMissing); var Buffer: array[0..31] of Char; begin if FmtLen > 31 then FmtLen := 31; if StrByteType(Format, FmtLen-1) = mbLeadByte then Dec(FmtLen); StrMove(Buffer, Format, FmtLen); Buffer[FmtLen] := #0; ConvertErrorFmt(FormatErrorStrs[ErrorCode], [PChar(@Buffer)]); end; procedure FormatVarToStr(var S: string; const V: Variant); begin {if Assigned(System.VarToLStr) then System.is(S, V) else System.Error(reVarInvalidOp); } S:=''; end; procedure FormatClearStr(var S: string); begin S := ''; end; function FloatToTextEx(BufferArg: PChar; const Value; ValueType: TFloatValue; Format: TFloatFormat; Precision, Digits: Integer; const FormatSettings: TFormatSettings): Integer; begin Result := FloatToText(BufferArg, Value, ValueType, Format, Precision, Digits, FormatSettings); end; function FormatBuf(var Buffer; BufLen: Cardinal; const Format; FmtLen: Cardinal; const Args: array of const): Cardinal; var ArgIndex, Width, Prec: Integer; BufferOrg, FormatOrg, FormatPtr, TempStr: PChar; JustFlag: Byte; StrBuf: array[0..64] of Char; TempAnsiStr: string; SaveGOT: Integer; { in: eax <-> Buffer } { in: edx <-> BufLen } { in: ecx <-> Format } asm PUSH EBX PUSH ESI PUSH EDI MOV EDI,EAX MOV ESI,ECX {$IFDEF PIC} PUSH ECX CALL GetGOT POP ECX {$ELSE} XOR EAX,EAX {$ENDIF} MOV SaveGOT,EAX ADD ECX,FmtLen MOV BufferOrg,EDI XOR EAX,EAX MOV ArgIndex,EAX MOV TempStr,EAX MOV TempAnsiStr,EAX @Loop: OR EDX,EDX JE @Done @NextChar: CMP ESI,ECX JE @Done LODSB CMP AL,'%' JE @Format @StoreChar: STOSB DEC EDX JNE @NextChar @Done: MOV EAX,EDI SUB EAX,BufferOrg JMP @Exit @Format: CMP ESI,ECX JE @Done LODSB CMP AL,'%' JE @StoreChar LEA EBX,[ESI-2] MOV FormatOrg,EBX @A0: MOV JustFlag,AL CMP AL,'-' JNE @A1 CMP ESI,ECX JE @Done LODSB @A1: CALL @Specifier CMP AL,':' JNE @A2 MOV ArgIndex,EBX CMP ESI,ECX JE @Done LODSB JMP @A0 @A2: MOV Width,EBX MOV EBX,-1 CMP AL,'.' JNE @A3 CMP ESI,ECX JE @Done LODSB CALL @Specifier @A3: MOV Prec,EBX MOV FormatPtr,ESI PUSH ECX PUSH EDX CALL @Convert POP EDX MOV EBX,Width SUB EBX,ECX //(* ECX <=> number of characters output *) JAE @A4 //(* jump -> output smaller than width *) XOR EBX,EBX @A4: CMP JustFlag,'-' JNE @A6 SUB EDX,ECX JAE @A5 ADD ECX,EDX XOR EDX,EDX @A5: REP MOVSB @A6: XCHG EBX,ECX SUB EDX,ECX JAE @A7 ADD ECX,EDX XOR EDX,EDX @A7: MOV AL,' ' REP STOSB XCHG EBX,ECX SUB EDX,ECX JAE @A8 ADD ECX,EDX XOR EDX,EDX @A8: REP MOVSB CMP TempStr,0 JE @A9 PUSH EDX LEA EAX,TempStr // PUSH EBX // GOT setup unnecessary for // MOV EBX, SaveGOT // same-unit calls to Pascal procedures CALL FormatClearStr // POP EBX POP EDX @A9: POP ECX MOV ESI,FormatPtr JMP @Loop @Specifier: XOR EBX,EBX CMP AL,'*' JE @B3 @B1: CMP AL,'0' JB @B5 CMP AL,'9' JA @B5 IMUL EBX,EBX,10 SUB AL,'0' MOVZX EAX,AL ADD EBX,EAX CMP ESI,ECX JE @B2 LODSB JMP @B1 @B2: POP EAX JMP @Done @B3: MOV EAX,ArgIndex CMP EAX,Args.Integer[-4] JG @B4 INC ArgIndex MOV EBX,Args CMP [EBX+EAX*8].Byte[4],vtInteger MOV EBX,[EBX+EAX*8] JE @B4 XOR EBX,EBX @B4: CMP ESI,ECX JE @B2 LODSB @B5: RET @Convert: AND AL,0DFH MOV CL,AL MOV EAX,1 MOV EBX,ArgIndex CMP EBX,Args.Integer[-4] JG @ErrorExit INC ArgIndex MOV ESI,Args LEA ESI,[ESI+EBX*8] MOV EAX,[ESI].Integer[0] // TVarRec.data MOVZX EDX,[ESI].Byte[4] // TVarRec.VType {$IFDEF PIC} MOV EBX, SaveGOT ADD EBX, offset @CvtVector MOV EBX, [EBX+EDX*4] ADD EBX, SaveGOT JMP EBX {$ELSE} JMP @CvtVector.Pointer[EDX*4] {$ENDIF} @CvtVector: DD @CvtInteger // vtInteger DD @CvtBoolean // vtBoolean DD @CvtChar // vtChar DD @CvtExtended // vtExtended DD @CvtShortStr // vtString DD @CvtPointer // vtPointer DD @CvtPChar // vtPChar DD @CvtObject // vtObject DD @CvtClass // vtClass DD @CvtWideChar // vtWideChar DD @CvtPWideChar // vtPWideChar DD @CvtAnsiStr // vtAnsiString DD @CvtCurrency // vtCurrency DD @CvtVariant // vtVariant DD @CvtInterface // vtInterface DD @CvtWideString // vtWideString DD @CvtInt64 // vtInt64 @CvtBoolean: @CvtObject: @CvtClass: @CvtWideChar: @CvtInterface: @CvtError: XOR EAX,EAX @ErrorExit: CALL @ClearTmpAnsiStr MOV EDX,FormatOrg MOV ECX,FormatPtr SUB ECX,EDX {$IFDEF PC_MAPPED_EXCEPTIONS} // Because of all the assembly code here, we can't call a routine // that throws an exception if it looks like we're still on the // stack. The static disassembler cannot give sufficient unwind // frame info to unwind the confusion that is generated from the // assembly code above. So before we throw the exception, we // go to some lengths to excise ourselves from the stack chain. // We were passed 12 bytes of parameters on the stack, and we have // to make sure that we get rid of those, too. MOV EBX, SaveGOT MOV ESP, EBP // Ditch everthing to the frame MOV EBP, [ESP + 4] // Get the return addr MOV [ESP + 16], EBP // Move the ret addr up in the stack POP EBP // Ditch the rest of the frame ADD ESP, 12 // Ditch the space that was taken by params JMP FormatError // Off to FormatErr {$ELSE} MOV EBX, SaveGOT CALL FormatError {$ENDIF} // The above call raises an exception and does not return @CvtInt64: // CL <= format character // EAX <= address of int64 // EBX <= TVarRec.VType LEA ESI,StrBuf[32] MOV EDX, Prec CMP EDX, 32 JBE @I64_1 // zero padded field width > buffer => no padding XOR EDX, EDX @I64_1: MOV EBX, ECX SUB CL, 'D' JZ CvtInt64 // branch predict backward jump taken MOV ECX, 16 CMP BL, 'X' JE CvtInt64 MOV ECX, 10 CMP BL, 'U' JE CvtInt64 JMP @CvtError { LEA EBX, TempInt64 // (input is array of const; save original) MOV EDX, [EAX] MOV [EBX], EDX MOV EDX, [EAX + 4] MOV [EBX + 4], EDX // EBX <= address of TempInt64 CMP CL,'D' JE @DecI64 CMP CL,'U' JE @DecI64_2 CMP CL,'X' JNE @CvtError @HexI64: MOV ECX,16 // hex divisor JMP @CvtI64 @DecI64: TEST DWORD PTR [EBX + 4], $80000000 // sign bit set? JE @DecI64_2 // no -> bypass '-' output NEG DWORD PTR [EBX] // negate lo-order, then hi-order ADC DWORD PTR [EBX+4], 0 NEG DWORD PTR [EBX+4] CALL @DecI64_2 MOV AL,'-' INC ECX DEC ESI MOV [ESI],AL RET @DecI64_2: // unsigned int64 output MOV ECX,10 // decimal divisor @CvtI64: LEA ESI,StrBuf[32] @CvtI64_1: PUSH EBX PUSH ECX // save radix PUSH 0 PUSH ECX // radix divisor (10 or 16 only) MOV EAX, [EBX] MOV EDX, [EBX + 4] MOV EBX, SaveGOT CALL System.@_llumod POP ECX // saved radix POP EBX XCHG EAX, EDX // lo-value to EDX for character output ADD DL,'0' CMP DL,'0'+10 JB @CvtI64_2 ADD DL,('A'-'0')-10 @CvtI64_2: DEC ESI MOV [ESI],DL PUSH EBX PUSH ECX // save radix PUSH 0 PUSH ECX // radix divisor (10 or 16 only) MOV EAX, [EBX] // value := value DIV radix MOV EDX, [EBX + 4] MOV EBX, SaveGOT CALL System.@_lludiv POP ECX // saved radix POP EBX MOV [EBX], EAX MOV [EBX + 4], EDX OR EAX,EDX // anything left to output? JNE @CvtI64_1 // no jump => EDX:EAX = 0 LEA ECX,StrBuf[32] SUB ECX,ESI MOV EDX,Prec CMP EDX,16 JBE @CvtI64_3 RET @CvtI64_3: SUB EDX,ECX JBE @CvtI64_5 ADD ECX,EDX MOV AL,'0' @CvtI64_4: DEC ESI MOV [ESI],AL DEC EDX JNE @CvtI64_4 @CvtI64_5: RET } //////////////////////////////////////////////// @CvtInteger: LEA ESI,StrBuf[16] MOV EDX, Prec MOV EBX, ECX CMP EDX, 16 JBE @C1 // zero padded field width > buffer => no padding XOR EDX, EDX @C1: SUB CL, 'D' JZ CvtInt // branch predict backward jump taken MOV ECX, 16 CMP BL, 'X' JE CvtInt MOV ECX, 10 CMP BL, 'U' JE CvtInt JMP @CvtError { CMP CL,'D' JE @C1 CMP CL,'U' JE @C2 CMP CL,'X' JNE @CvtError MOV ECX,16 JMP @CvtLong @C1: OR EAX,EAX JNS @C2 NEG EAX CALL @C2 MOV AL,'-' INC ECX DEC ESI MOV [ESI],AL RET @C2: MOV ECX,10 @CvtLong: LEA ESI,StrBuf[16] @D1: XOR EDX,EDX DIV ECX ADD DL,'0' CMP DL,'0'+10 JB @D2 ADD DL,('A'-'0')-10 @D2: DEC ESI MOV [ESI],DL OR EAX,EAX JNE @D1 LEA ECX,StrBuf[16] SUB ECX,ESI MOV EDX,Prec CMP EDX,16 JBE @D3 RET @D3: SUB EDX,ECX JBE @D5 ADD ECX,EDX MOV AL,'0' @D4: DEC ESI MOV [ESI],AL DEC EDX JNE @D4 @D5: RET } @CvtChar: CMP CL,'S' JNE @CvtError MOV ECX,1 RET @CvtVariant: CMP CL,'S' JNE @CvtError CMP [EAX].TVarData.VType,varNull JBE @CvtEmptyStr MOV EDX,EAX LEA EAX,TempStr // PUSH EBX // GOT setup unnecessary for // MOV EBX, SaveGOT // same-unit calls to Pascal procedures CALL FormatVarToStr // POP EBX MOV ESI,TempStr JMP @CvtStrRef @CvtEmptyStr: XOR ECX,ECX RET @CvtShortStr: CMP CL,'S' JNE @CvtError MOV ESI,EAX LODSB MOVZX ECX,AL JMP @CvtStrLen @CvtPWideChar: MOV ESI,OFFSET System.@LStrFromPWChar JMP @CvtWideThing @CvtWideString: MOV ESI,OFFSET System.@LStrFromWStr @CvtWideThing: ADD ESI, SaveGOT {$IFDEF PIC} MOV ESI, [ESI] {$ENDIF} CMP CL,'S' JNE @CvtError MOV EDX,EAX LEA EAX,TempAnsiStr PUSH EBX MOV EBX, SaveGOT CALL ESI POP EBX MOV ESI,TempAnsiStr MOV EAX,ESI JMP @CvtStrRef @CvtAnsiStr: CMP CL,'S' JNE @CvtError MOV ESI,EAX @CvtStrRef: OR ESI,ESI JE @CvtEmptyStr MOV ECX,[ESI-4] @CvtStrLen: CMP ECX,Prec JA @E1 RET @E1: MOV ECX,Prec RET @CvtPChar: CMP CL,'S' JNE @CvtError MOV ESI,EAX PUSH EDI MOV EDI,EAX XOR AL,AL MOV ECX,Prec JECXZ @F1 REPNE SCASB JNE @F1 DEC EDI @F1: MOV ECX,EDI SUB ECX,ESI POP EDI RET @CvtPointer: CMP CL,'P' JNE @CvtError MOV EDX,8 MOV ECX,16 LEA ESI,StrBuf[16] JMP CvtInt @CvtCurrency: MOV BH,fvCurrency JMP @CvtFloat @CvtExtended: MOV BH,fvExtended @CvtFloat: MOV ESI,EAX MOV BL,ffGeneral CMP CL,'G' JE @G2 MOV BL,ffExponent CMP CL,'E' JE @G2 MOV BL,ffFixed CMP CL,'F' JE @G1 MOV BL,ffNumber CMP CL,'N' JE @G1 CMP CL,'M' JNE @CvtError MOV BL,ffCurrency @G1: MOV EAX,18 MOV EDX,Prec CMP EDX,EAX JBE @G3 MOV EDX,2 CMP CL,'M' JNE @G3 MOVZX EDX,CurrencyDecimals JMP @G3 @G2: MOV EAX,Prec MOV EDX,3 CMP EAX,18 JBE @G3 MOV EAX,15 @G3: PUSH EBX PUSH EAX PUSH EDX LEA EAX,StrBuf MOV EDX,ESI MOVZX ECX,BH MOV EBX, SaveGOT CALL FloatToText MOV ECX,EAX LEA ESI,StrBuf RET @ClearTmpAnsiStr: PUSH EBX PUSH EAX LEA EAX,TempAnsiStr MOV EBX, SaveGOT CALL System.@LStrClr POP EAX POP EBX RET @Exit: CALL @ClearTmpAnsiStr POP EDI POP ESI POP EBX end; function FormatBuf(var Buffer; BufLen: Cardinal; const Format; FmtLen: Cardinal; const Args: array of const; const FormatSettings: TFormatSettings): Cardinal; var ArgIndex, Width, Prec: Integer; BufferOrg, FormatOrg, FormatPtr, TempStr: PChar; JustFlag: Byte; StrBuf: array[0..64] of Char; TempAnsiStr: string; SaveGOT: Integer; { in: eax <-> Buffer } { in: edx <-> BufLen } { in: ecx <-> Format } asm PUSH EBX PUSH ESI PUSH EDI MOV EDI,EAX MOV ESI,ECX {$IFDEF PIC} PUSH ECX CALL GetGOT POP ECX {$ELSE} XOR EAX,EAX {$ENDIF} MOV SaveGOT,EAX ADD ECX,FmtLen MOV BufferOrg,EDI XOR EAX,EAX MOV ArgIndex,EAX MOV TempStr,EAX MOV TempAnsiStr,EAX @Loop: OR EDX,EDX JE @Done @NextChar: CMP ESI,ECX JE @Done LODSB CMP AL,'%' JE @Format @StoreChar: STOSB DEC EDX JNE @NextChar @Done: MOV EAX,EDI SUB EAX,BufferOrg JMP @Exit @Format: CMP ESI,ECX JE @Done LODSB CMP AL,'%' JE @StoreChar LEA EBX,[ESI-2] MOV FormatOrg,EBX @A0: MOV JustFlag,AL CMP AL,'-' JNE @A1 CMP ESI,ECX JE @Done LODSB @A1: CALL @Specifier CMP AL,':' JNE @A2 MOV ArgIndex,EBX CMP ESI,ECX JE @Done LODSB JMP @A0 @A2: MOV Width,EBX MOV EBX,-1 CMP AL,'.' JNE @A3 CMP ESI,ECX JE @Done LODSB CALL @Specifier @A3: MOV Prec,EBX MOV FormatPtr,ESI PUSH ECX PUSH EDX CALL @Convert POP EDX MOV EBX,Width SUB EBX,ECX //(* ECX <=> number of characters output *) JAE @A4 //(* jump -> output smaller than width *) XOR EBX,EBX @A4: CMP JustFlag,'-' JNE @A6 SUB EDX,ECX JAE @A5 ADD ECX,EDX XOR EDX,EDX @A5: REP MOVSB @A6: XCHG EBX,ECX SUB EDX,ECX JAE @A7 ADD ECX,EDX XOR EDX,EDX @A7: MOV AL,' ' REP STOSB XCHG EBX,ECX SUB EDX,ECX JAE @A8 ADD ECX,EDX XOR EDX,EDX @A8: REP MOVSB CMP TempStr,0 JE @A9 PUSH EDX LEA EAX,TempStr // PUSH EBX // GOT setup unnecessary for // MOV EBX, SaveGOT // same-unit calls to Pascal procedures CALL FormatClearStr // POP EBX POP EDX @A9: POP ECX MOV ESI,FormatPtr JMP @Loop @Specifier: XOR EBX,EBX CMP AL,'*' JE @B3 @B1: CMP AL,'0' JB @B5 CMP AL,'9' JA @B5 IMUL EBX,EBX,10 SUB AL,'0' MOVZX EAX,AL ADD EBX,EAX CMP ESI,ECX JE @B2 LODSB JMP @B1 @B2: POP EAX JMP @Done @B3: MOV EAX,ArgIndex CMP EAX,Args.Integer[-4] JG @B4 INC ArgIndex MOV EBX,Args CMP [EBX+EAX*8].Byte[4],vtInteger MOV EBX,[EBX+EAX*8] JE @B4 XOR EBX,EBX @B4: CMP ESI,ECX JE @B2 LODSB @B5: RET @Convert: AND AL,0DFH MOV CL,AL MOV EAX,1 MOV EBX,ArgIndex CMP EBX,Args.Integer[-4] JG @ErrorExit INC ArgIndex MOV ESI,Args LEA ESI,[ESI+EBX*8] MOV EAX,[ESI].Integer[0] // TVarRec.data MOVZX EDX,[ESI].Byte[4] // TVarRec.VType {$IFDEF PIC} MOV EBX, SaveGOT ADD EBX, offset @CvtVector MOV EBX, [EBX+EDX*4] ADD EBX, SaveGOT JMP EBX {$ELSE} JMP @CvtVector.Pointer[EDX*4] {$ENDIF} @CvtVector: DD @CvtInteger // vtInteger DD @CvtBoolean // vtBoolean DD @CvtChar // vtChar DD @CvtExtended // vtExtended DD @CvtShortStr // vtString DD @CvtPointer // vtPointer DD @CvtPChar // vtPChar DD @CvtObject // vtObject DD @CvtClass // vtClass DD @CvtWideChar // vtWideChar DD @CvtPWideChar // vtPWideChar DD @CvtAnsiStr // vtAnsiString DD @CvtCurrency // vtCurrency DD @CvtVariant // vtVariant DD @CvtInterface // vtInterface DD @CvtWideString // vtWideString DD @CvtInt64 // vtInt64 @CvtBoolean: @CvtObject: @CvtClass: @CvtWideChar: @CvtInterface: @CvtError: XOR EAX,EAX @ErrorExit: CALL @ClearTmpAnsiStr MOV EDX,FormatOrg MOV ECX,FormatPtr SUB ECX,EDX {$IFDEF PC_MAPPED_EXCEPTIONS} // Because of all the assembly code here, we can't call a routine // that throws an exception if it looks like we're still on the // stack. The static disassembler cannot give sufficient unwind // frame info to unwind the confusion that is generated from the // assembly code above. So before we throw the exception, we // go to some lengths to excise ourselves from the stack chain. // We were passed 12 bytes of parameters on the stack, and we have // to make sure that we get rid of those, too. MOV EBX, SaveGOT MOV ESP, EBP // Ditch everthing to the frame MOV EBP, [ESP + 4] // Get the return addr MOV [ESP + 16], EBP // Move the ret addr up in the stack POP EBP // Ditch the rest of the frame ADD ESP, 12 // Ditch the space that was taken by params JMP FormatError // Off to FormatErr {$ELSE} MOV EBX, SaveGOT CALL FormatError {$ENDIF} // The above call raises an exception and does not return @CvtInt64: // CL <= format character // EAX <= address of int64 // EBX <= TVarRec.VType LEA ESI,StrBuf[32] MOV EDX, Prec CMP EDX, 32 JBE @I64_1 // zero padded field width > buffer => no padding XOR EDX, EDX @I64_1: MOV EBX, ECX SUB CL, 'D' JZ CvtInt64 // branch predict backward jump taken MOV ECX, 16 CMP BL, 'X' JE CvtInt64 MOV ECX, 10 CMP BL, 'U' JE CvtInt64 JMP @CvtError //////////////////////////////////////////////// @CvtInteger: LEA ESI,StrBuf[16] MOV EDX, Prec MOV EBX, ECX CMP EDX, 16 JBE @C1 // zero padded field width > buffer => no padding XOR EDX, EDX @C1: SUB CL, 'D' JZ CvtInt // branch predict backward jump taken MOV ECX, 16 CMP BL, 'X' JE CvtInt MOV ECX, 10 CMP BL, 'U' JE CvtInt JMP @CvtError @CvtChar: CMP CL,'S' JNE @CvtError MOV ECX,1 RET @CvtVariant: CMP CL,'S' JNE @CvtError CMP [EAX].TVarData.VType,varNull JBE @CvtEmptyStr MOV EDX,EAX LEA EAX,TempStr // PUSH EBX // GOT setup unnecessary for // MOV EBX, SaveGOT // same-unit calls to Pascal procedures CALL FormatVarToStr // POP EBX MOV ESI,TempStr JMP @CvtStrRef @CvtEmptyStr: XOR ECX,ECX RET @CvtShortStr: CMP CL,'S' JNE @CvtError MOV ESI,EAX LODSB MOVZX ECX,AL JMP @CvtStrLen @CvtPWideChar: MOV ESI,OFFSET System.@LStrFromPWChar JMP @CvtWideThing @CvtWideString: MOV ESI,OFFSET System.@LStrFromWStr @CvtWideThing: ADD ESI, SaveGOT {$IFDEF PIC} MOV ESI, [ESI] {$ENDIF} CMP CL,'S' JNE @CvtError MOV EDX,EAX LEA EAX,TempAnsiStr PUSH EBX MOV EBX, SaveGOT CALL ESI POP EBX MOV ESI,TempAnsiStr MOV EAX,ESI JMP @CvtStrRef @CvtAnsiStr: CMP CL,'S' JNE @CvtError MOV ESI,EAX @CvtStrRef: OR ESI,ESI JE @CvtEmptyStr MOV ECX,[ESI-4] @CvtStrLen: CMP ECX,Prec JA @E1 RET @E1: MOV ECX,Prec RET @CvtPChar: CMP CL,'S' JNE @CvtError MOV ESI,EAX PUSH EDI MOV EDI,EAX XOR AL,AL MOV ECX,Prec JECXZ @F1 REPNE SCASB JNE @F1 DEC EDI @F1: MOV ECX,EDI SUB ECX,ESI POP EDI RET @CvtPointer: CMP CL,'P' JNE @CvtError MOV EDX,8 MOV ECX,16 LEA ESI,StrBuf[16] JMP CvtInt @CvtCurrency: MOV BH,fvCurrency JMP @CvtFloat @CvtExtended: MOV BH,fvExtended @CvtFloat: MOV ESI,EAX MOV BL,ffGeneral CMP CL,'G' JE @G2 MOV BL,ffExponent CMP CL,'E' JE @G2 MOV BL,ffFixed CMP CL,'F' JE @G1 MOV BL,ffNumber CMP CL,'N' JE @G1 CMP CL,'M' JNE @CvtError MOV BL,ffCurrency @G1: MOV EAX,18 MOV EDX,Prec CMP EDX,EAX JBE @G3 MOV EDX,2 CMP CL,'M' JNE @G3 MOV EDX,FormatSettings MOVZX EDX,[EDX].TFormatSettings.CurrencyDecimals JMP @G3 @G2: MOV EAX,Prec MOV EDX,3 CMP EAX,18 JBE @G3 MOV EAX,15 @G3: PUSH EBX PUSH EAX PUSH EDX MOV EDX,[FormatSettings] PUSH EDX LEA EAX,StrBuf MOV EDX,ESI MOVZX ECX,BH MOV EBX, SaveGOT CALL FloatToTextEx MOV ECX,EAX LEA ESI,StrBuf RET @ClearTmpAnsiStr: PUSH EBX PUSH EAX LEA EAX,TempAnsiStr MOV EBX, SaveGOT CALL System.@LStrClr POP EAX POP EBX RET @Exit: CALL @ClearTmpAnsiStr POP EDI POP ESI POP EBX end; function StrFmt(Buffer, Format: PChar; const Args: array of const): PChar; begin if (Buffer <> nil) and (Format <> nil) then begin Buffer[FormatBuf(Buffer^, MaxInt, Format^, StrLen(Format), Args)] := #0; Result := Buffer; end else Result := nil; end; function StrFmt(Buffer, Format: PChar; const Args: array of const; const FormatSettings: TFormatSettings): PChar; begin if (Buffer <> nil) and (Format <> nil) then begin Buffer[FormatBuf(Buffer^, MaxInt, Format^, StrLen(Format), Args, FormatSettings)] := #0; Result := Buffer; end else Result := nil; end; function StrLFmt(Buffer: PChar; MaxBufLen: Cardinal; Format: PChar; const Args: array of const): PChar; begin if (Buffer <> nil) and (Format <> nil) then begin Buffer[FormatBuf(Buffer^, MaxBufLen, Format^, StrLen(Format), Args)] := #0; Result := Buffer; end else Result := nil; end; function StrLFmt(Buffer: PChar; MaxBufLen: Cardinal; Format: PChar; const Args: array of const; const FormatSettings: TFormatSettings): PChar; begin if (Buffer <> nil) and (Format <> nil) then begin Buffer[FormatBuf(Buffer^, MaxBufLen, Format^, StrLen(Format), Args, FormatSettings)] := #0; Result := Buffer; end else Result := nil; end; function Format(const Format: string; const Args: array of const): string; begin FmtStr(Result, Format, Args); end; function Format(const Format: string; const Args: array of const; const FormatSettings: TFormatSettings): string; begin FmtStr(Result, Format, Args, FormatSettings); end; procedure FmtStr(var Result: string; const Format: string; const Args: array of const); var Len, BufLen: Integer; Buffer: array[0..4095] of Char; begin BufLen := SizeOf(Buffer); if Length(Format) < (sizeof(Buffer) - (sizeof(Buffer) div 4)) then Len := FormatBuf(Buffer, sizeof(Buffer) - 1, Pointer(Format)^, Length(Format), Args) else begin BufLen := Length(Format); Len := BufLen; end; if Len >= BufLen - 1 then begin while Len >= BufLen - 1 do begin Inc(BufLen, BufLen); Result := ''; // prevent copying of existing data, for speed SetLength(Result, BufLen); Len := FormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^, Length(Format), Args); end; SetLength(Result, Len); end else SetString(Result, Buffer, Len); end; procedure FmtStr(var Result: string; const Format: string; const Args: array of const; const FormatSettings: TFormatSettings); var Len, BufLen: Integer; Buffer: array[0..4095] of Char; begin BufLen := SizeOf(Buffer); if Length(Format) < (sizeof(Buffer) - (sizeof(Buffer) div 4)) then Len := FormatBuf(Buffer, sizeof(Buffer) - 1, Pointer(Format)^, Length(Format), Args, FormatSettings) else begin BufLen := Length(Format); Len := BufLen; end; if Len >= BufLen - 1 then begin while Len >= BufLen - 1 do begin Inc(BufLen, BufLen); Result := ''; // prevent copying of existing data, for speed SetLength(Result, BufLen); Len := FormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^, Length(Format), Args, FormatSettings); end; SetLength(Result, Len); end else SetString(Result, Buffer, Len); end; procedure WideFormatError(ErrorCode: Integer; Format: PWideChar; FmtLen: Cardinal); var WideFormat: WideString; FormatText: string; begin SetLength(WideFormat, FmtLen); SetString(WideFormat, Format, FmtLen); FormatText := WideFormat; FormatError(ErrorCode, PChar(FormatText), FmtLen); end; procedure WideFormatVarToStr(var S: WideString; const V: TVarData); begin {if Assigned(System.VarToWStrProc) then System.VarToWStrProc(S, V) else System.Error(reVarInvalidOp); } S:='Cutted'; end; function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format; FmtLen: Cardinal; const Args: array of const): Cardinal; var ArgIndex, Width, Prec: Integer; BufferOrg, FormatOrg, FormatPtr: PWideChar; JustFlag: WideChar; StrBuf: array[0..64] of WideChar; TempWideStr: WideString; SaveGOT: Integer; { in: eax <-> Buffer } { in: edx <-> BufLen } { in: ecx <-> Format } asm PUSH EBX PUSH ESI PUSH EDI MOV EDI,EAX MOV ESI,ECX {$IFDEF PIC} CALL GetGOT {$ELSE} XOR EAX,EAX {$ENDIF} MOV SaveGOT,EAX MOV ECX,FmtLen LEA ECX,[ECX*2+ESI] MOV BufferOrg,EDI XOR EAX,EAX MOV ArgIndex,EAX MOV TempWideStr,EAX @Loop: OR EDX,EDX JE @Done @NextChar: CMP ESI,ECX JE @Done LODSW CMP AX,'%' JE @Format @StoreChar: STOSW DEC EDX JNE @NextChar @Done: MOV EAX,EDI SUB EAX,BufferOrg SHR EAX,1 JMP @Exit @Format: CMP ESI,ECX JE @Done LODSW CMP AX,'%' JE @StoreChar LEA EBX,[ESI-4] MOV FormatOrg,EBX @A0: MOV JustFlag,AX CMP AX,'-' JNE @A1 CMP ESI,ECX JE @Done LODSW @A1: CALL @Specifier CMP AX,':' JNE @A2 MOV ArgIndex,EBX CMP ESI,ECX JE @Done LODSW JMP @A0 @A2: MOV Width,EBX MOV EBX,-1 CMP AX,'.' JNE @A3 CMP ESI,ECX JE @Done LODSW CALL @Specifier @A3: MOV Prec,EBX MOV FormatPtr,ESI PUSH ECX PUSH EDX CALL @Convert POP EDX MOV EBX,Width SUB EBX,ECX //(* ECX <=> number of characters output *) JAE @A4 //(* jump -> output smaller than width *) XOR EBX,EBX @A4: CMP JustFlag,'-' JNE @A6 SUB EDX,ECX JAE @A5 ADD ECX,EDX XOR EDX,EDX @A5: REP MOVSW @A6: XCHG EBX,ECX SUB EDX,ECX JAE @A7 ADD ECX,EDX XOR EDX,EDX @A7: MOV AX,' ' REP STOSW XCHG EBX,ECX SUB EDX,ECX JAE @A8 ADD ECX,EDX XOR EDX,EDX @A8: REP MOVSW POP ECX MOV ESI,FormatPtr JMP @Loop @Specifier: XOR EBX,EBX CMP AX,'*' JE @B3 @B1: CMP AX,'0' JB @B5 CMP AX,'9' JA @B5 IMUL EBX,EBX,10 SUB AX,'0' MOVZX EAX,AX ADD EBX,EAX CMP ESI,ECX JE @B2 LODSW JMP @B1 @B2: POP EAX JMP @Done @B3: MOV EAX,ArgIndex CMP EAX,Args.Integer[-4] JG @B4 INC ArgIndex MOV EBX,Args CMP [EBX+EAX*8].Byte[4],vtInteger MOV EBX,[EBX+EAX*8] JE @B4 XOR EBX,EBX @B4: CMP ESI,ECX JE @B2 LODSW @B5: RET @Convert: AND AL,0DFH MOV CL,AL MOV EAX,1 MOV EBX,ArgIndex CMP EBX,Args.Integer[-4] JG @ErrorExit INC ArgIndex MOV ESI,Args LEA ESI,[ESI+EBX*8] MOV EAX,[ESI].Integer[0] // TVarRec.data MOVZX EDX,[ESI].Byte[4] // TVarRec.VType {$IFDEF PIC} MOV EBX, SaveGOT ADD EBX, offset @CvtVector MOV EBX, [EBX+EDX*4] ADD EBX, SaveGOT JMP EBX {$ELSE} JMP @CvtVector.Pointer[EDX*4] {$ENDIF} @CvtVector: DD @CvtInteger // vtInteger DD @CvtBoolean // vtBoolean DD @CvtChar // vtChar DD @CvtExtended // vtExtended DD @CvtShortStr // vtString DD @CvtPointer // vtPointer DD @CvtPChar // vtPChar DD @CvtObject // vtObject DD @CvtClass // vtClass DD @CvtWideChar // vtWideChar DD @CvtPWideChar // vtPWideChar DD @CvtAnsiStr // vtAnsiString DD @CvtCurrency // vtCurrency DD @CvtVariant // vtVariant DD @CvtInterface // vtInterface DD @CvtWideString // vtWideString DD @CvtInt64 // vtInt64 @CvtBoolean: @CvtObject: @CvtClass: @CvtInterface: @CvtError: XOR EAX,EAX @ErrorExit: CALL @ClearTmpWideStr MOV EDX,FormatOrg MOV ECX,FormatPtr SUB ECX,EDX SHR ECX,1 MOV EBX, SaveGOT {$IFDEF PC_MAPPED_EXCEPTIONS} // Because of all the assembly code here, we can't call a routine // that throws an exception if it looks like we're still on the // stack. The static disassembler cannot give sufficient unwind // frame info to unwind the confusion that is generated from the // assembly code above. So before we throw the exception, we // go to some lengths to excise ourselves from the stack chain. // We were passed 12 bytes of parameters on the stack, and we have // to make sure that we get rid of those, too. MOV ESP, EBP // Ditch everthing to the frame MOV EBP, [ESP + 4] // Get the return addr MOV [ESP + 16], EBP // Move the ret addr up in the stack POP EBP // Ditch the rest of the frame ADD ESP, 12 // Ditch the space that was taken by params JMP WideFormatError // Off to FormatErr {$ELSE} CALL WideFormatError {$ENDIF} // The above call raises an exception and does not return @CvtInt64: // CL <= format character // EAX <= address of int64 // EBX <= TVarRec.VType LEA ESI,StrBuf[64] MOV EDX, Prec CMP EDX, 32 JBE @I64_1 // zero padded field width > buffer => no padding XOR EDX, EDX @I64_1: MOV EBX, ECX SUB CL, 'D' JZ CvtInt64W // branch predict backward jump taken MOV ECX, 16 CMP BL, 'X' JE CvtInt64W MOV ECX, 10 CMP BL, 'U' JE CvtInt64W JMP @CvtError @CvtInteger: LEA ESI,StrBuf[32] MOV EDX, Prec MOV EBX, ECX CMP EDX, 16 JBE @C1 // zero padded field width > buffer => no padding XOR EDX, EDX @C1: SUB CL, 'D' JZ CvtIntW // branch predict backward jump taken MOV ECX, 16 CMP BL, 'X' JE CvtIntW MOV ECX, 10 CMP BL, 'U' JE CvtIntW JMP @CvtError @CvtChar: CMP CL,'S' JNE @CvtError MOV EAX,ESI MOV ECX,1 JMP @CvtAnsiThingLen @CvtWideChar: CMP CL,'S' JNE @CvtError MOV ECX,1 RET @CvtVariant: CMP CL,'S' JNE @CvtError CMP [EAX].TVarData.VType,varNull JBE @CvtEmptyStr MOV EDX,EAX LEA EAX,TempWideStr CALL WideFormatVarToStr MOV ESI,TempWideStr JMP @CvtWideStrRef @CvtEmptyStr: XOR ECX,ECX RET @CvtShortStr: CMP CL,'S' JNE @CvtError MOVZX ECX,BYTE PTR [EAX] INC EAX @CvtAnsiThingLen: MOV ESI,OFFSET System.@WStrFromPCharLen JMP @CvtAnsiThing @CvtPChar: MOV ESI,OFFSET System.@WStrFromPChar JMP @CvtAnsiThingTest @CvtAnsiStr: MOV ESI,OFFSET System.@WStrFromLStr @CvtAnsiThingTest: CMP CL,'S' JNE @CvtError @CvtAnsiThing: ADD ESI, SaveGOT {$IFDEF PIC} MOV ESI, [ESI] {$ENDIF} MOV EDX,EAX LEA EAX,TempWideStr PUSH EBX MOV EBX, SaveGOT CALL ESI POP EBX MOV ESI,TempWideStr JMP @CvtWideStrRef @CvtWideString: CMP CL,'S' JNE @CvtError MOV ESI,EAX @CvtWideStrRef: OR ESI,ESI JE @CvtEmptyStr MOV ECX,[ESI-4] SHR ECX,1 @CvtWideStrLen: CMP ECX,Prec JA @E1 RET @E1: MOV ECX,Prec RET @CvtPWideChar: CMP CL,'S' JNE @CvtError MOV ESI,EAX PUSH EDI MOV EDI,EAX XOR EAX,EAX MOV ECX,Prec JECXZ @F1 REPNE SCASW JNE @F1 DEC EDI DEC EDI @F1: MOV ECX,EDI SUB ECX,ESI SHR ECX,1 POP EDI RET @CvtPointer: CMP CL,'P' JNE @CvtError MOV EDX,8 MOV ECX,16 LEA ESI,StrBuf[32] JMP CvtInt @CvtCurrency: MOV BH,fvCurrency JMP @CvtFloat @CvtExtended: MOV BH,fvExtended @CvtFloat: MOV ESI,EAX MOV BL,ffGeneral CMP CL,'G' JE @G2 MOV BL,ffExponent CMP CL,'E' JE @G2 MOV BL,ffFixed CMP CL,'F' JE @G1 MOV BL,ffNumber CMP CL,'N' JE @G1 CMP CL,'M' JNE @CvtError MOV BL,ffCurrency @G1: MOV EAX,18 MOV EDX,Prec CMP EDX,EAX JBE @G3 MOV EDX,2 CMP CL,'M' JNE @G3 MOVZX EDX,CurrencyDecimals JMP @G3 @G2: MOV EAX,Prec MOV EDX,3 CMP EAX,18 JBE @G3 MOV EAX,15 @G3: PUSH EBX PUSH EAX PUSH EDX LEA EAX,StrBuf MOV EDX,ESI MOVZX ECX,BH MOV EBX, SaveGOT CALL FloatToText MOV ECX,EAX LEA EDX,StrBuf LEA EAX,TempWideStr MOV EBX, SaveGOT CALL System.@WStrFromPCharLen MOV ESI,TempWideStr OR ESI,ESI JE @CvtEmptyStr MOV ECX,[ESI-4] SHR ECX,1 RET @ClearTmpWideStr: PUSH EBX PUSH EAX LEA EAX,TempWideStr MOV EBX, SaveGOT CALL System.@WStrClr POP EAX POP EBX RET @Exit: CALL @ClearTmpWideStr POP EDI POP ESI POP EBX end; function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format; FmtLen: Cardinal; const Args: array of const; const FormatSettings: TFormatSettings): Cardinal; var ArgIndex, Width, Prec: Integer; BufferOrg, FormatOrg, FormatPtr: PWideChar; JustFlag: WideChar; StrBuf: array[0..64] of WideChar; TempWideStr: WideString; SaveGOT: Integer; { in: eax <-> Buffer } { in: edx <-> BufLen } { in: ecx <-> Format } asm PUSH EBX PUSH ESI PUSH EDI MOV EDI,EAX MOV ESI,ECX {$IFDEF PIC} CALL GetGOT {$ELSE} XOR EAX,EAX {$ENDIF} MOV SaveGOT,EAX MOV ECX,FmtLen LEA ECX,[ECX*2+ESI] MOV BufferOrg,EDI XOR EAX,EAX MOV ArgIndex,EAX MOV TempWideStr,EAX @Loop: OR EDX,EDX JE @Done @NextChar: CMP ESI,ECX JE @Done LODSW CMP AX,'%' JE @Format @StoreChar: STOSW DEC EDX JNE @NextChar @Done: MOV EAX,EDI SUB EAX,BufferOrg SHR EAX,1 JMP @Exit @Format: CMP ESI,ECX JE @Done LODSW CMP AX,'%' JE @StoreChar LEA EBX,[ESI-4] MOV FormatOrg,EBX @A0: MOV JustFlag,AX CMP AX,'-' JNE @A1 CMP ESI,ECX JE @Done LODSW @A1: CALL @Specifier CMP AX,':' JNE @A2 MOV ArgIndex,EBX CMP ESI,ECX JE @Done LODSW JMP @A0 @A2: MOV Width,EBX MOV EBX,-1 CMP AX,'.' JNE @A3 CMP ESI,ECX JE @Done LODSW CALL @Specifier @A3: MOV Prec,EBX MOV FormatPtr,ESI PUSH ECX PUSH EDX CALL @Convert POP EDX MOV EBX,Width SUB EBX,ECX //(* ECX <=> number of characters output *) JAE @A4 //(* jump -> output smaller than width *) XOR EBX,EBX @A4: CMP JustFlag,'-' JNE @A6 SUB EDX,ECX JAE @A5 ADD ECX,EDX XOR EDX,EDX @A5: REP MOVSW @A6: XCHG EBX,ECX SUB EDX,ECX JAE @A7 ADD ECX,EDX XOR EDX,EDX @A7: MOV AX,' ' REP STOSW XCHG EBX,ECX SUB EDX,ECX JAE @A8 ADD ECX,EDX XOR EDX,EDX @A8: REP MOVSW POP ECX MOV ESI,FormatPtr JMP @Loop @Specifier: XOR EBX,EBX CMP AX,'*' JE @B3 @B1: CMP AX,'0' JB @B5 CMP AX,'9' JA @B5 IMUL EBX,EBX,10 SUB AX,'0' MOVZX EAX,AX ADD EBX,EAX CMP ESI,ECX JE @B2 LODSW JMP @B1 @B2: POP EAX JMP @Done @B3: MOV EAX,ArgIndex CMP EAX,Args.Integer[-4] JG @B4 INC ArgIndex MOV EBX,Args CMP [EBX+EAX*8].Byte[4],vtInteger MOV EBX,[EBX+EAX*8] JE @B4 XOR EBX,EBX @B4: CMP ESI,ECX JE @B2 LODSW @B5: RET @Convert: AND AL,0DFH MOV CL,AL MOV EAX,1 MOV EBX,ArgIndex CMP EBX,Args.Integer[-4] JG @ErrorExit INC ArgIndex MOV ESI,Args LEA ESI,[ESI+EBX*8] MOV EAX,[ESI].Integer[0] // TVarRec.data MOVZX EDX,[ESI].Byte[4] // TVarRec.VType {$IFDEF PIC} MOV EBX, SaveGOT ADD EBX, offset @CvtVector MOV EBX, [EBX+EDX*4] ADD EBX, SaveGOT JMP EBX {$ELSE} JMP @CvtVector.Pointer[EDX*4] {$ENDIF} @CvtVector: DD @CvtInteger // vtInteger DD @CvtBoolean // vtBoolean DD @CvtChar // vtChar DD @CvtExtended // vtExtended DD @CvtShortStr // vtString DD @CvtPointer // vtPointer DD @CvtPChar // vtPChar DD @CvtObject // vtObject DD @CvtClass // vtClass DD @CvtWideChar // vtWideChar DD @CvtPWideChar // vtPWideChar DD @CvtAnsiStr // vtAnsiString DD @CvtCurrency // vtCurrency DD @CvtVariant // vtVariant DD @CvtInterface // vtInterface DD @CvtWideString // vtWideString DD @CvtInt64 // vtInt64 @CvtBoolean: @CvtObject: @CvtClass: @CvtInterface: @CvtError: XOR EAX,EAX @ErrorExit: CALL @ClearTmpWideStr MOV EDX,FormatOrg MOV ECX,FormatPtr SUB ECX,EDX SHR ECX,1 MOV EBX, SaveGOT {$IFDEF PC_MAPPED_EXCEPTIONS} // Because of all the assembly code here, we can't call a routine // that throws an exception if it looks like we're still on the // stack. The static disassembler cannot give sufficient unwind // frame info to unwind the confusion that is generated from the // assembly code above. So before we throw the exception, we // go to some lengths to excise ourselves from the stack chain. // We were passed 12 bytes of parameters on the stack, and we have // to make sure that we get rid of those, too. MOV ESP, EBP // Ditch everthing to the frame MOV EBP, [ESP + 4] // Get the return addr MOV [ESP + 16], EBP // Move the ret addr up in the stack POP EBP // Ditch the rest of the frame ADD ESP, 12 // Ditch the space that was taken by params JMP WideFormatError // Off to FormatErr {$ELSE} CALL WideFormatError {$ENDIF} // The above call raises an exception and does not return @CvtInt64: // CL <= format character // EAX <= address of int64 // EBX <= TVarRec.VType LEA ESI,StrBuf[64] MOV EDX,Prec CMP EDX, 32 JBE @I64_1 // zero padded field width > buffer => no padding XOR EDX, EDX @I64_1: MOV EBX, ECX SUB CL, 'D' JZ CvtInt64W // branch predict backward jump taken MOV ECX,16 CMP BL, 'X' JE CvtInt64W MOV ECX, 10 CMP BL, 'U' JE CvtInt64W JMP @CvtError @CvtInteger: LEA ESI,StrBuf[32] MOV EDX,Prec MOV EBX, ECX CMP EDX,16 JBE @C1 // zero padded field width > buffer => no padding XOR EDX, EDX @C1: SUB CL, 'D' JZ CvtIntW // branch predict backward jump taken MOV ECX, 16 CMP BL, 'X' JE CvtIntW MOV ECX, 10 CMP BL, 'U' JE CvtIntW JMP @CvtError @CvtChar: CMP CL,'S' JNE @CvtError MOV EAX,ESI MOV ECX,1 JMP @CvtAnsiThingLen @CvtWideChar: CMP CL,'S' JNE @CvtError MOV ECX,1 RET @CvtVariant: CMP CL,'S' JNE @CvtError CMP [EAX].TVarData.VType,varNull JBE @CvtEmptyStr MOV EDX,EAX LEA EAX,TempWideStr CALL WideFormatVarToStr MOV ESI,TempWideStr JMP @CvtWideStrRef @CvtEmptyStr: XOR ECX,ECX RET @CvtShortStr: CMP CL,'S' JNE @CvtError MOVZX ECX,BYTE PTR [EAX] INC EAX @CvtAnsiThingLen: MOV ESI,OFFSET System.@WStrFromPCharLen JMP @CvtAnsiThing @CvtPChar: MOV ESI,OFFSET System.@WStrFromPChar JMP @CvtAnsiThingTest @CvtAnsiStr: MOV ESI,OFFSET System.@WStrFromLStr @CvtAnsiThingTest: CMP CL,'S' JNE @CvtError @CvtAnsiThing: ADD ESI, SaveGOT {$IFDEF PIC} MOV ESI, [ESI] {$ENDIF} MOV EDX,EAX LEA EAX,TempWideStr PUSH EBX MOV EBX, SaveGOT CALL ESI POP EBX MOV ESI,TempWideStr JMP @CvtWideStrRef @CvtWideString: CMP CL,'S' JNE @CvtError MOV ESI,EAX @CvtWideStrRef: OR ESI,ESI JE @CvtEmptyStr MOV ECX,[ESI-4] SHR ECX,1 @CvtWideStrLen: CMP ECX,Prec JA @E1 RET @E1: MOV ECX,Prec RET @CvtPWideChar: CMP CL,'S' JNE @CvtError MOV ESI,EAX PUSH EDI MOV EDI,EAX XOR EAX,EAX MOV ECX,Prec JECXZ @F1 REPNE SCASW JNE @F1 DEC EDI DEC EDI @F1: MOV ECX,EDI SUB ECX,ESI SHR ECX,1 POP EDI RET @CvtPointer: CMP CL,'P' JNE @CvtError MOV EDX,8 MOV ECX,16 LEA ESI,StrBuf[32] JMP CvtInt @CvtCurrency: MOV BH,fvCurrency JMP @CvtFloat @CvtExtended: MOV BH,fvExtended @CvtFloat: MOV ESI,EAX MOV BL,ffGeneral CMP CL,'G' JE @G2 MOV BL,ffExponent CMP CL,'E' JE @G2 MOV BL,ffFixed CMP CL,'F' JE @G1 MOV BL,ffNumber CMP CL,'N' JE @G1 CMP CL,'M' JNE @CvtError MOV BL,ffCurrency @G1: MOV EAX,18 MOV EDX,Prec CMP EDX,EAX JBE @G3 MOV EDX,2 CMP CL,'M' JNE @G3 MOV EDX,FormatSettings MOVZX EDX,[EDX].TFormatSettings.CurrencyDecimals JMP @G3 @G2: MOV EAX,Prec MOV EDX,3 CMP EAX,18 JBE @G3 MOV EAX,15 @G3: PUSH EBX PUSH EAX PUSH EDX MOV EDX,[FormatSettings] PUSH EDX LEA EAX,StrBuf MOV EDX,ESI MOVZX ECX,BH MOV EBX, SaveGOT CALL FloatToTextEx MOV ECX,EAX LEA EDX,StrBuf LEA EAX,TempWideStr MOV EBX, SaveGOT CALL System.@WStrFromPCharLen MOV ESI,TempWideStr OR ESI,ESI JE @CvtEmptyStr MOV ECX,[ESI-4] SHR ECX,1 RET @ClearTmpWideStr: PUSH EBX PUSH EAX LEA EAX,TempWideStr MOV EBX, SaveGOT CALL System.@WStrClr POP EAX POP EBX RET @Exit: CALL @ClearTmpWideStr POP EDI POP ESI POP EBX end; procedure WideFmtStr(var Result: WideString; const Format: WideString; const Args: array of const); const BufSize = 2048; var Len, BufLen: Integer; Buffer: array[0..BufSize-1] of WideChar; begin if Length(Format) < (BufSize - (BufSize div 4)) then begin BufLen := BufSize; Len := WideFormatBuf(Buffer, BufSize - 1, Pointer(Format)^, Length(Format), Args); if Len < BufLen - 1 then begin SetString(Result, Buffer, Len); Exit; end; end else begin BufLen := Length(Format); Len := BufLen; end; while Len >= BufLen - 1 do begin Inc(BufLen, BufLen); Result := ''; // prevent copying of existing data, for speed SetLength(Result, BufLen); Len := WideFormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^, Length(Format), Args); end; SetLength(Result, Len); end; procedure WideFmtStr(var Result: WideString; const Format: WideString; const Args: array of const; const FormatSettings: TFormatSettings); const BufSize = 2048; var Len, BufLen: Integer; Buffer: array[0..BufSize-1] of WideChar; begin if Length(Format) < (BufSize - (BufSize div 4)) then begin BufLen := BufSize; Len := WideFormatBuf(Buffer, BufSize - 1, Pointer(Format)^, Length(Format), Args, FormatSettings); if Len < BufLen - 1 then begin SetString(Result, Buffer, Len); Exit; end; end else begin BufLen := Length(Format); Len := BufLen; end; while Len >= BufLen - 1 do begin Inc(BufLen, BufLen); Result := ''; // prevent copying of existing data, for speed SetLength(Result, BufLen); Len := WideFormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^, Length(Format), Args, FormatSettings); end; SetLength(Result, Len); end; function WideFormat(const Format: WideString; const Args: array of const): WideString; begin WideFmtStr(Result, Format, Args); end; function WideFormat(const Format: WideString; const Args: array of const; const FormatSettings: TFormatSettings): WideString; begin WideFmtStr(Result, Format, Args, FormatSettings); end; { Floating point conversion routines } const // 1E18 as a 64-bit integer Const1E18Lo = $0A7640000; Const1E18Hi = $00DE0B6B3; FCon1E18: Extended = 1E18; DCon10: Integer = 10; procedure PutExponent; // Store exponent // In AL = Exponent character ('E' or 'e') // AH = Positive sign character ('+' or 0) // BL = Zero indicator // ECX = Minimum number of digits (0..4) // EDX = Exponent // EDI = Destination buffer asm PUSH ESI {$IFDEF PIC} PUSH EAX PUSH ECX CALL GetGOT MOV ESI,EAX POP ECX POP EAX {$ELSE} XOR ESI,ESI {$ENDIF} STOSB OR BL,BL JNE @@0 XOR EDX,EDX JMP @@1 @@0: OR EDX,EDX JGE @@1 MOV AL,'-' NEG EDX JMP @@2 @@1: OR AH,AH JE @@3 MOV AL,AH @@2: STOSB @@3: XCHG EAX,EDX PUSH EAX MOV EBX,ESP @@4: XOR EDX,EDX DIV [ESI].DCon10 ADD DL,'0' MOV [EBX],DL INC EBX DEC ECX OR EAX,EAX JNE @@4 OR ECX,ECX JG @@4 @@5: DEC EBX MOV AL,[EBX] STOSB CMP EBX,ESP JNE @@5 POP EAX POP ESI end; function FloatToText(BufferArg: PChar; const Value; ValueType: TFloatValue; Format: TFloatFormat; Precision, Digits: Integer): Integer; var Buffer: Cardinal; FloatRec: TFloatRec; SaveGOT: Integer; DecimalSep: Char; ThousandSep: Char; CurrencyStr: Pointer; CurrFmt: Byte; NegCurrFmt: Byte; asm PUSH EDI PUSH ESI PUSH EBX MOV Buffer,EAX {$IFDEF PIC} PUSH ECX CALL GetGOT MOV SaveGOT,EAX MOV ECX,[EAX].OFFSET DecimalSeparator MOV CL,[ECX] MOV DecimalSep,CL MOV ECX,[EAX].OFFSET ThousandSeparator MOV CL,[ECX].Byte MOV ThousandSep,CL MOV ECX,[EAX].OFFSET CurrencyString MOV ECX,[ECX].Integer MOV CurrencyStr,ECX MOV ECX,[EAX].OFFSET CurrencyFormat MOV CL,[ECX].Byte MOV CurrFmt,CL MOV ECX,[EAX].OFFSET NegCurrFormat MOV CL,[ECX].Byte MOV NegCurrFmt,CL POP ECX {$ELSE} MOV AL,DecimalSeparator MOV DecimalSep,AL MOV AL,ThousandSeparator MOV ThousandSep,AL MOV EAX,CurrencyString MOV CurrencyStr,EAX MOV AL,CurrencyFormat MOV CurrFmt,AL MOV AL,NegCurrFormat MOV NegCurrFmt,AL MOV SaveGOT,0 {$ENDIF} MOV EAX,19 CMP CL,fvExtended JNE @@2 MOV EAX,Precision CMP EAX,2 JGE @@1 MOV EAX,2 @@1: CMP EAX,18 JLE @@2 MOV EAX,18 @@2: MOV Precision,EAX PUSH EAX MOV EAX,9999 CMP Format,ffFixed JB @@3 MOV EAX,Digits @@3: PUSH EAX LEA EAX,FloatRec CALL FloatToDecimal MOV EDI,Buffer MOVZX EAX,FloatRec.Exponent SUB EAX,7FFFH CMP EAX,2 JAE @@4 MOV ECX, EAX CALL @@PutSign LEA ESI,@@INFNAN[ECX+ECX*2] ADD ESI,SaveGOT MOV ECX,3 REP MOVSB JMP @@7 @@4: LEA ESI,FloatRec.Digits MOVZX EBX,Format CMP BL,ffExponent JE @@6 CMP BL,ffCurrency JA @@5 MOVSX EAX,FloatRec.Exponent CMP EAX,Precision JLE @@6 @@5: MOV BL,ffGeneral @@6: LEA EBX,@@FormatVector[EBX*4] ADD EBX,SaveGOT MOV EBX,[EBX] ADD EBX,SaveGOT CALL EBX @@7: MOV EAX,EDI SUB EAX,Buffer POP EBX POP ESI POP EDI JMP @@Exit @@FormatVector: DD @@PutFGeneral DD @@PutFExponent DD @@PutFFixed DD @@PutFNumber DD @@PutFCurrency @@INFNAN: DB 'INFNAN' // Get digit or '0' if at end of digit string @@GetDigit: LODSB OR AL,AL JNE @@a1 MOV AL,'0' DEC ESI @@a1: RET // Store '-' if number is negative @@PutSign: CMP FloatRec.Negative,0 JE @@b1 MOV AL,'-' STOSB @@b1: RET // Convert number using ffGeneral format @@PutFGeneral: CALL @@PutSign MOVSX ECX,FloatRec.Exponent XOR EDX,EDX CMP ECX,Precision JG @@c1 CMP ECX,-3 JL @@c1 OR ECX,ECX JG @@c2 MOV AL,'0' STOSB CMP BYTE PTR [ESI],0 JE @@c6 MOV AL,DecimalSep STOSB NEG ECX MOV AL,'0' REP STOSB JMP @@c3 @@c1: MOV ECX,1 INC EDX @@c2: LODSB OR AL,AL JE @@c4 STOSB LOOP @@c2 LODSB OR AL,AL JE @@c5 MOV AH,AL MOV AL,DecimalSep STOSW @@c3: LODSB OR AL,AL JE @@c5 STOSB JMP @@c3 @@c4: MOV AL,'0' REP STOSB @@c5: OR EDX,EDX JE @@c6 XOR EAX,EAX JMP @@PutFloatExpWithDigits @@c6: RET // Convert number using ffExponent format @@PutFExponent: CALL @@PutSign CALL @@GetDigit MOV AH,DecimalSep STOSW MOV ECX,Precision DEC ECX @@d1: CALL @@GetDigit STOSB LOOP @@d1 MOV AH,'+' @@PutFloatExpWithDigits: MOV ECX,Digits CMP ECX,4 JBE @@PutFloatExp XOR ECX,ECX // Store exponent // In AH = Positive sign character ('+' or 0) // ECX = Minimum number of digits (0..4) @@PutFloatExp: MOV AL,'E' MOV BL, FloatRec.Digits.Byte MOVSX EDX,FloatRec.Exponent DEC EDX CALL PutExponent RET // Convert number using ffFixed or ffNumber format @@PutFFixed: @@PutFNumber: CALL @@PutSign // Store number in fixed point format @@PutNumber: MOV EDX,Digits CMP EDX,18 JB @@f1 MOV EDX,18 @@f1: MOVSX ECX,FloatRec.Exponent OR ECX,ECX JG @@f2 MOV AL,'0' STOSB JMP @@f4 @@f2: XOR EBX,EBX CMP Format,ffFixed JE @@f3 MOV EAX,ECX DEC EAX MOV BL,3 DIV BL MOV BL,AH INC EBX @@f3: CALL @@GetDigit STOSB DEC ECX JE @@f4 DEC EBX JNE @@f3 MOV AL,ThousandSep TEST AL,AL JZ @@f3 STOSB MOV BL,3 JMP @@f3 @@f4: OR EDX,EDX JE @@f7 MOV AL,DecimalSep TEST AL,AL JZ @@f4b STOSB @@f4b: JECXZ @@f6 MOV AL,'0' @@f5: STOSB DEC EDX JE @@f7 INC ECX JNE @@f5 @@f6: CALL @@GetDigit STOSB DEC EDX JNE @@f6 @@f7: RET // Convert number using ffCurrency format @@PutFCurrency: XOR EBX,EBX MOV BL,CurrFmt.Byte MOV ECX,0003H CMP FloatRec.Negative,0 JE @@g1 MOV BL,NegCurrFmt.Byte MOV ECX,040FH @@g1: CMP BL,CL JBE @@g2 MOV BL,CL @@g2: ADD BL,CH LEA EBX,@@MoneyFormats[EBX+EBX*4] ADD EBX,SaveGOT MOV ECX,5 @@g10: MOV AL,[EBX] CMP AL,'@' JE @@g14 PUSH ECX PUSH EBX CMP AL,'$' JE @@g11 CMP AL,'*' JE @@g12 STOSB JMP @@g13 @@g11: CALL @@PutCurSym JMP @@g13 @@g12: CALL @@PutNumber @@g13: POP EBX POP ECX INC EBX LOOP @@g10 @@g14: RET // Store currency symbol string @@PutCurSym: PUSH ESI MOV ESI,CurrencyStr TEST ESI,ESI JE @@h1 MOV ECX,[ESI-4] REP MOVSB @@h1: POP ESI RET // Currency formatting templates @@MoneyFormats: DB '$*@@@' DB '*$@@@' DB '$ *@@' DB '* $@@' DB '($*)@' DB '-$*@@' DB '$-*@@' DB '$*-@@' DB '(*$)@' DB '-*$@@' DB '*-$@@' DB '*$-@@' DB '-* $@' DB '-$ *@' DB '* $-@' DB '$ *-@' DB '$ -*@' DB '*- $@' DB '($ *)' DB '(* $)' @@Exit: end; function FloatToText(BufferArg: PChar; const Value; ValueType: TFloatValue; Format: TFloatFormat; Precision, Digits: Integer; const FormatSettings: TFormatSettings): Integer; var Buffer: Cardinal; FloatRec: TFloatRec; SaveGOT: Integer; DecimalSep: Char; ThousandSep: Char; CurrencyStr: Pointer; CurrFmt: Byte; NegCurrFmt: Byte; asm PUSH EDI PUSH ESI PUSH EBX MOV Buffer,EAX {$IFDEF PIC} PUSH ECX CALL GetGOT MOV SaveGOT,EAX POP ECX {$ENDIF} MOV EAX,FormatSettings MOV AL,[EAX].TFormatSettings.DecimalSeparator MOV DecimalSep,AL MOV EAX,FormatSettings MOV AL,[EAX].TFormatSettings.ThousandSeparator MOV ThousandSep,AL MOV EAX,FormatSettings MOV EAX,[EAX].TFormatSettings.CurrencyString MOV CurrencyStr,EAX MOV EAX,FormatSettings MOV AL,[EAX].TFormatSettings.CurrencyFormat MOV CurrFmt,AL MOV EAX,FormatSettings MOV AL,[EAX].TFormatSettings.NegCurrFormat MOV NegCurrFmt,AL MOV SaveGOT,0 MOV EAX,19 CMP CL,fvExtended JNE @@2 MOV EAX,Precision CMP EAX,2 JGE @@1 MOV EAX,2 @@1: CMP EAX,18 JLE @@2 MOV EAX,18 @@2: MOV Precision,EAX PUSH EAX MOV EAX,9999 CMP Format,ffFixed JB @@3 MOV EAX,Digits @@3: PUSH EAX LEA EAX,FloatRec CALL FloatToDecimal MOV EDI,Buffer MOVZX EAX,FloatRec.Exponent SUB EAX,7FFFH CMP EAX,2 JAE @@4 MOV ECX, EAX CALL @@PutSign LEA ESI,@@INFNAN[ECX+ECX*2] ADD ESI,SaveGOT MOV ECX,3 REP MOVSB JMP @@7 @@4: LEA ESI,FloatRec.Digits MOVZX EBX,Format CMP BL,ffExponent JE @@6 CMP BL,ffCurrency JA @@5 MOVSX EAX,FloatRec.Exponent CMP EAX,Precision JLE @@6 @@5: MOV BL,ffGeneral @@6: LEA EBX,@@FormatVector[EBX*4] ADD EBX,SaveGOT MOV EBX,[EBX] ADD EBX,SaveGOT CALL EBX @@7: MOV EAX,EDI SUB EAX,Buffer POP EBX POP ESI POP EDI JMP @@Exit @@FormatVector: DD @@PutFGeneral DD @@PutFExponent DD @@PutFFixed DD @@PutFNumber DD @@PutFCurrency @@INFNAN: DB 'INFNAN' // Get digit or '0' if at end of digit string @@GetDigit: LODSB OR AL,AL JNE @@a1 MOV AL,'0' DEC ESI @@a1: RET // Store '-' if number is negative @@PutSign: CMP FloatRec.Negative,0 JE @@b1 MOV AL,'-' STOSB @@b1: RET // Convert number using ffGeneral format @@PutFGeneral: CALL @@PutSign MOVSX ECX,FloatRec.Exponent XOR EDX,EDX CMP ECX,Precision JG @@c1 CMP ECX,-3 JL @@c1 OR ECX,ECX JG @@c2 MOV AL,'0' STOSB CMP BYTE PTR [ESI],0 JE @@c6 MOV AL,DecimalSep STOSB NEG ECX MOV AL,'0' REP STOSB JMP @@c3 @@c1: MOV ECX,1 INC EDX @@c2: LODSB OR AL,AL JE @@c4 STOSB LOOP @@c2 LODSB OR AL,AL JE @@c5 MOV AH,AL MOV AL,DecimalSep STOSW @@c3: LODSB OR AL,AL JE @@c5 STOSB JMP @@c3 @@c4: MOV AL,'0' REP STOSB @@c5: OR EDX,EDX JE @@c6 XOR EAX,EAX JMP @@PutFloatExpWithDigits @@c6: RET // Convert number using ffExponent format @@PutFExponent: CALL @@PutSign CALL @@GetDigit MOV AH,DecimalSep STOSW MOV ECX,Precision DEC ECX @@d1: CALL @@GetDigit STOSB LOOP @@d1 MOV AH,'+' @@PutFloatExpWithDigits: MOV ECX,Digits CMP ECX,4 JBE @@PutFloatExp XOR ECX,ECX // Store exponent // In AH = Positive sign character ('+' or 0) // ECX = Minimum number of digits (0..4) @@PutFloatExp: MOV AL,'E' MOV BL, FloatRec.Digits.Byte MOVSX EDX,FloatRec.Exponent DEC EDX CALL PutExponent RET // Convert number using ffFixed or ffNumber format @@PutFFixed: @@PutFNumber: CALL @@PutSign // Store number in fixed point format @@PutNumber: MOV EDX,Digits CMP EDX,18 JB @@f1 MOV EDX,18 @@f1: MOVSX ECX,FloatRec.Exponent OR ECX,ECX JG @@f2 MOV AL,'0' STOSB JMP @@f4 @@f2: XOR EBX,EBX CMP Format,ffFixed JE @@f3 MOV EAX,ECX DEC EAX MOV BL,3 DIV BL MOV BL,AH INC EBX @@f3: CALL @@GetDigit STOSB DEC ECX JE @@f4 DEC EBX JNE @@f3 MOV AL,ThousandSep TEST AL,AL JZ @@f3 STOSB MOV BL,3 JMP @@f3 @@f4: OR EDX,EDX JE @@f7 MOV AL,DecimalSep TEST AL,AL JZ @@f4b STOSB @@f4b: JECXZ @@f6 MOV AL,'0' @@f5: STOSB DEC EDX JE @@f7 INC ECX JNE @@f5 @@f6: CALL @@GetDigit STOSB DEC EDX JNE @@f6 @@f7: RET // Convert number using ffCurrency format @@PutFCurrency: XOR EBX,EBX MOV BL,CurrFmt.Byte MOV ECX,0003H CMP FloatRec.Negative,0 JE @@g1 MOV BL,NegCurrFmt.Byte MOV ECX,040FH @@g1: CMP BL,CL JBE @@g2 MOV BL,CL @@g2: ADD BL,CH LEA EBX,@@MoneyFormats[EBX+EBX*4] ADD EBX,SaveGOT MOV ECX,5 @@g10: MOV AL,[EBX] CMP AL,'@' JE @@g14 PUSH ECX PUSH EBX CMP AL,'$' JE @@g11 CMP AL,'*' JE @@g12 STOSB JMP @@g13 @@g11: CALL @@PutCurSym JMP @@g13 @@g12: CALL @@PutNumber @@g13: POP EBX POP ECX INC EBX LOOP @@g10 @@g14: RET // Store currency symbol string @@PutCurSym: PUSH ESI MOV ESI,CurrencyStr TEST ESI,ESI JE @@h1 MOV ECX,[ESI-4] REP MOVSB @@h1: POP ESI RET // Currency formatting templates @@MoneyFormats: DB '$*@@@' DB '*$@@@' DB '$ *@@' DB '* $@@' DB '($*)@' DB '-$*@@' DB '$-*@@' DB '$*-@@' DB '(*$)@' DB '-*$@@' DB '*-$@@' DB '*$-@@' DB '-* $@' DB '-$ *@' DB '* $-@' DB '$ *-@' DB '$ -*@' DB '*- $@' DB '($ *)' DB '(* $)' @@Exit: end; function FloatToTextFmt(Buf: PChar; const Value; ValueType: TFloatValue; Format: PChar): Integer; var Buffer: Pointer; ThousandSep: Boolean; DecimalSep: Char; ThousandsSep: Char; Scientific: Boolean; Section: Integer; DigitCount: Integer; DecimalIndex: Integer; FirstDigit: Integer; LastDigit: Integer; DigitPlace: Integer; DigitDelta: Integer; FloatRec: TFloatRec; SaveGOT: Pointer; asm PUSH EDI PUSH ESI PUSH EBX MOV Buffer,EAX MOV EDI,EDX MOV EBX,ECX {$IFDEF PIC} CALL GetGOT MOV SaveGOT,EAX MOV ECX,[EAX].OFFSET DecimalSeparator MOV CL,[ECX].Byte MOV DecimalSep,CL MOV ECX,[EAX].OFFSET ThousandSeparator MOV CL,[ECX].Byte MOV ThousandsSep,CL {$ELSE} MOV SaveGOT,0 MOV AL,DecimalSeparator MOV DecimalSep,AL MOV AL,ThousandSeparator MOV ThousandsSep,AL {$ENDIF} MOV ECX,2 CMP BL,fvExtended JE @@1 MOV EAX,[EDI].Integer OR EAX,[EDI].Integer[4] JE @@2 MOV ECX,[EDI].Integer[4] SHR ECX,31 JMP @@2 @@1: MOVZX EAX,[EDI].Word[8] OR EAX,[EDI].Integer[0] OR EAX,[EDI].Integer[4] JE @@2 MOVZX ECX,[EDI].Word[8] SHR ECX,15 @@2: CALL @@FindSection JE @@5 CALL @@ScanSection MOV EAX,DigitCount MOV EDX,9999 CMP Scientific,0 JNE @@3 SUB EAX,DecimalIndex MOV EDX,EAX MOV EAX,18 @@3: PUSH EAX PUSH EDX LEA EAX,FloatRec MOV EDX,EDI MOV ECX,EBX CALL FloatToDecimal MOV AX,FloatRec.Exponent CMP AX,8000H JE @@5 CMP AX,7FFFH JE @@5 CMP BL,fvExtended JNE @@6 CMP AX,18 JLE @@6 CMP Scientific,0 JNE @@6 @@5: PUSH ffGeneral PUSH 15 PUSH 0 MOV EAX,Buffer MOV EDX,EDI MOV ECX,EBX CALL FloatToText JMP @@Exit @@6: CMP FloatRec.Digits.Byte,0 JNE @@7 MOV ECX,2 CALL @@FindSection JE @@5 CMP ESI,Section JE @@7 CALL @@ScanSection @@7: CALL @@ApplyFormat JMP @@Exit // Find format section // In ECX = Section index // Out ESI = Section offset // ZF = 1 if section is empty @@FindSection: MOV ESI,Format JECXZ @@fs2 @@fs1: LODSB CMP AL,"'" JE @@fs4 CMP AL,'"' JE @@fs4 OR AL,AL JE @@fs2 CMP AL,';' JNE @@fs1 LOOP @@fs1 MOV AL,byte ptr [ESI] OR AL,AL JE @@fs2 CMP AL,';' JNE @@fs3 @@fs2: MOV ESI,Format MOV AL,byte ptr [ESI] OR AL,AL JE @@fs3 CMP AL,';' @@fs3: RET @@fs4: MOV AH,AL @@fs5: LODSB CMP AL,AH JE @@fs1 OR AL,AL JNE @@fs5 JMP @@fs2 // Scan format section @@ScanSection: PUSH EBX MOV Section,ESI MOV EBX,32767 XOR ECX,ECX XOR EDX,EDX MOV DecimalIndex,-1 MOV ThousandSep,DL MOV Scientific,DL @@ss1: LODSB @@ss2: CMP AL,'#' JE @@ss10 CMP AL,'0' JE @@ss11 CMP AL,'.' JE @@ss13 CMP AL,',' JE @@ss14 CMP AL,"'" JE @@ss15 CMP AL,'"' JE @@ss15 CMP AL,'E' JE @@ss20 CMP AL,'e' JE @@ss20 CMP AL,';' JE @@ss30 OR AL,AL JNE @@ss1 JMP @@ss30 @@ss10: INC EDX JMP @@ss1 @@ss11: CMP EDX,EBX JGE @@ss12 MOV EBX,EDX @@ss12: INC EDX MOV ECX,EDX JMP @@ss1 @@ss13: CMP DecimalIndex,-1 JNE @@ss1 MOV DecimalIndex,EDX JMP @@ss1 @@ss14: MOV ThousandSep,1 JMP @@ss1 @@ss15: MOV AH,AL @@ss16: LODSB CMP AL,AH JE @@ss1 OR AL,AL JNE @@ss16 JMP @@ss30 @@ss20: LODSB CMP AL,'-' JE @@ss21 CMP AL,'+' JNE @@ss2 @@ss21: MOV Scientific,1 @@ss22: LODSB CMP AL,'0' JE @@ss22 JMP @@ss2 @@ss30: MOV DigitCount,EDX CMP DecimalIndex,-1 JNE @@ss31 MOV DecimalIndex,EDX @@ss31: MOV EAX,DecimalIndex SUB EAX,ECX JLE @@ss32 XOR EAX,EAX @@ss32: MOV LastDigit,EAX MOV EAX,DecimalIndex SUB EAX,EBX JGE @@ss33 XOR EAX,EAX @@ss33: MOV FirstDigit,EAX POP EBX RET // Apply format string @@ApplyFormat: CMP Scientific,0 JE @@af1 MOV EAX,DecimalIndex XOR EDX,EDX JMP @@af3 @@af1: MOVSX EAX,FloatRec.Exponent CMP EAX,DecimalIndex JG @@af2 MOV EAX,DecimalIndex @@af2: MOVSX EDX,FloatRec.Exponent SUB EDX,DecimalIndex @@af3: MOV DigitPlace,EAX MOV DigitDelta,EDX MOV ESI,Section MOV EDI,Buffer LEA EBX,FloatRec.Digits CMP FloatRec.Negative,0 JE @@af10 CMP ESI,Format JNE @@af10 MOV AL,'-' STOSB @@af10: LODSB CMP AL,'#' JE @@af20 CMP AL,'0' JE @@af20 CMP AL,'.' JE @@af10 CMP AL,',' JE @@af10 CMP AL,"'" JE @@af25 CMP AL,'"' JE @@af25 CMP AL,'E' JE @@af30 CMP AL,'e' JE @@af30 CMP AL,';' JE @@af40 OR AL,AL JE @@af40 @@af11: STOSB JMP @@af10 @@af20: CALL @@PutFmtDigit JMP @@af10 @@af25: MOV AH,AL @@af26: LODSB CMP AL,AH JE @@af10 OR AL,AL JE @@af40 STOSB JMP @@af26 @@af30: MOV AH,[ESI] CMP AH,'+' JE @@af31 CMP AH,'-' JNE @@af11 XOR AH,AH @@af31: MOV ECX,-1 @@af32: INC ECX INC ESI CMP [ESI].Byte,'0' JE @@af32 CMP ECX,4 JB @@af33 MOV ECX,4 @@af33: PUSH EBX MOV BL,FloatRec.Digits.Byte MOVSX EDX,FloatRec.Exponent SUB EDX,DecimalIndex CALL PutExponent POP EBX JMP @@af10 @@af40: MOV EAX,EDI SUB EAX,Buffer RET // Store formatted digit @@PutFmtDigit: CMP DigitDelta,0 JE @@fd3 JL @@fd2 @@fd1: CALL @@fd3 DEC DigitDelta JNE @@fd1 JMP @@fd3 @@fd2: INC DigitDelta MOV EAX,DigitPlace CMP EAX,FirstDigit JLE @@fd4 JMP @@fd7 @@fd3: MOV AL,[EBX] INC EBX OR AL,AL JNE @@fd5 DEC EBX MOV EAX,DigitPlace CMP EAX,LastDigit JLE @@fd7 @@fd4: MOV AL,'0' @@fd5: CMP DigitPlace,0 JNE @@fd6 MOV AH,AL MOV AL,DecimalSep STOSW JMP @@fd7 @@fd6: STOSB CMP ThousandSep,0 JE @@fd7 MOV EAX,DigitPlace CMP EAX,1 JLE @@fd7 MOV DL,3 DIV DL CMP AH,1 JNE @@fd7 MOV AL,ThousandsSep TEST AL,AL JZ @@fd7 STOSB @@fd7: DEC DigitPlace RET @@exit: POP EBX POP ESI POP EDI end; function FloatToTextFmt(Buf: PChar; const Value; ValueType: TFloatValue; Format: PChar; const FormatSettings: TFormatSettings): Integer; var Buffer: Pointer; ThousandSep: Boolean; DecimalSep: Char; ThousandsSep: Char; Scientific: Boolean; Section: Integer; DigitCount: Integer; DecimalIndex: Integer; FirstDigit: Integer; LastDigit: Integer; DigitPlace: Integer; DigitDelta: Integer; FloatRec: TFloatRec; SaveGOT: Pointer; asm PUSH EDI PUSH ESI PUSH EBX MOV Buffer,EAX MOV EDI,EDX MOV EBX,ECX {$IFDEF PIC} CALL GetGOT MOV SaveGOT,EAX {$ELSE} MOV SaveGOT,0 {$ENDIF} MOV EAX,FormatSettings MOV AL,[EAX].TFormatSettings.DecimalSeparator MOV DecimalSep,AL MOV EAX,FormatSettings MOV AL,[EAX].TFormatSettings.ThousandSeparator MOV ThousandsSep,AL MOV ECX,2 CMP BL,fvExtended JE @@1 MOV EAX,[EDI].Integer OR EAX,[EDI].Integer[4] JE @@2 MOV ECX,[EDI].Integer[4] SHR ECX,31 JMP @@2 @@1: MOVZX EAX,[EDI].Word[8] OR EAX,[EDI].Integer[0] OR EAX,[EDI].Integer[4] JE @@2 MOVZX ECX,[EDI].Word[8] SHR ECX,15 @@2: CALL @@FindSection JE @@5 CALL @@ScanSection MOV EAX,DigitCount MOV EDX,9999 CMP Scientific,0 JNE @@3 SUB EAX,DecimalIndex MOV EDX,EAX MOV EAX,18 @@3: PUSH EAX PUSH EDX LEA EAX,FloatRec MOV EDX,EDI MOV ECX,EBX CALL FloatToDecimal MOV AX,FloatRec.Exponent CMP AX,8000H JE @@5 CMP AX,7FFFH JE @@5 CMP BL,fvExtended JNE @@6 CMP AX,18 JLE @@6 CMP Scientific,0 JNE @@6 @@5: PUSH ffGeneral PUSH 15 PUSH 0 MOV EAX,[FormatSettings] PUSH EAX MOV EAX,Buffer MOV EDX,EDI MOV ECX,EBX CALL FloatToTextEx JMP @@Exit @@6: CMP FloatRec.Digits.Byte,0 JNE @@7 MOV ECX,2 CALL @@FindSection JE @@5 CMP ESI,Section JE @@7 CALL @@ScanSection @@7: CALL @@ApplyFormat JMP @@Exit // Find format section // In ECX = Section index // Out ESI = Section offset // ZF = 1 if section is empty @@FindSection: MOV ESI,Format JECXZ @@fs2 @@fs1: LODSB CMP AL,"'" JE @@fs4 CMP AL,'"' JE @@fs4 OR AL,AL JE @@fs2 CMP AL,';' JNE @@fs1 LOOP @@fs1 MOV AL,byte ptr [ESI] OR AL,AL JE @@fs2 CMP AL,';' JNE @@fs3 @@fs2: MOV ESI,Format MOV AL,byte ptr [ESI] OR AL,AL JE @@fs3 CMP AL,';' @@fs3: RET @@fs4: MOV AH,AL @@fs5: LODSB CMP AL,AH JE @@fs1 OR AL,AL JNE @@fs5 JMP @@fs2 // Scan format section @@ScanSection: PUSH EBX MOV Section,ESI MOV EBX,32767 XOR ECX,ECX XOR EDX,EDX MOV DecimalIndex,-1 MOV ThousandSep,DL MOV Scientific,DL @@ss1: LODSB @@ss2: CMP AL,'#' JE @@ss10 CMP AL,'0' JE @@ss11 CMP AL,'.' JE @@ss13 CMP AL,',' JE @@ss14 CMP AL,"'" JE @@ss15 CMP AL,'"' JE @@ss15 CMP AL,'E' JE @@ss20 CMP AL,'e' JE @@ss20 CMP AL,';' JE @@ss30 OR AL,AL JNE @@ss1 JMP @@ss30 @@ss10: INC EDX JMP @@ss1 @@ss11: CMP EDX,EBX JGE @@ss12 MOV EBX,EDX @@ss12: INC EDX MOV ECX,EDX JMP @@ss1 @@ss13: CMP DecimalIndex,-1 JNE @@ss1 MOV DecimalIndex,EDX JMP @@ss1 @@ss14: MOV ThousandSep,1 JMP @@ss1 @@ss15: MOV AH,AL @@ss16: LODSB CMP AL,AH JE @@ss1 OR AL,AL JNE @@ss16 JMP @@ss30 @@ss20: LODSB CMP AL,'-' JE @@ss21 CMP AL,'+' JNE @@ss2 @@ss21: MOV Scientific,1 @@ss22: LODSB CMP AL,'0' JE @@ss22 JMP @@ss2 @@ss30: MOV DigitCount,EDX CMP DecimalIndex,-1 JNE @@ss31 MOV DecimalIndex,EDX @@ss31: MOV EAX,DecimalIndex SUB EAX,ECX JLE @@ss32 XOR EAX,EAX @@ss32: MOV LastDigit,EAX MOV EAX,DecimalIndex SUB EAX,EBX JGE @@ss33 XOR EAX,EAX @@ss33: MOV FirstDigit,EAX POP EBX RET // Apply format string @@ApplyFormat: CMP Scientific,0 JE @@af1 MOV EAX,DecimalIndex XOR EDX,EDX JMP @@af3 @@af1: MOVSX EAX,FloatRec.Exponent CMP EAX,DecimalIndex JG @@af2 MOV EAX,DecimalIndex @@af2: MOVSX EDX,FloatRec.Exponent SUB EDX,DecimalIndex @@af3: MOV DigitPlace,EAX MOV DigitDelta,EDX MOV ESI,Section MOV EDI,Buffer LEA EBX,FloatRec.Digits CMP FloatRec.Negative,0 JE @@af10 CMP ESI,Format JNE @@af10 MOV AL,'-' STOSB @@af10: LODSB CMP AL,'#' JE @@af20 CMP AL,'0' JE @@af20 CMP AL,'.' JE @@af10 CMP AL,',' JE @@af10 CMP AL,"'" JE @@af25 CMP AL,'"' JE @@af25 CMP AL,'E' JE @@af30 CMP AL,'e' JE @@af30 CMP AL,';' JE @@af40 OR AL,AL JE @@af40 @@af11: STOSB JMP @@af10 @@af20: CALL @@PutFmtDigit JMP @@af10 @@af25: MOV AH,AL @@af26: LODSB CMP AL,AH JE @@af10 OR AL,AL JE @@af40 STOSB JMP @@af26 @@af30: MOV AH,[ESI] CMP AH,'+' JE @@af31 CMP AH,'-' JNE @@af11 XOR AH,AH @@af31: MOV ECX,-1 @@af32: INC ECX INC ESI CMP [ESI].Byte,'0' JE @@af32 CMP ECX,4 JB @@af33 MOV ECX,4 @@af33: PUSH EBX MOV BL,FloatRec.Digits.Byte MOVSX EDX,FloatRec.Exponent SUB EDX,DecimalIndex CALL PutExponent POP EBX JMP @@af10 @@af40: MOV EAX,EDI SUB EAX,Buffer RET // Store formatted digit @@PutFmtDigit: CMP DigitDelta,0 JE @@fd3 JL @@fd2 @@fd1: CALL @@fd3 DEC DigitDelta JNE @@fd1 JMP @@fd3 @@fd2: INC DigitDelta MOV EAX,DigitPlace CMP EAX,FirstDigit JLE @@fd4 JMP @@fd7 @@fd3: MOV AL,[EBX] INC EBX OR AL,AL JNE @@fd5 DEC EBX MOV EAX,DigitPlace CMP EAX,LastDigit JLE @@fd7 @@fd4: MOV AL,'0' @@fd5: CMP DigitPlace,0 JNE @@fd6 MOV AH,AL MOV AL,DecimalSep STOSW JMP @@fd7 @@fd6: STOSB CMP ThousandSep,0 JE @@fd7 MOV EAX,DigitPlace CMP EAX,1 JLE @@fd7 MOV DL,3 DIV DL CMP AH,1 JNE @@fd7 MOV AL,ThousandsSep TEST AL,AL JZ @@fd7 STOSB @@fd7: DEC DigitPlace RET @@exit: POP EBX POP ESI POP EDI end; const // 8087 status word masks mIE = $0001; mDE = $0002; mZE = $0004; mOE = $0008; mUE = $0010; mPE = $0020; mC0 = $0100; mC1 = $0200; mC2 = $0400; mC3 = $4000; procedure FloatToDecimal(var Result: TFloatRec; const Value; ValueType: TFloatValue; Precision, Decimals: Integer); var StatWord: Word; Exponent: Integer; Temp: Double; BCDValue: Extended; SaveGOT: Pointer; asm PUSH EDI PUSH ESI PUSH EBX MOV EBX,EAX MOV ESI,EDX {$IFDEF PIC} PUSH ECX CALL GetGOT POP ECX MOV SaveGOT,EAX {$ELSE} MOV SaveGOT,0 {$ENDIF} CMP CL,fvExtended JE @@1 CALL @@CurrToDecimal JMP @@Exit @@1: CALL @@ExtToDecimal JMP @@Exit // Convert Extended to decimal @@ExtToDecimal: MOV AX,[ESI].Word[8] MOV EDX,EAX AND EAX,7FFFH JE @@ed1 CMP EAX,7FFFH JNE @@ed10 // check for special values (INF, NAN) TEST [ESI].Word[6],8000H JZ @@ed2 // any significand bit set = NAN // all significand bits clear = INF CMP dword ptr [ESI], 0 JNZ @@ed0 CMP dword ptr [ESI+4], 80000000H JZ @@ed2 @@ed0: INC EAX @@ed1: XOR EDX,EDX @@ed2: MOV [EBX].TFloatRec.Digits.Byte,0 JMP @@ed31 @@ed10: FLD TBYTE PTR [ESI] SUB EAX,3FFFH IMUL EAX,19728 SAR EAX,16 INC EAX MOV Exponent,EAX MOV EAX,18 SUB EAX,Exponent FABS PUSH EBX MOV EBX,SaveGOT CALL FPower10 POP EBX FRNDINT MOV EDI,SaveGOT FLD [EDI].FCon1E18 FCOMP FSTSW StatWord FWAIT TEST StatWord,mC0+mC3 JE @@ed11 FIDIV [EDI].DCon10 INC Exponent @@ed11: FBSTP BCDValue LEA EDI,[EBX].TFloatRec.Digits MOV EDX,9 FWAIT @@ed12: MOV AL,BCDValue[EDX-1].Byte MOV AH,AL SHR AL,4 AND AH,0FH ADD AX,'00' STOSW DEC EDX JNE @@ed12 XOR AL,AL STOSB @@ed20: MOV EDI,Exponent ADD EDI,Decimals JNS @@ed21 XOR EAX,EAX JMP @@ed1 @@ed21: CMP EDI,Precision JB @@ed22 MOV EDI,Precision @@ed22: CMP EDI,18 JAE @@ed26 CMP [EBX].TFloatRec.Digits.Byte[EDI],'5' JB @@ed25 @@ed23: MOV [EBX].TFloatRec.Digits.Byte[EDI],0 DEC EDI JS @@ed24 INC [EBX].TFloatRec.Digits.Byte[EDI] CMP [EBX].TFloatRec.Digits.Byte[EDI],'9' JA @@ed23 JMP @@ed30 @@ed24: MOV [EBX].TFloatRec.Digits.Word,'1' INC Exponent JMP @@ed30 @@ed26: MOV EDI,18 @@ed25: MOV [EBX].TFloatRec.Digits.Byte[EDI],0 DEC EDI JS @@ed32 CMP [EBX].TFloatRec.Digits.Byte[EDI],'0' JE @@ed25 @@ed30: MOV DX,[ESI].Word[8] @@ed30a: MOV EAX,Exponent @@ed31: SHR DX,15 MOV [EBX].TFloatRec.Exponent,AX MOV [EBX].TFloatRec.Negative,DL RET @@ed32: XOR EDX,EDX JMP @@ed30a @@DecimalTable: DD 10 DD 100 DD 1000 DD 10000 // Convert Currency to decimal @@CurrToDecimal: MOV EAX,[ESI].Integer[0] MOV EDX,[ESI].Integer[4] MOV ECX,EAX OR ECX,EDX JE @@cd20 OR EDX,EDX JNS @@cd1 NEG EDX NEG EAX SBB EDX,0 @@cd1: XOR ECX,ECX MOV EDI,Decimals OR EDI,EDI JGE @@cd2 XOR EDI,EDI @@cd2: CMP EDI,4 JL @@cd4 MOV EDI,4 @@cd3: INC ECX SUB EAX,Const1E18Lo SBB EDX,Const1E18Hi JNC @@cd3 DEC ECX ADD EAX,Const1E18Lo ADC EDX,Const1E18Hi @@cd4: MOV Temp.Integer[0],EAX MOV Temp.Integer[4],EDX FILD Temp MOV EDX,EDI MOV EAX,4 SUB EAX,EDX JE @@cd5 MOV EDI,SaveGOT FIDIV @@DecimalTable.Integer[EDI+EAX*4-4] @@cd5: FBSTP BCDValue LEA EDI,[EBX].TFloatRec.Digits FWAIT OR ECX,ECX JNE @@cd11 MOV ECX,9 @@cd10: MOV AL,BCDValue[ECX-1].Byte MOV AH,AL SHR AL,4 JNE @@cd13 MOV AL,AH AND AL,0FH JNE @@cd14 DEC ECX JNE @@cd10 JMP @@cd20 @@cd11: MOV AL,CL ADD AL,'0' STOSB MOV ECX,9 @@cd12: MOV AL,BCDValue[ECX-1].Byte MOV AH,AL SHR AL,4 @@cd13: ADD AL,'0' STOSB MOV AL,AH AND AL,0FH @@cd14: ADD AL,'0' STOSB DEC ECX JNE @@cd12 MOV EAX,EDI LEA ECX,[EBX].TFloatRec.Digits[EDX] SUB EAX,ECX @@cd15: MOV BYTE PTR [EDI],0 DEC EDI CMP BYTE PTR [EDI],'0' JE @@cd15 MOV EDX,[ESI].Integer[4] SHR EDX,31 JMP @@cd21 @@cd20: XOR EAX,EAX XOR EDX,EDX MOV [EBX].TFloatRec.Digits.Byte[0],AL @@cd21: MOV [EBX].TFloatRec.Exponent,AX MOV [EBX].TFloatRec.Negative,DL RET @@Exit: POP EBX POP ESI POP EDI end; function TextToFloat(Buffer: PChar; var Value; ValueType: TFloatValue): Boolean; const // 8087 control word // Infinity control = 1 Affine // Rounding Control = 0 Round to nearest or even // Precision Control = 3 64 bits // All interrupts masked CWNear: Word = $133F; var Temp: Integer; CtrlWord: Word; DecimalSep: Char; SaveGOT: Integer; asm PUSH EDI PUSH ESI PUSH EBX MOV ESI,EAX MOV EDI,EDX {$IFDEF PIC} PUSH ECX CALL GetGOT POP EBX MOV SaveGOT,EAX MOV ECX,[EAX].OFFSET DecimalSeparator MOV CL,[ECX].Byte MOV DecimalSep,CL {$ELSE} MOV SaveGOT,0 MOV AL,DecimalSeparator MOV DecimalSep,AL MOV EBX,ECX {$ENDIF} FSTCW CtrlWord FCLEX {$IFDEF PIC} FLDCW [EAX].CWNear {$ELSE} FLDCW CWNear {$ENDIF} FLDZ CALL @@SkipBlanks MOV BH, byte ptr [ESI] CMP BH,'+' JE @@1 CMP BH,'-' JNE @@2 @@1: INC ESI @@2: MOV ECX,ESI CALL @@GetDigitStr XOR EDX,EDX MOV AL,[ESI] CMP AL,DecimalSep JNE @@3 INC ESI CALL @@GetDigitStr NEG EDX @@3: CMP ECX,ESI JE @@9 MOV AL, byte ptr [ESI] AND AL,0DFH CMP AL,'E' JNE @@4 INC ESI PUSH EDX CALL @@GetExponent POP EAX ADD EDX,EAX @@4: CALL @@SkipBlanks CMP BYTE PTR [ESI],0 JNE @@9 MOV EAX,EDX CMP BL,fvCurrency JNE @@5 ADD EAX,4 @@5: PUSH EBX MOV EBX,SaveGOT CALL FPower10 POP EBX CMP BH,'-' JNE @@6 FCHS @@6: CMP BL,fvExtended JE @@7 FISTP QWORD PTR [EDI] JMP @@8 @@7: FSTP TBYTE PTR [EDI] @@8: FSTSW AX TEST AX,mIE+mOE JNE @@10 MOV AL,1 JMP @@11 @@9: FSTP ST(0) @@10: XOR EAX,EAX @@11: FCLEX FLDCW CtrlWord FWAIT JMP @@Exit @@SkipBlanks: @@21: LODSB OR AL,AL JE @@22 CMP AL,' ' JE @@21 @@22: DEC ESI RET // Process string of digits // Out EDX = Digit count @@GetDigitStr: XOR EAX,EAX XOR EDX,EDX @@31: LODSB SUB AL,'0'+10 ADD AL,10 JNC @@32 {$IFDEF PIC} XCHG SaveGOT,EBX FIMUL [EBX].DCon10 XCHG SaveGOT,EBX {$ELSE} FIMUL DCon10 {$ENDIF} MOV Temp,EAX FIADD Temp INC EDX JMP @@31 @@32: DEC ESI RET // Get exponent // Out EDX = Exponent (-4999..4999) @@GetExponent: XOR EAX,EAX XOR EDX,EDX MOV CL, byte ptr [ESI] CMP CL,'+' JE @@41 CMP CL,'-' JNE @@42 @@41: INC ESI @@42: MOV AL, byte ptr [ESI] SUB AL,'0'+10 ADD AL,10 JNC @@43 INC ESI IMUL EDX,10 ADD EDX,EAX CMP EDX,500 JB @@42 @@43: CMP CL,'-' JNE @@44 NEG EDX @@44: RET @@Exit: POP EBX POP ESI POP EDI end; function TextToFloat(Buffer: PChar; var Value; ValueType: TFloatValue; const FormatSettings: TFormatSettings): Boolean; const // 8087 control word // Infinity control = 1 Affine // Rounding Control = 0 Round to nearest or even // Precision Control = 3 64 bits // All interrupts masked CWNear: Word = $133F; var Temp: Integer; CtrlWord: Word; DecimalSep: Char; SaveGOT: Integer; asm PUSH EDI PUSH ESI PUSH EBX MOV ESI,EAX MOV EDI,EDX {$IFDEF PIC} PUSH ECX CALL GetGOT POP EBX MOV SaveGOT,EAX {$ELSE} MOV SaveGOT,0 MOV EBX,ECX {$ENDIF} MOV EAX,FormatSettings MOV AL,[EAX].TFormatSettings.DecimalSeparator MOV DecimalSep,AL FSTCW CtrlWord FCLEX {$IFDEF PIC} FLDCW [EAX].CWNear {$ELSE} FLDCW CWNear {$ENDIF} FLDZ CALL @@SkipBlanks MOV BH, byte ptr [ESI] CMP BH,'+' JE @@1 CMP BH,'-' JNE @@2 @@1: INC ESI @@2: MOV ECX,ESI CALL @@GetDigitStr XOR EDX,EDX MOV AL,[ESI] CMP AL,DecimalSep JNE @@3 INC ESI CALL @@GetDigitStr NEG EDX @@3: CMP ECX,ESI JE @@9 MOV AL, byte ptr [ESI] AND AL,0DFH CMP AL,'E' JNE @@4 INC ESI PUSH EDX CALL @@GetExponent POP EAX ADD EDX,EAX @@4: CALL @@SkipBlanks CMP BYTE PTR [ESI],0 JNE @@9 MOV EAX,EDX CMP BL,fvCurrency JNE @@5 ADD EAX,4 @@5: PUSH EBX MOV EBX,SaveGOT CALL FPower10 POP EBX CMP BH,'-' JNE @@6 FCHS @@6: CMP BL,fvExtended JE @@7 FISTP QWORD PTR [EDI] JMP @@8 @@7: FSTP TBYTE PTR [EDI] @@8: FSTSW AX TEST AX,mIE+mOE JNE @@10 MOV AL,1 JMP @@11 @@9: FSTP ST(0) @@10: XOR EAX,EAX @@11: FCLEX FLDCW CtrlWord FWAIT JMP @@Exit @@SkipBlanks: @@21: LODSB OR AL,AL JE @@22 CMP AL,' ' JE @@21 @@22: DEC ESI RET // Process string of digits // Out EDX = Digit count @@GetDigitStr: XOR EAX,EAX XOR EDX,EDX @@31: LODSB SUB AL,'0'+10 ADD AL,10 JNC @@32 {$IFDEF PIC} XCHG SaveGOT,EBX FIMUL [EBX].DCon10 XCHG SaveGOT,EBX {$ELSE} FIMUL DCon10 {$ENDIF} MOV Temp,EAX FIADD Temp INC EDX JMP @@31 @@32: DEC ESI RET // Get exponent // Out EDX = Exponent (-4999..4999) @@GetExponent: XOR EAX,EAX XOR EDX,EDX MOV CL, byte ptr [ESI] CMP CL,'+' JE @@41 CMP CL,'-' JNE @@42 @@41: INC ESI @@42: MOV AL, byte ptr [ESI] SUB AL,'0'+10 ADD AL,10 JNC @@43 INC ESI IMUL EDX,10 ADD EDX,EAX CMP EDX,500 JB @@42 @@43: CMP CL,'-' JNE @@44 NEG EDX @@44: RET @@Exit: POP EBX POP ESI POP EDI end; function FloatToStr(Value: Extended): string; var Buffer: array[0..63] of Char; begin SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended, ffGeneral, 15, 0)); end; function FloatToStr(Value: Extended; const FormatSettings: TFormatSettings): string; var Buffer: array[0..63] of Char; begin SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended, ffGeneral, 15, 0, FormatSettings)); end; function CurrToStr(Value: Currency): string; var Buffer: array[0..63] of Char; begin SetString(Result, Buffer, FloatToText(Buffer, Value, fvCurrency, ffGeneral, 0, 0)); end; function CurrToStr(Value: Currency; const FormatSettings: TFormatSettings): string; var Buffer: array[0..63] of Char; begin SetString(Result, Buffer, FloatToText(Buffer, Value, fvCurrency, ffGeneral, 0, 0, FormatSettings)); end; function TryFloatToCurr(const Value: Extended; out AResult: Currency): Boolean; begin Result := (Value >= MinCurrency) and (Value <= MaxCurrency); if Result then AResult := Value; end; function FloatToCurr(const Value: Extended): Currency; begin if not TryFloatToCurr(Value, Result) then ConvertErrorFmt(SInvalidCurrency, [FloatToStr(Value)]); end; function FloatToStrF(Value: Extended; Format: TFloatFormat; Precision, Digits: Integer): string; var Buffer: array[0..63] of Char; begin SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended, Format, Precision, Digits)); end; function FloatToStrF(Value: Extended; Format: TFloatFormat; Precision, Digits: Integer; const FormatSettings: TFormatSettings): string; var Buffer: array[0..63] of Char; begin SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended, Format, Precision, Digits, FormatSettings)); end; function CurrToStrF(Value: Currency; Format: TFloatFormat; Digits: Integer): string; var Buffer: array[0..63] of Char; begin SetString(Result, Buffer, FloatToText(Buffer, Value, fvCurrency, Format, 0, Digits)); end; function CurrToStrF(Value: Currency; Format: TFloatFormat; Digits: Integer; const FormatSettings: TFormatSettings): string; var Buffer: array[0..63] of Char; begin SetString(Result, Buffer, FloatToText(Buffer, Value, fvCurrency, Format, 0, Digits, FormatSettings)); end; function FormatFloat(const Format: string; Value: Extended): string; var Buffer: array[0..255] of Char; begin if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong); SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvExtended, PChar(Format))); end; function FormatFloat(const Format: string; Value: Extended; const FormatSettings: TFormatSettings): string; var Buffer: array[0..255] of Char; begin if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong); SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvExtended, PChar(Format), FormatSettings)); end; function FormatCurr(const Format: string; Value: Currency): string; var Buffer: array[0..255] of Char; begin if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong); SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvCurrency, PChar(Format))); end; function FormatCurr(const Format: string; Value: Currency; const FormatSettings: TFormatSettings): string; var Buffer: array[0..255] of Char; begin if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong); SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvCurrency, PChar(Format), FormatSettings)); end; function StrToFloat(const S: string): Extended; begin if not TextToFloat(PChar(S), Result, fvExtended) then ConvertErrorFmt(SInvalidFloat, [S]); end; function StrToFloat(const S: string; const FormatSettings: TFormatSettings): Extended; begin if not TextToFloat(PChar(S), Result, fvExtended, FormatSettings) then ConvertErrorFmt(SInvalidFloat, [S]); end; function StrToFloatDef(const S: string; const Default: Extended): Extended; begin if not TextToFloat(PChar(S), Result, fvExtended) then Result := Default; end; function StrToFloatDef(const S: string; const Default: Extended; const FormatSettings: TFormatSettings): Extended; begin if not TextToFloat(PChar(S), Result, fvExtended, FormatSettings) then Result := Default; end; function TryStrToFloat(const S: string; out Value: Extended): Boolean; begin Result := TextToFloat(PChar(S), Value, fvExtended); end; function TryStrToFloat(const S: string; out Value: Extended; const FormatSettings: TFormatSettings): Boolean; begin Result := TextToFloat(PChar(S), Value, fvExtended, FormatSettings); end; function TryStrToFloat(const S: string; out Value: Double): Boolean; var LValue: Extended; begin Result := TextToFloat(PChar(S), LValue, fvExtended); if Result then Value := LValue; end; function TryStrToFloat(const S: string; out Value: Double; const FormatSettings: TFormatSettings): Boolean; var LValue: Extended; begin Result := TextToFloat(PChar(S), LValue, fvExtended, FormatSettings); if Result then Value := LValue; end; function TryStrToFloat(const S: string; out Value: Single): Boolean; var LValue: Extended; begin Result := TextToFloat(PChar(S), LValue, fvExtended); if Result then Value := LValue; end; function TryStrToFloat(const S: string; out Value: Single; const FormatSettings: TFormatSettings): Boolean; var LValue: Extended; begin Result := TextToFloat(PChar(S), LValue, fvExtended, FormatSettings); if Result then Value := LValue; end; function StrToCurr(const S: string): Currency; begin if not TextToFloat(PChar(S), Result, fvCurrency) then ConvertErrorFmt(SInvalidFloat, [S]); end; function StrToCurr(const S: string; const FormatSettings: TFormatSettings): Currency; begin if not TextToFloat(PChar(S), Result, fvCurrency, FormatSettings) then ConvertErrorFmt(SInvalidFloat, [S]); end; function StrToCurrDef(const S: string; const Default: Currency): Currency; begin if not TextToFloat(PChar(S), Result, fvCurrency) then Result := Default; end; function StrToCurrDef(const S: string; const Default: Currency; const FormatSettings: TFormatSettings): Currency; begin if not TextToFloat(PChar(S), Result, fvCurrency, FormatSettings) then Result := Default; end; function TryStrToCurr(const S: string; out Value: Currency): Boolean; begin Result := TextToFloat(PChar(S), Value, fvCurrency); end; function TryStrToCurr(const S: string; out Value: Currency; const FormatSettings: TFormatSettings): Boolean; begin Result := TextToFloat(PChar(S), Value, fvCurrency, FormatSettings); end; { Date/time support routines } const FMSecsPerDay: Single = MSecsPerDay; IMSecsPerDay: Integer = MSecsPerDay; function DateTimeToTimeStamp(DateTime: TDateTime): TTimeStamp; asm PUSH EBX {$IFDEF PIC} PUSH EAX CALL GetGOT MOV EBX,EAX POP EAX {$ELSE} XOR EBX,EBX {$ENDIF} MOV ECX,EAX FLD DateTime FMUL [EBX].FMSecsPerDay SUB ESP,8 FISTP QWORD PTR [ESP] FWAIT POP EAX POP EDX OR EDX,EDX JNS @@1 NEG EDX NEG EAX SBB EDX,0 DIV [EBX].IMSecsPerDay NEG EAX JMP @@2 @@1: DIV [EBX].IMSecsPerDay @@2: ADD EAX,DateDelta MOV [ECX].TTimeStamp.Time,EDX MOV [ECX].TTimeStamp.Date,EAX POP EBX end; procedure ValidateTimeStamp(const TimeStamp: TTimeStamp); begin if (TimeStamp.Time < 0) or (TimeStamp.Date <= 0) then ConvertErrorFmt(SInvalidTimeStamp, [TimeStamp.Date, TimeStamp.Time]); end; function TimeStampToDateTime(const TimeStamp: TTimeStamp): TDateTime; asm PUSH EBX {$IFDEF PIC} PUSH EAX CALL GetGOT MOV EBX,EAX POP EAX {$ELSE} XOR EBX,EBX {$ENDIF} PUSH EAX CALL ValidateTimeStamp POP EAX MOV ECX,[EAX].TTimeStamp.Time MOV EAX,[EAX].TTimeStamp.Date SUB EAX,DateDelta IMUL [EBX].IMSecsPerDay OR EDX,EDX JNS @@1 SUB EAX,ECX SBB EDX,0 JMP @@2 @@1: ADD EAX,ECX ADC EDX,0 @@2: PUSH EDX PUSH EAX FILD QWORD PTR [ESP] FDIV [EBX].FMSecsPerDay ADD ESP,8 POP EBX end; function MSecsToTimeStamp(MSecs: Comp): TTimeStamp; asm PUSH EBX {$IFDEF PIC} PUSH EAX CALL GetGOT MOV EBX,EAX POP EAX {$ELSE} XOR EBX,EBX {$ENDIF} MOV ECX,EAX MOV EAX,MSecs.Integer[0] MOV EDX,MSecs.Integer[4] DIV [EBX].IMSecsPerDay MOV [ECX].TTimeStamp.Time,EDX MOV [ECX].TTimeStamp.Date,EAX POP EBX end; function TimeStampToMSecs(const TimeStamp: TTimeStamp): Comp; asm PUSH EBX {$IFDEF PIC} PUSH EAX CALL GetGOT MOV EBX,EAX POP EAX {$ELSE} XOR EBX,EBX {$ENDIF} PUSH EAX CALL ValidateTimeStamp POP EAX FILD [EAX].TTimeStamp.Date FMUL [EBX].FMSecsPerDay FIADD [EAX].TTimeStamp.Time POP EBX end; { Time encoding and decoding } function TryEncodeTime(Hour, Min, Sec, MSec: Word; out Time: TDateTime): Boolean; begin Result := False; if (Hour < HoursPerDay) and (Min < MinsPerHour) and (Sec < SecsPerMin) and (MSec < MSecsPerSec) then begin Time := (Hour * (MinsPerHour * SecsPerMin * MSecsPerSec) + Min * (SecsPerMin * MSecsPerSec) + Sec * MSecsPerSec + MSec) / MSecsPerDay; Result := True; end; end; function EncodeTime(Hour, Min, Sec, MSec: Word): TDateTime; begin if not TryEncodeTime(Hour, Min, Sec, MSec, Result) then ConvertError(STimeEncodeError); end; procedure DecodeTime(const DateTime: TDateTime; var Hour, Min, Sec, MSec: Word); var MinCount, MSecCount: Word; begin DivMod(DateTimeToTimeStamp(DateTime).Time, SecsPerMin * MSecsPerSec, MinCount, MSecCount); DivMod(MinCount, MinsPerHour, Hour, Min); DivMod(MSecCount, MSecsPerSec, Sec, MSec); end; { Date encoding and decoding } function IsLeapYear(Year: Word): Boolean; begin Result := (Year mod 4 = 0) and ((Year mod 100 <> 0) or (Year mod 400 = 0)); end; function TryEncodeDate(Year, Month, Day: Word; out Date: TDateTime): Boolean; var I: Integer; DayTable: PDayTable; begin Result := False; DayTable := @MonthDays[IsLeapYear(Year)]; if (Year >= 1) and (Year <= 9999) and (Month >= 1) and (Month <= 12) and (Day >= 1) and (Day <= DayTable^[Month]) then begin for I := 1 to Month - 1 do Inc(Day, DayTable^[I]); I := Year - 1; Date := I * 365 + I div 4 - I div 100 + I div 400 + Day - DateDelta; Result := True; end; end; function EncodeDate(Year, Month, Day: Word): TDateTime; begin if not TryEncodeDate(Year, Month, Day, Result) then ConvertError(SDateEncodeError); end; function DecodeDateFully(const DateTime: TDateTime; var Year, Month, Day, DOW: Word): Boolean; const D1 = 365; D4 = D1 * 4 + 1; D100 = D4 * 25 - 1; D400 = D100 * 4 + 1; var Y, M, D, I: Word; T: Integer; DayTable: PDayTable; begin T := DateTimeToTimeStamp(DateTime).Date; if T <= 0 then begin Year := 0; Month := 0; Day := 0; DOW := 0; Result := False; end else begin DOW := T mod 7 + 1; Dec(T); Y := 1; while T >= D400 do begin Dec(T, D400); Inc(Y, 400); end; DivMod(T, D100, I, D); if I = 4 then begin Dec(I); Inc(D, D100); end; Inc(Y, I * 100); DivMod(D, D4, I, D); Inc(Y, I * 4); DivMod(D, D1, I, D); if I = 4 then begin Dec(I); Inc(D, D1); end; Inc(Y, I); Result := IsLeapYear(Y); DayTable := @MonthDays[Result]; M := 1; while True do begin I := DayTable^[M]; if D < I then Break; Dec(D, I); Inc(M); end; Year := Y; Month := M; Day := D + 1; end; end; function InternalDecodeDate(const DateTime: TDateTime; var Year, Month, Day, DOW: Word): Boolean; begin Result := DecodeDateFully(DateTime, Year, Month, Day, DOW); Dec(DOW); end; procedure DecodeDate(const DateTime: TDateTime; var Year, Month, Day: Word); var Dummy: Word; begin DecodeDateFully(DateTime, Year, Month, Day, Dummy); end; {$IFDEF MSWINDOWS} procedure DateTimeToSystemTime(const DateTime: TDateTime; var SystemTime: TSystemTime); begin with SystemTime do begin DecodeDateFully(DateTime, wYear, wMonth, wDay, wDayOfWeek); Dec(wDayOfWeek); DecodeTime(DateTime, wHour, wMinute, wSecond, wMilliseconds); end; end; function SystemTimeToDateTime(const SystemTime: TSystemTime): TDateTime; begin with SystemTime do begin Result := EncodeDate(wYear, wMonth, wDay); if Result >= 0 then Result := Result + EncodeTime(wHour, wMinute, wSecond, wMilliSeconds) else Result := Result - EncodeTime(wHour, wMinute, wSecond, wMilliSeconds); end; end; {$ENDIF} function DayOfWeek(const DateTime: TDateTime): Word; begin Result := DateTimeToTimeStamp(DateTime).Date mod 7 + 1; end; function Date: TDateTime; {$IFDEF MSWINDOWS} var SystemTime: TSystemTime; begin GetLocalTime(SystemTime); with SystemTime do Result := EncodeDate(wYear, wMonth, wDay); end; {$ENDIF} {$IFDEF LINUX} var T: TTime_T; UT: TUnixTime; begin __time(@T); localtime_r(@T, UT); Result := EncodeDate(UT.tm_year + 1900, UT.tm_mon + 1, UT.tm_mday); end; {$ENDIF} function Time: TDateTime; {$IFDEF MSWINDOWS} var SystemTime: TSystemTime; begin GetLocalTime(SystemTime); with SystemTime do Result := EncodeTime(wHour, wMinute, wSecond, wMilliSeconds); end; {$ENDIF} {$IFDEF LINUX} var T: TTime_T; TV: TTimeVal; UT: TUnixTime; begin gettimeofday(TV, nil); T := TV.tv_sec; localtime_r(@T, UT); Result := EncodeTime(UT.tm_hour, UT.tm_min, UT.tm_sec, TV.tv_usec div 1000); end; {$ENDIF} function GetTime: TDateTime; begin Result := Time; end; function Now: TDateTime; {$IFDEF MSWINDOWS} var SystemTime: TSystemTime; begin GetLocalTime(SystemTime); with SystemTime do Result := EncodeDate(wYear, wMonth, wDay) + EncodeTime(wHour, wMinute, wSecond, wMilliseconds); end; {$ENDIF} {$IFDEF LINUX} var T: TTime_T; TV: TTimeVal; UT: TUnixTime; begin gettimeofday(TV, nil); T := TV.tv_sec; localtime_r(@T, UT); Result := EncodeDate(UT.tm_year + 1900, UT.tm_mon + 1, UT.tm_mday) + EncodeTime(UT.tm_hour, UT.tm_min, UT.tm_sec, TV.tv_usec div 1000); end; {$ENDIF} function IncMonth(const DateTime: TDateTime; NumberOfMonths: Integer): TDateTime; var Year, Month, Day: Word; begin DecodeDate(DateTime, Year, Month, Day); IncAMonth(Year, Month, Day, NumberOfMonths); Result := EncodeDate(Year, Month, Day); ReplaceTime(Result, DateTime); end; procedure IncAMonth(var Year, Month, Day: Word; NumberOfMonths: Integer = 1); var DayTable: PDayTable; Sign: Integer; begin if NumberOfMonths >= 0 then Sign := 1 else Sign := -1; Year := Year + (NumberOfMonths div 12); NumberOfMonths := NumberOfMonths mod 12; Inc(Month, NumberOfMonths); if Word(Month-1) > 11 then // if Month <= 0, word(Month-1) > 11) begin Inc(Year, Sign); Inc(Month, -12 * Sign); end; DayTable := @MonthDays[IsLeapYear(Year)]; if Day > DayTable^[Month] then Day := DayTable^[Month]; end; procedure ReplaceTime(var DateTime: TDateTime; const NewTime: TDateTime); begin DateTime := Trunc(DateTime); if DateTime >= 0 then DateTime := DateTime + Abs(Frac(NewTime)) else DateTime := DateTime - Abs(Frac(NewTime)); end; procedure ReplaceDate(var DateTime: TDateTime; const NewDate: TDateTime); var Temp: TDateTime; begin Temp := NewDate; ReplaceTime(Temp, DateTime); DateTime := Temp; end; function CurrentYear: Word; {$IFDEF MSWINDOWS} var SystemTime: TSystemTime; begin GetLocalTime(SystemTime); Result := SystemTime.wYear; end; {$ENDIF} {$IFDEF LINUX} var T: TTime_T; UT: TUnixTime; begin __time(@T); localtime_r(@T, UT); Result := UT.tm_year + 1900; end; {$ENDIF} { Date/time to string conversions } procedure DateTimeToString(var Result: string; const Format: string; DateTime: TDateTime); var BufPos, AppendLevel: Integer; Buffer: array[0..255] of Char; procedure AppendChars(P: PChar; Count: Integer); var N: Integer; begin N := SizeOf(Buffer) - BufPos; if N > Count then N := Count; if N <> 0 then Move(P[0], Buffer[BufPos], N); Inc(BufPos, N); end; procedure AppendString(const S: string); begin AppendChars(Pointer(S), Length(S)); end; procedure AppendNumber(Number, Digits: Integer); const Format: array[0..3] of Char = '%.*d'; var NumBuf: array[0..15] of Char; begin AppendChars(NumBuf, FormatBuf(NumBuf, SizeOf(NumBuf), Format, SizeOf(Format), [Digits, Number])); end; procedure AppendFormat(Format: PChar); var Starter, Token, LastToken: Char; DateDecoded, TimeDecoded, Use12HourClock, BetweenQuotes: Boolean; P: PChar; Count: Integer; Year, Month, Day, Hour, Min, Sec, MSec, H: Word; procedure GetCount; var P: PChar; begin P := Format; while Format^ = Starter do Inc(Format); Count := Format - P + 1; end; procedure GetDate; begin if not DateDecoded then begin DecodeDate(DateTime, Year, Month, Day); DateDecoded := True; end; end; procedure GetTime; begin if not TimeDecoded then begin DecodeTime(DateTime, Hour, Min, Sec, MSec); TimeDecoded := True; end; end; {$IFDEF MSWINDOWS} function ConvertEraString(const Count: Integer) : string; var FormatStr: string; SystemTime: TSystemTime; Buffer: array[Byte] of Char; P: PChar; begin Result := ''; with SystemTime do begin wYear := Year; wMonth := Month; wDay := Day; end; FormatStr := 'gg'; if GetDateFormat(GetThreadLocale, DATE_USE_ALT_CALENDAR, @SystemTime, PChar(FormatStr), Buffer, SizeOf(Buffer)) <> 0 then begin Result := Buffer; if Count = 1 then begin case SysLocale.PriLangID of LANG_JAPANESE: Result := Copy(Result, 1, CharToBytelen(Result, 1)); LANG_CHINESE: if (SysLocale.SubLangID = SUBLANG_CHINESE_TRADITIONAL) and (ByteToCharLen(Result, Length(Result)) = 4) then begin P := Buffer + CharToByteIndex(Result, 3) - 1; SetString(Result, P, CharToByteLen(P, 2)); end; end; end; end; end; function ConvertYearString(const Count: Integer): string; var FormatStr: string; SystemTime: TSystemTime; Buffer: array[Byte] of Char; begin Result := ''; with SystemTime do begin wYear := Year; wMonth := Month; wDay := Day; end; if Count <= 2 then FormatStr := 'yy' // avoid Win95 bug. else FormatStr := 'yyyy'; if GetDateFormat(GetThreadLocale, DATE_USE_ALT_CALENDAR, @SystemTime, PChar(FormatStr), Buffer, SizeOf(Buffer)) <> 0 then begin Result := Buffer; if (Count = 1) and (Result[1] = '0') then Result := Copy(Result, 2, Length(Result)-1); end; end; {$ENDIF} {$IFDEF LINUX} function FindEra(Date: Integer): Byte; var I : Byte; begin Result := 0; for I := 1 to EraCount do begin if (EraRanges[I].StartDate <= Date) and (EraRanges[I].EndDate >= Date) then begin Result := I; Exit; end; end; end; function ConvertEraString(const Count: Integer) : String; var I : Byte; begin Result := ''; I := FindEra(Trunc(DateTime)); if I > 0 then Result := EraNames[I]; end; function ConvertYearString(const Count: Integer) : String; var I : Byte; S : string; begin I := FindEra(Trunc(DateTime)); if I > 0 then S := IntToStr(Year - EraYearOffsets[I]) else S := IntToStr(Year); while Length(S) < Count do S := '0' + S; if Length(S) > Count then S := Copy(S, Length(S) - (Count - 1), Count); Result := S; end; {$ENDIF} begin if (Format <> nil) and (AppendLevel < 2) then begin Inc(AppendLevel); LastToken := ' '; DateDecoded := False; TimeDecoded := False; Use12HourClock := False; while Format^ <> #0 do begin Starter := Format^; if Starter in LeadBytes then begin AppendChars(Format, StrCharLength(Format)); Format := StrNextChar(Format); LastToken := ' '; Continue; end; Format := StrNextChar(Format); Token := Starter; if Token in ['a'..'z'] then Dec(Token, 32); if Token in ['A'..'Z'] then begin if (Token = 'M') and (LastToken = 'H') then Token := 'N'; LastToken := Token; end; case Token of 'Y': begin GetCount; GetDate; if Count <= 2 then AppendNumber(Year mod 100, 2) else AppendNumber(Year, 4); end; 'G': begin GetCount; GetDate; AppendString(ConvertEraString(Count)); end; 'E': begin GetCount; GetDate; AppendString(ConvertYearString(Count)); end; 'M': begin GetCount; GetDate; case Count of 1, 2: AppendNumber(Month, Count); 3: AppendString(ShortMonthNames[Month]); else AppendString(LongMonthNames[Month]); end; end; 'D': begin GetCount; case Count of 1, 2: begin GetDate; AppendNumber(Day, Count); end; 3: AppendString(ShortDayNames[DayOfWeek(DateTime)]); 4: AppendString(LongDayNames[DayOfWeek(DateTime)]); 5: AppendFormat(Pointer(ShortDateFormat)); else AppendFormat(Pointer(LongDateFormat)); end; end; 'H': begin GetCount; GetTime; BetweenQuotes := False; P := Format; while P^ <> #0 do begin if P^ in LeadBytes then begin P := StrNextChar(P); Continue; end; case P^ of 'A', 'a': if not BetweenQuotes then begin if ( (StrLIComp(P, 'AM/PM', 5) = 0) or (StrLIComp(P, 'A/P', 3) = 0) or (StrLIComp(P, 'AMPM', 4) = 0) ) then Use12HourClock := True; Break; end; 'H', 'h': Break; '''', '"': BetweenQuotes := not BetweenQuotes; end; Inc(P); end; H := Hour; if Use12HourClock then if H = 0 then H := 12 else if H > 12 then Dec(H, 12); if Count > 2 then Count := 2; AppendNumber(H, Count); end; 'N': begin GetCount; GetTime; if Count > 2 then Count := 2; AppendNumber(Min, Count); end; 'S': begin GetCount; GetTime; if Count > 2 then Count := 2; AppendNumber(Sec, Count); end; 'T': begin GetCount; if Count = 1 then AppendFormat(Pointer(ShortTimeFormat)) else AppendFormat(Pointer(LongTimeFormat)); end; 'Z': begin GetCount; GetTime; if Count > 3 then Count := 3; AppendNumber(MSec, Count); end; 'A': begin GetTime; P := Format - 1; if StrLIComp(P, 'AM/PM', 5) = 0 then begin if Hour >= 12 then Inc(P, 3); AppendChars(P, 2); Inc(Format, 4); Use12HourClock := TRUE; end else if StrLIComp(P, 'A/P', 3) = 0 then begin if Hour >= 12 then Inc(P, 2); AppendChars(P, 1); Inc(Format, 2); Use12HourClock := TRUE; end else if StrLIComp(P, 'AMPM', 4) = 0 then begin if Hour < 12 then AppendString(TimeAMString) else AppendString(TimePMString); Inc(Format, 3); Use12HourClock := TRUE; end else if StrLIComp(P, 'AAAA', 4) = 0 then begin GetDate; AppendString(LongDayNames[DayOfWeek(DateTime)]); Inc(Format, 3); end else if StrLIComp(P, 'AAA', 3) = 0 then begin GetDate; AppendString(ShortDayNames[DayOfWeek(DateTime)]); Inc(Format, 2); end else AppendChars(@Starter, 1); end; 'C': begin GetCount; AppendFormat(Pointer(ShortDateFormat)); GetTime; if (Hour <> 0) or (Min <> 0) or (Sec <> 0) then begin AppendChars(' ', 1); AppendFormat(Pointer(LongTimeFormat)); end; end; '/': if DateSeparator <> #0 then AppendChars(@DateSeparator, 1); ':': if TimeSeparator <> #0 then AppendChars(@TimeSeparator, 1); '''', '"': begin P := Format; while (Format^ <> #0) and (Format^ <> Starter) do begin if Format^ in LeadBytes then Format := StrNextChar(Format) else Inc(Format); end; AppendChars(P, Format - P); if Format^ <> #0 then Inc(Format); end; else AppendChars(@Starter, 1); end; end; Dec(AppendLevel); end; end; begin BufPos := 0; AppendLevel := 0; if Format <> '' then AppendFormat(Pointer(Format)) else AppendFormat('C'); SetString(Result, Buffer, BufPos); end; procedure DateTimeToString(var Result: string; const Format: string; DateTime: TDateTime; const FormatSettings: TFormatSettings); var BufPos, AppendLevel: Integer; Buffer: array[0..255] of Char; procedure AppendChars(P: PChar; Count: Integer); var N: Integer; begin N := SizeOf(Buffer) - BufPos; if N > Count then N := Count; if N <> 0 then Move(P[0], Buffer[BufPos], N); Inc(BufPos, N); end; procedure AppendString(const S: string); begin AppendChars(Pointer(S), Length(S)); end; procedure AppendNumber(Number, Digits: Integer); const Format: array[0..3] of Char = '%.*d'; var NumBuf: array[0..15] of Char; begin AppendChars(NumBuf, FormatBuf(NumBuf, SizeOf(NumBuf), Format, SizeOf(Format), [Digits, Number])); end; procedure AppendFormat(Format: PChar); var Starter, Token, LastToken: Char; DateDecoded, TimeDecoded, Use12HourClock, BetweenQuotes: Boolean; P: PChar; Count: Integer; Year, Month, Day, Hour, Min, Sec, MSec, H: Word; procedure GetCount; var P: PChar; begin P := Format; while Format^ = Starter do Inc(Format); Count := Format - P + 1; end; procedure GetDate; begin if not DateDecoded then begin DecodeDate(DateTime, Year, Month, Day); DateDecoded := True; end; end; procedure GetTime; begin if not TimeDecoded then begin DecodeTime(DateTime, Hour, Min, Sec, MSec); TimeDecoded := True; end; end; {$IFDEF MSWINDOWS} function ConvertEraString(const Count: Integer) : string; var FormatStr: string; SystemTime: TSystemTime; Buffer: array[Byte] of Char; P: PChar; begin Result := ''; with SystemTime do begin wYear := Year; wMonth := Month; wDay := Day; end; FormatStr := 'gg'; if GetDateFormat(GetThreadLocale, DATE_USE_ALT_CALENDAR, @SystemTime, PChar(FormatStr), Buffer, SizeOf(Buffer)) <> 0 then begin Result := Buffer; if Count = 1 then begin case SysLocale.PriLangID of LANG_JAPANESE: Result := Copy(Result, 1, CharToBytelen(Result, 1)); LANG_CHINESE: if (SysLocale.SubLangID = SUBLANG_CHINESE_TRADITIONAL) and (ByteToCharLen(Result, Length(Result)) = 4) then begin P := Buffer + CharToByteIndex(Result, 3) - 1; SetString(Result, P, CharToByteLen(P, 2)); end; end; end; end; end; function ConvertYearString(const Count: Integer): string; var FormatStr: string; SystemTime: TSystemTime; Buffer: array[Byte] of Char; begin Result := ''; with SystemTime do begin wYear := Year; wMonth := Month; wDay := Day; end; if Count <= 2 then FormatStr := 'yy' // avoid Win95 bug. else FormatStr := 'yyyy'; if GetDateFormat(GetThreadLocale, DATE_USE_ALT_CALENDAR, @SystemTime, PChar(FormatStr), Buffer, SizeOf(Buffer)) <> 0 then begin Result := Buffer; if (Count = 1) and (Result[1] = '0') then Result := Copy(Result, 2, Length(Result)-1); end; end; {$ENDIF} {$IFDEF LINUX} function FindEra(Date: Integer): Byte; var I : Byte; begin Result := 0; for I := 1 to EraCount do begin if (EraRanges[I].StartDate <= Date) and (EraRanges[I].EndDate >= Date) then begin Result := I; Exit; end; end; end; function ConvertEraString(const Count: Integer) : String; var I : Byte; begin Result := ''; I := FindEra(Trunc(DateTime)); if I > 0 then Result := EraNames[I]; end; function ConvertYearString(const Count: Integer) : String; var I : Byte; S : string; begin I := FindEra(Trunc(DateTime)); if I > 0 then S := IntToStr(Year - EraYearOffsets[I]) else S := IntToStr(Year); while Length(S) < Count do S := '0' + S; if Length(S) > Count then S := Copy(S, Length(S) - (Count - 1), Count); Result := S; end; {$ENDIF} begin if (Format <> nil) and (AppendLevel < 2) then begin Inc(AppendLevel); LastToken := ' '; DateDecoded := False; TimeDecoded := False; Use12HourClock := False; while Format^ <> #0 do begin Starter := Format^; if Starter in LeadBytes then begin AppendChars(Format, StrCharLength(Format)); Format := StrNextChar(Format); LastToken := ' '; Continue; end; Format := StrNextChar(Format); Token := Starter; if Token in ['a'..'z'] then Dec(Token, 32); if Token in ['A'..'Z'] then begin if (Token = 'M') and (LastToken = 'H') then Token := 'N'; LastToken := Token; end; case Token of 'Y': begin GetCount; GetDate; if Count <= 2 then AppendNumber(Year mod 100, 2) else AppendNumber(Year, 4); end; 'G': begin GetCount; GetDate; AppendString(ConvertEraString(Count)); end; 'E': begin GetCount; GetDate; AppendString(ConvertYearString(Count)); end; 'M': begin GetCount; GetDate; case Count of 1, 2: AppendNumber(Month, Count); 3: AppendString(FormatSettings.ShortMonthNames[Month]); else AppendString(FormatSettings.LongMonthNames[Month]); end; end; 'D': begin GetCount; case Count of 1, 2: begin GetDate; AppendNumber(Day, Count); end; 3: AppendString(FormatSettings.ShortDayNames[DayOfWeek(DateTime)]); 4: AppendString(FormatSettings.LongDayNames[DayOfWeek(DateTime)]); 5: AppendFormat(Pointer(FormatSettings.ShortDateFormat)); else AppendFormat(Pointer(FormatSettings.LongDateFormat)); end; end; 'H': begin GetCount; GetTime; BetweenQuotes := False; P := Format; while P^ <> #0 do begin if P^ in LeadBytes then begin P := StrNextChar(P); Continue; end; case P^ of 'A', 'a': if not BetweenQuotes then begin if ( (StrLIComp(P, 'AM/PM', 5) = 0) or (StrLIComp(P, 'A/P', 3) = 0) or (StrLIComp(P, 'AMPM', 4) = 0) ) then Use12HourClock := True; Break; end; 'H', 'h': Break; '''', '"': BetweenQuotes := not BetweenQuotes; end; Inc(P); end; H := Hour; if Use12HourClock then if H = 0 then H := 12 else if H > 12 then Dec(H, 12); if Count > 2 then Count := 2; AppendNumber(H, Count); end; 'N': begin GetCount; GetTime; if Count > 2 then Count := 2; AppendNumber(Min, Count); end; 'S': begin GetCount; GetTime; if Count > 2 then Count := 2; AppendNumber(Sec, Count); end; 'T': begin GetCount; if Count = 1 then AppendFormat(Pointer(FormatSettings.ShortTimeFormat)) else AppendFormat(Pointer(FormatSettings.LongTimeFormat)); end; 'Z': begin GetCount; GetTime; if Count > 3 then Count := 3; AppendNumber(MSec, Count); end; 'A': begin GetTime; P := Format - 1; if StrLIComp(P, 'AM/PM', 5) = 0 then begin if Hour >= 12 then Inc(P, 3); AppendChars(P, 2); Inc(Format, 4); Use12HourClock := TRUE; end else if StrLIComp(P, 'A/P', 3) = 0 then begin if Hour >= 12 then Inc(P, 2); AppendChars(P, 1); Inc(Format, 2); Use12HourClock := TRUE; end else if StrLIComp(P, 'AMPM', 4) = 0 then begin if Hour < 12 then AppendString(FormatSettings.TimeAMString) else AppendString(FormatSettings.TimePMString); Inc(Format, 3); Use12HourClock := TRUE; end else if StrLIComp(P, 'AAAA', 4) = 0 then begin GetDate; AppendString(FormatSettings.LongDayNames[DayOfWeek(DateTime)]); Inc(Format, 3); end else if StrLIComp(P, 'AAA', 3) = 0 then begin GetDate; AppendString(FormatSettings.ShortDayNames[DayOfWeek(DateTime)]); Inc(Format, 2); end else AppendChars(@Starter, 1); end; 'C': begin GetCount; AppendFormat(Pointer(FormatSettings.ShortDateFormat)); GetTime; if (Hour <> 0) or (Min <> 0) or (Sec <> 0) then begin AppendChars(' ', 1); AppendFormat(Pointer(FormatSettings.LongTimeFormat)); end; end; '/': if DateSeparator <> #0 then AppendChars(@FormatSettings.DateSeparator, 1); ':': if TimeSeparator <> #0 then AppendChars(@FormatSettings.TimeSeparator, 1); '''', '"': begin P := Format; while (Format^ <> #0) and (Format^ <> Starter) do begin if Format^ in LeadBytes then Format := StrNextChar(Format) else Inc(Format); end; AppendChars(P, Format - P); if Format^ <> #0 then Inc(Format); end; else AppendChars(@Starter, 1); end; end; Dec(AppendLevel); end; end; begin BufPos := 0; AppendLevel := 0; if Format <> '' then AppendFormat(Pointer(Format)) else AppendFormat('C'); SetString(Result, Buffer, BufPos); end; function TryFloatToDateTime(const Value: Extended; out AResult: TDateTime): Boolean; begin Result := not ((Value < MinDateTime) or (Value >= Int(MaxDateTime) + 1.0)); if Result then AResult := Value; end; function FloatToDateTime(const Value: Extended): TDateTime; begin if not TryFloatToDateTime(Value, Result) then ConvertErrorFmt(SInvalidDateTimeFloat, [Value]); end; function DateToStr(const DateTime: TDateTime): string; begin DateTimeToString(Result, ShortDateFormat, DateTime); end; function DateToStr(const DateTime: TDateTime; const FormatSettings: TFormatSettings): string; begin DateTimeToString(Result, FormatSettings.ShortDateFormat, DateTime, FormatSettings); end; function TimeToStr(const DateTime: TDateTime): string; begin DateTimeToString(Result, LongTimeFormat, DateTime); end; function TimeToStr(const DateTime: TDateTime; const FormatSettings: TFormatSettings): string; begin DateTimeToString(Result, FormatSettings.LongTimeFormat, DateTime, FormatSettings); end; function DateTimeToStr(const DateTime: TDateTime): string; begin DateTimeToString(Result, '', DateTime); end; function DateTimeToStr(const DateTime: TDateTime; const FormatSettings: TFormatSettings): string; begin DateTimeToString(Result, '', DateTime, FormatSettings); end; function FormatDateTime(const Format: string; DateTime: TDateTime): string; begin DateTimeToString(Result, Format, DateTime); end; function FormatDateTime(const Format: string; DateTime: TDateTime; const FormatSettings: TFormatSettings): string; begin DateTimeToString(Result, Format, DateTime, FormatSettings); end; { String to date/time conversions } type TDateOrder = (doMDY, doDMY, doYMD); procedure ScanBlanks(const S: string; var Pos: Integer); var I: Integer; begin I := Pos; while (I <= Length(S)) and (S[I] = ' ') do Inc(I); Pos := I; end; function ScanNumber(const S: string; var Pos: Integer; var Number: Word; var CharCount: Byte): Boolean; var I: Integer; N: Word; begin Result := False; CharCount := 0; ScanBlanks(S, Pos); I := Pos; N := 0; while (I <= Length(S)) and (S[I] in ['0'..'9']) and (N < 1000) do begin N := N * 10 + (Ord(S[I]) - Ord('0')); Inc(I); end; if I > Pos then begin CharCount := I - Pos; Pos := I; Number := N; Result := True; end; end; function ScanString(const S: string; var Pos: Integer; const Symbol: string): Boolean; begin Result := False; if Symbol <> '' then begin ScanBlanks(S, Pos); if AnsiCompareText(Symbol, Copy(S, Pos, Length(Symbol))) = 0 then begin Inc(Pos, Length(Symbol)); Result := True; end; end; end; function ScanChar(const S: string; var Pos: Integer; Ch: Char): Boolean; begin Result := False; ScanBlanks(S, Pos); if (Pos <= Length(S)) and (S[Pos] = Ch) then begin Inc(Pos); Result := True; end; end; function GetDateOrder(const DateFormat: string): TDateOrder; var I: Integer; begin Result := doMDY; I := 1; while I <= Length(DateFormat) do begin case Chr(Ord(DateFormat[I]) and $DF) of 'E': Result := doYMD; 'Y': Result := doYMD; 'M': Result := doMDY; 'D': Result := doDMY; else Inc(I); Continue; end; Exit; end; Result := doMDY; end; procedure ScanToNumber(const S: string; var Pos: Integer); begin while (Pos <= Length(S)) and not (S[Pos] in ['0'..'9']) do begin if S[Pos] in LeadBytes then Pos := NextCharIndex(S, Pos) else Inc(Pos); end; end; function GetEraYearOffset(const Name: string): Integer; var I: Integer; begin Result := 0; for I := Low(EraNames) to High(EraNames) do begin if EraNames[I] = '' then Break; if AnsiStrPos(PChar(EraNames[I]), PChar(Name)) <> nil then begin Result := EraYearOffsets[I]; Exit; end; end; end; function ScanDate(const S: string; var Pos: Integer; var Date: TDateTime): Boolean; overload; var DateOrder: TDateOrder; N1, N2, N3, Y, M, D: Word; L1, L2, L3, YearLen: Byte; CenturyBase: Integer; EraName : string; EraYearOffset: Integer; function EraToYear(Year: Integer): Integer; begin {$IFDEF MSWINDOWS} if SysLocale.PriLangID = LANG_KOREAN then begin if Year <= 99 then Inc(Year, (CurrentYear + Abs(EraYearOffset)) div 100 * 100); if EraYearOffset > 0 then EraYearOffset := -EraYearOffset; end else Dec(EraYearOffset); {$ENDIF} Result := Year + EraYearOffset; end; begin Y := 0; M := 0; D := 0; YearLen := 0; Result := False; DateOrder := GetDateOrder(ShortDateFormat); EraYearOffset := 0; if ShortDateFormat[1] = 'g' then // skip over prefix text begin ScanToNumber(S, Pos); EraName := Trim(Copy(S, 1, Pos-1)); EraYearOffset := GetEraYearOffset(EraName); end else if AnsiPos('e', ShortDateFormat) > 0 then EraYearOffset := EraYearOffsets[1]; if not (ScanNumber(S, Pos, N1, L1) and ScanChar(S, Pos, DateSeparator) and ScanNumber(S, Pos, N2, L2)) then Exit; if ScanChar(S, Pos, DateSeparator) then begin if not ScanNumber(S, Pos, N3, L3) then Exit; case DateOrder of doMDY: begin Y := N3; YearLen := L3; M := N1; D := N2; end; doDMY: begin Y := N3; YearLen := L3; M := N2; D := N1; end; doYMD: begin Y := N1; YearLen := L1; M := N2; D := N3; end; end; if EraYearOffset > 0 then Y := EraToYear(Y) else if (YearLen <= 2) then begin CenturyBase := CurrentYear - TwoDigitYearCenturyWindow; Inc(Y, CenturyBase div 100 * 100); if (TwoDigitYearCenturyWindow > 0) and (Y < CenturyBase) then Inc(Y, 100); end; end else begin Y := CurrentYear; if DateOrder = doDMY then begin D := N1; M := N2; end else begin M := N1; D := N2; end; end; ScanChar(S, Pos, DateSeparator); ScanBlanks(S, Pos); if SysLocale.FarEast and (System.Pos('ddd', ShortDateFormat) <> 0) then begin // ignore trailing text if ShortTimeFormat[1] in ['0'..'9'] then // stop at time digit ScanToNumber(S, Pos) else // stop at time prefix repeat while (Pos <= Length(S)) and (S[Pos] <> ' ') do Inc(Pos); ScanBlanks(S, Pos); until (Pos > Length(S)) or (AnsiCompareText(TimeAMString, Copy(S, Pos, Length(TimeAMString))) = 0) or (AnsiCompareText(TimePMString, Copy(S, Pos, Length(TimePMString))) = 0); end; Result := TryEncodeDate(Y, M, D, Date); end; function ScanDate(const S: string; var Pos: Integer; var Date: TDateTime; const FormatSettings: TFormatSettings): Boolean; overload; var DateOrder: TDateOrder; N1, N2, N3, Y, M, D: Word; L1, L2, L3, YearLen: Byte; CenturyBase: Integer; EraName : string; EraYearOffset: Integer; function EraToYear(Year: Integer): Integer; begin {$IFDEF MSWINDOWS} if SysLocale.PriLangID = LANG_KOREAN then begin if Year <= 99 then Inc(Year, (CurrentYear + Abs(EraYearOffset)) div 100 * 100); if EraYearOffset > 0 then EraYearOffset := -EraYearOffset; end else Dec(EraYearOffset); {$ENDIF} Result := Year + EraYearOffset; end; begin Y := 0; M := 0; D := 0; YearLen := 0; Result := False; DateOrder := GetDateOrder(FormatSettings.ShortDateFormat); EraYearOffset := 0; if FormatSettings.ShortDateFormat[1] = 'g' then // skip over prefix text begin ScanToNumber(S, Pos); EraName := Trim(Copy(S, 1, Pos-1)); EraYearOffset := GetEraYearOffset(EraName); end else if AnsiPos('e', FormatSettings.ShortDateFormat) > 0 then EraYearOffset := EraYearOffsets[1]; if not (ScanNumber(S, Pos, N1, L1) and ScanChar(S, Pos, FormatSettings.DateSeparator) and ScanNumber(S, Pos, N2, L2)) then Exit; if ScanChar(S, Pos, FormatSettings.DateSeparator) then begin if not ScanNumber(S, Pos, N3, L3) then Exit; case DateOrder of doMDY: begin Y := N3; YearLen := L3; M := N1; D := N2; end; doDMY: begin Y := N3; YearLen := L3; M := N2; D := N1; end; doYMD: begin Y := N1; YearLen := L1; M := N2; D := N3; end; end; if EraYearOffset > 0 then Y := EraToYear(Y) else if (YearLen <= 2) then begin CenturyBase := CurrentYear - FormatSettings.TwoDigitYearCenturyWindow; Inc(Y, CenturyBase div 100 * 100); if (FormatSettings.TwoDigitYearCenturyWindow > 0) and (Y < CenturyBase) then Inc(Y, 100); end; end else begin Y := CurrentYear; if DateOrder = doDMY then begin D := N1; M := N2; end else begin M := N1; D := N2; end; end; ScanChar(S, Pos, FormatSettings.DateSeparator); ScanBlanks(S, Pos); if SysLocale.FarEast and (System.Pos('ddd', FormatSettings.ShortDateFormat) <> 0) then begin // ignore trailing text if FormatSettings.ShortTimeFormat[1] in ['0'..'9'] then // stop at time digit ScanToNumber(S, Pos) else // stop at time prefix repeat while (Pos <= Length(S)) and (S[Pos] <> ' ') do Inc(Pos); ScanBlanks(S, Pos); until (Pos > Length(S)) or (AnsiCompareText(FormatSettings.TimeAMString, Copy(S, Pos, Length(FormatSettings.TimeAMString))) = 0) or (AnsiCompareText(FormatSettings.TimePMString, Copy(S, Pos, Length(FormatSettings.TimePMString))) = 0); end; Result := TryEncodeDate(Y, M, D, Date); end; function ScanTime(const S: string; var Pos: Integer; var Time: TDateTime): Boolean; overload; var BaseHour: Integer; Hour, Min, Sec, MSec: Word; Junk: Byte; begin Result := False; BaseHour := -1; if ScanString(S, Pos, TimeAMString) or ScanString(S, Pos, 'AM') then BaseHour := 0 else if ScanString(S, Pos, TimePMString) or ScanString(S, Pos, 'PM') then BaseHour := 12; if BaseHour >= 0 then ScanBlanks(S, Pos); if not ScanNumber(S, Pos, Hour, Junk) then Exit; Min := 0; Sec := 0; MSec := 0; if ScanChar(S, Pos, TimeSeparator) then begin if not ScanNumber(S, Pos, Min, Junk) then Exit; if ScanChar(S, Pos, TimeSeparator) then begin if not ScanNumber(S, Pos, Sec, Junk) then Exit; if ScanChar(S, Pos, DecimalSeparator) then if not ScanNumber(S, Pos, MSec, Junk) then Exit; end; end; if BaseHour < 0 then if ScanString(S, Pos, TimeAMString) or ScanString(S, Pos, 'AM') then BaseHour := 0 else if ScanString(S, Pos, TimePMString) or ScanString(S, Pos, 'PM') then BaseHour := 12; if BaseHour >= 0 then begin if (Hour = 0) or (Hour > 12) then Exit; if Hour = 12 then Hour := 0; Inc(Hour, BaseHour); end; ScanBlanks(S, Pos); Result := TryEncodeTime(Hour, Min, Sec, MSec, Time); end; function ScanTime(const S: string; var Pos: Integer; var Time: TDateTime; const FormatSettings: TFormatSettings): Boolean; overload; var BaseHour: Integer; Hour, Min, Sec, MSec: Word; Junk: Byte; begin Result := False; BaseHour := -1; if ScanString(S, Pos, FormatSettings.TimeAMString) or ScanString(S, Pos, 'AM') then BaseHour := 0 else if ScanString(S, Pos, FormatSettings.TimePMString) or ScanString(S, Pos, 'PM') then BaseHour := 12; if BaseHour >= 0 then ScanBlanks(S, Pos); if not ScanNumber(S, Pos, Hour, Junk) then Exit; Min := 0; Sec := 0; MSec := 0; if ScanChar(S, Pos, FormatSettings.TimeSeparator) then begin if not ScanNumber(S, Pos, Min, Junk) then Exit; if ScanChar(S, Pos, FormatSettings.TimeSeparator) then begin if not ScanNumber(S, Pos, Sec, Junk) then Exit; if ScanChar(S, Pos, FormatSettings.DecimalSeparator) then if not ScanNumber(S, Pos, MSec, Junk) then Exit; end; end; if BaseHour < 0 then if ScanString(S, Pos, FormatSettings.TimeAMString) or ScanString(S, Pos, 'AM') then BaseHour := 0 else if ScanString(S, Pos, FormatSettings.TimePMString) or ScanString(S, Pos, 'PM') then BaseHour := 12; if BaseHour >= 0 then begin if (Hour = 0) or (Hour > 12) then Exit; if Hour = 12 then Hour := 0; Inc(Hour, BaseHour); end; ScanBlanks(S, Pos); Result := TryEncodeTime(Hour, Min, Sec, MSec, Time); end; function StrToDate(const S: string): TDateTime; begin if not TryStrToDate(S, Result) then ConvertErrorFmt(SInvalidDate, [S]); end; function StrToDate(const S: string; const FormatSettings: TFormatSettings): TDateTime; begin if not TryStrToDate(S, Result, FormatSettings) then ConvertErrorFmt(SInvalidDate, [S]); end; function StrToDateDef(const S: string; const Default: TDateTime): TDateTime; begin if not TryStrToDate(S, Result) then Result := Default; end; function StrToDateDef(const S: string; const Default: TDateTime; const FormatSettings: TFormatSettings): TDateTime; begin if not TryStrToDate(S, Result, FormatSettings) then Result := Default; end; function TryStrToDate(const S: string; out Value: TDateTime): Boolean; var Pos: Integer; begin Pos := 1; Result := ScanDate(S, Pos, Value) and (Pos > Length(S)); end; function TryStrToDate(const S: string; out Value: TDateTime; const FormatSettings: TFormatSettings): Boolean; var Pos: Integer; begin Pos := 1; Result := ScanDate(S, Pos, Value, FormatSettings) and (Pos > Length(S)); end; function StrToTime(const S: string): TDateTime; begin if not TryStrToTime(S, Result) then ConvertErrorFmt(SInvalidTime, [S]); end; function StrToTime(const S: string; const FormatSettings: TFormatSettings): TDateTime; begin if not TryStrToTime(S, Result, FormatSettings) then ConvertErrorFmt(SInvalidTime, [S]); end; function StrToTimeDef(const S: string; const Default: TDateTime): TDateTime; begin if not TryStrToTime(S, Result) then Result := Default; end; function StrToTimeDef(const S: string; const Default: TDateTime; const FormatSettings: TFormatSettings): TDateTime; begin if not TryStrToTime(S, Result, FormatSettings) then Result := Default; end; function TryStrToTime(const S: string; out Value: TDateTime): Boolean; var Pos: Integer; begin Pos := 1; Result := ScanTime(S, Pos, Value) and (Pos > Length(S)); end; function TryStrToTime(const S: string; out Value: TDateTime; const FormatSettings: TFormatSettings): Boolean; var Pos: Integer; begin Pos := 1; Result := ScanTime(S, Pos, Value, FormatSettings) and (Pos > Length(S)); end; function StrToDateTime(const S: string): TDateTime; begin if not TryStrToDateTime(S, Result) then ConvertErrorFmt(SInvalidDateTime, [S]); end; function StrToDateTime(const S: string; const FormatSettings: TFormatSettings): TDateTime; begin if not TryStrToDateTime(S, Result, FormatSettings) then ConvertErrorFmt(SInvalidDateTime, [S]); end; function StrToDateTimeDef(const S: string; const Default: TDateTime): TDateTime; begin if not TryStrToDateTime(S, Result) then Result := Default; end; function StrToDateTimeDef(const S: string; const Default: TDateTime; const FormatSettings: TFormatSettings): TDateTime; begin if not TryStrToDateTime(S, Result, FormatSettings) then Result := Default; end; function TryStrToDateTime(const S: string; out Value: TDateTime): Boolean; var Pos: Integer; Date, Time: TDateTime; begin Result := True; Pos := 1; Time := 0; if not ScanDate(S, Pos, Date) or not ((Pos > Length(S)) or ScanTime(S, Pos, Time)) then // Try time only Result := TryStrToTime(S, Value) else if Date >= 0 then Value := Date + Time else Value := Date - Time; end; function TryStrToDateTime(const S: string; out Value: TDateTime; const FormatSettings: TFormatSettings): Boolean; var Pos: Integer; Date, Time: TDateTime; begin Result := True; Pos := 1; Time := 0; if not ScanDate(S, Pos, Date, FormatSettings) or not ((Pos > Length(S)) or ScanTime(S, Pos, Time, FormatSettings)) then // Try time only Result := TryStrToTime(S, Value, FormatSettings) else if Date >= 0 then Value := Date + Time else Value := Date - Time; end; { System error messages } function SysErrorMessage(ErrorCode: Integer): string; var Buffer: array[0..255] of Char; {$IFDEF MSWINDOWS} var Len: Integer; begin Len := FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS or FORMAT_MESSAGE_ARGUMENT_ARRAY, nil, ErrorCode, 0, Buffer, SizeOf(Buffer), nil); while (Len > 0) and (Buffer[Len - 1] in [#0..#32, '.']) do Dec(Len); SetString(Result, Buffer, Len); end; {$ENDIF} {$IFDEF LINUX} begin //Result := Format('System error: %4x',[ErrorCode]); Result := strerror_r(ErrorCode, Buffer, sizeof(Buffer)); end; {$ENDIF} { Initialization file support } function GetLocaleStr(Locale, LocaleType: Integer; const Default: string): string; {$IFDEF MSWINDOWS} var L: Integer; Buffer: array[0..255] of Char; begin L := GetLocaleInfo(Locale, LocaleType, Buffer, SizeOf(Buffer)); if L > 0 then SetString(Result, Buffer, L - 1) else Result := Default; end; {$ENDIF} {$IFDEF LINUX} begin Result := Default; end; {$ENDIF} function GetLocaleChar(Locale, LocaleType: Integer; Default: Char): Char; {$IFDEF MSWINDOWS} var Buffer: array[0..1] of Char; begin if GetLocaleInfo(Locale, LocaleType, Buffer, 2) > 0 then Result := Buffer[0] else Result := Default; end; {$ENDIF} {$IFDEF LINUX} begin Result := Default; end; {$ENDIF} {var DefShortMonthNames: array[1..12] of Pointer = (@SShortMonthNameJan, @SShortMonthNameFeb, @SShortMonthNameMar, @SShortMonthNameApr, @SShortMonthNameMay, @SShortMonthNameJun, @SShortMonthNameJul, @SShortMonthNameAug, @SShortMonthNameSep, @SShortMonthNameOct, @SShortMonthNameNov, @SShortMonthNameDec); DefLongMonthNames: array[1..12] of Pointer = (@SLongMonthNameJan, @SLongMonthNameFeb, @SLongMonthNameMar, @SLongMonthNameApr, @SLongMonthNameMay, @SLongMonthNameJun, @SLongMonthNameJul, @SLongMonthNameAug, @SLongMonthNameSep, @SLongMonthNameOct, @SLongMonthNameNov, @SLongMonthNameDec); DefShortDayNames: array[1..7] of Pointer = (@SShortDayNameSun, @SShortDayNameMon, @SShortDayNameTue, @SShortDayNameWed, @SShortDayNameThu, @SShortDayNameFri, @SShortDayNameSat); DefLongDayNames: array[1..7] of Pointer = (@SLongDayNameSun, @SLongDayNameMon, @SLongDayNameTue, @SLongDayNameWed, @SLongDayNameThu, @SLongDayNameFri, @SLongDayNameSat); } procedure GetMonthDayNames; {$IFDEF MSWINDOWS} var I, Day: Integer; DefaultLCID: LCID; function LocalGetLocaleStr(LocaleType: Integer): string; begin Result := GetLocaleStr(DefaultLCID, LocaleType, ''); if Result = '' then Result := GetLocaleStr($409, LocaleType, ''); //Result := LoadResString(DefValues[Index]); end; begin DefaultLCID := GetThreadLocale; for I := 1 to 12 do begin ShortMonthNames[I] := LocalGetLocaleStr(LOCALE_SABBREVMONTHNAME1 + I - 1); LongMonthNames[I] := LocalGetLocaleStr(LOCALE_SMONTHNAME1 + I - 1); end; for I := 1 to 7 do begin Day := (I + 5) mod 7; ShortDayNames[I] := LocalGetLocaleStr(LOCALE_SABBREVDAYNAME1 + Day); LongDayNames[I] := LocalGetLocaleStr(LOCALE_SDAYNAME1 + Day); end; end; {$ELSE} {$IFDEF LINUX} function GetLocaleStr(LocaleIndex, Index: Integer; const DefValues: array of Pointer): string; var temp: PChar; begin temp := nl_langinfo(LocaleIndex); if (temp = nil) or (temp^ = #0) then Result := LoadResString(DefValues[Index]) else Result := temp; end; var I: Integer; begin for I := 1 to 12 do begin ShortMonthNames[I] := GetLocaleStr(ABMON_1 + I - 1, I - Low(DefShortMonthNames), DefShortMonthNames); LongMonthNames[I] := GetLocaleStr(MON_1 + I - 1, I - Low(DefLongMonthNames), DefLongMonthNames); end; for I := 1 to 7 do begin ShortDayNames[I] := GetLocaleStr(ABDAY_1 + I - 1, I - Low(DefShortDayNames), DefShortDayNames); LongDayNames[I] := GetLocaleStr(DAY_1 + I - 1, I - Low(DefLongDayNames), DefLongDayNames); end; end; {$ELSE} var I: Integer; begin for I := 1 to 12 do begin ShortMonthNames[I] := LoadResString(DefShortMonthNames[I]); LongMonthNames[I] := LoadResString(DefLongMonthNames[I]); end; for I := 1 to 7 do begin ShortDayNames[I] := LoadResString(DefShortDayNames[I]); LongDayNames[I] := LoadResString(DefLongDayNames[I]); end; end; {$ENDIF} {$ENDIF} {$IFDEF MSWINDOWS} procedure GetLocaleMonthDayNames(DefaultLCID: Integer; var FormatSettings: TFormatSettings); var I, Day: Integer; function LocalGetLocaleStr(LocaleType: Integer): string; begin Result := GetLocaleStr(DefaultLCID, LocaleType, ''); if Result = '' then Result := GetLocaleStr($409, LocaleType, ''); end; begin for I := 1 to 12 do begin FormatSettings.ShortMonthNames[I] := LocalGetLocaleStr(LOCALE_SABBREVMONTHNAME1 + I - 1); FormatSettings.LongMonthNames[I] := LocalGetLocaleStr(LOCALE_SMONTHNAME1 + I - 1); end; for I := 1 to 7 do begin Day := (I + 5) mod 7; FormatSettings.ShortDayNames[I] := LocalGetLocaleStr(LOCALE_SABBREVDAYNAME1 + Day); FormatSettings.LongDayNames[I] := LocalGetLocaleStr(LOCALE_SDAYNAME1 + Day); end; end; {$ENDIF} {$IFDEF MSWINDOWS} function EnumEraNames(Names: PChar): Integer; stdcall; var I: Integer; begin Result := 0; I := Low(EraNames); while EraNames[I] <> '' do if (I = High(EraNames)) then Exit else Inc(I); EraNames[I] := Names; Result := 1; end; function EnumEraYearOffsets(YearOffsets: PChar): Integer; stdcall; var I: Integer; begin Result := 0; I := Low(EraYearOffsets); while EraYearOffsets[I] <> -1 do if (I = High(EraYearOffsets)) then Exit else Inc(I); EraYearOffsets[I] := StrToIntDef(YearOffsets, 0); Result := 1; end; procedure GetEraNamesAndYearOffsets; var J: Integer; CalendarType: CALTYPE; begin CalendarType := StrToIntDef(GetLocaleStr(GetThreadLocale, LOCALE_IOPTIONALCALENDAR, '1'), 1); if CalendarType in [CAL_JAPAN, CAL_TAIWAN, CAL_KOREA] then begin EnumCalendarInfoA(@EnumEraNames, GetThreadLocale, CalendarType, CAL_SERASTRING); for J := Low(EraYearOffsets) to High(EraYearOffsets) do EraYearOffsets[J] := -1; EnumCalendarInfoA(@EnumEraYearOffsets, GetThreadLocale, CalendarType, CAL_IYEAROFFSETRANGE); end; end; function TranslateDateFormat(const FormatStr: string): string; var I: Integer; L: Integer; CalendarType: CALTYPE; RemoveEra: Boolean; begin I := 1; Result := ''; CalendarType := StrToIntDef(GetLocaleStr(GetThreadLocale, LOCALE_ICALENDARTYPE, '1'), 1); if not (CalendarType in [CAL_JAPAN, CAL_TAIWAN, CAL_KOREA]) then begin RemoveEra := SysLocale.PriLangID in [LANG_JAPANESE, LANG_CHINESE, LANG_KOREAN]; if RemoveEra then begin While I <= Length(FormatStr) do begin if not (FormatStr[I] in ['g', 'G']) then Result := Result + FormatStr[I]; Inc(I); end; end else Result := FormatStr; Exit; end; while I <= Length(FormatStr) do begin if FormatStr[I] in LeadBytes then begin L := CharLength(FormatStr, I); Result := Result + Copy(FormatStr, I, L); Inc(I, L); end else begin if StrLIComp(@FormatStr[I], 'gg', 2) = 0 then begin Result := Result + 'ggg'; Inc(I, 1); end else if StrLIComp(@FormatStr[I], 'yyyy', 4) = 0 then begin Result := Result + 'eeee'; Inc(I, 4-1); end else if StrLIComp(@FormatStr[I], 'yy', 2) = 0 then begin Result := Result + 'ee'; Inc(I, 2-1); end else if FormatStr[I] in ['y', 'Y'] then Result := Result + 'e' else Result := Result + FormatStr[I]; Inc(I); end; end; end; {$ENDIF} {$IFDEF LINUX} procedure InitEras; var Count : Byte; I, J, Pos : Integer; Number : Word; S : string; Year, Month, Day: Word; begin EraCount := 0; S := nl_langinfo(ERA); if S = '' then S := LoadResString(@SEraEntries); Pos := 1; for I := 1 to MaxEraCount do begin if Pos > Length(S) then Break; if not(ScanChar(S, Pos, '+') or ScanChar(S, Pos, '-')) then Break; // Eras in which year increases with negative time (eg Christian BC era) // are not currently supported. // EraRanges[I].Direction := S[Pos - 1]; // Era offset, in years from Gregorian calendar year if not ScanChar(S, Pos, ':') then Break; if ScanChar(S, Pos, '-') then J := -1 else J := 1; if not ScanNumber(S, Pos, Number, Count) then Break; EraYearOffsets[I] := J * Number; // apply sign to Number // Era start date, in Gregorian year/month/day format if not ScanChar(S, Pos, ':') then Break; if not ScanNumber(S, Pos, Year, Count) then Break; if not ScanChar(S, Pos, '/') then Break; if not ScanNumber(S, Pos, Month, Count) then Break; if not ScanChar(S, Pos, '/') then Break; if not ScanNumber(S, Pos, Day, Count) then Break; EraRanges[I].StartDate := Trunc(EncodeDate(Year, Month, Day)); EraYearOffsets[I] := Year - EraYearOffsets[I]; // Era end date, in Gregorian year/month/day format if not ScanChar(S, Pos, ':') then Break; if ScanString(S, Pos, '+*') then // positive infinity EraRanges[I].EndDate := High(EraRanges[I].EndDate) else if ScanString(S, Pos, '-*') then // negative infinity EraRanges[I].EndDate := Low(EraRanges[I].EndDate) else if not ScanNumber(S, Pos, Year, Count) then Break else begin if not ScanChar(S, Pos, '/') then Break; if not ScanNumber(S, Pos, Month, Count) then Break; if not ScanChar(S, Pos, '/') then Break; if not ScanNumber(S, Pos, Day, Count) then Break; EraRanges[I].EndDate := Trunc(EncodeDate(Year, Month, Day)); end; // Era name, in locale charset if not ScanChar(S, Pos, ':') then Break; J := AnsiPos(':', Copy(S, Pos, Length(S) + 1 - Pos)); if J = 0 then Break; EraNames[I] := Copy(S, Pos, J - 1); Inc(Pos, J - 1); // Optional Era format string for era year, in locale charset if not ScanChar(S, Pos, ':') then Break; J := AnsiPos(';', Copy(S, Pos, Length(S) + 1 - Pos)); if J = 0 then J := 1 + Length(S) + 1 - Pos; {if J = 0 then Break;} EraYearFormats[I] := Copy(S, Pos, J - 1); Inc(Pos, J - 1); Inc(EraCount); if not((Pos > Length(S)) or ScanChar(S, Pos, ';')) then Break; end; // Clear the rest of the era slots, including partial entry from failed parse for I := EraCount+1 to MaxEraCount do begin EraNames[I] := ''; EraYearOffsets[I] := -1; EraRanges[I].StartDate := High(EraRanges[I].StartDate); EraRanges[I].EndDate := High(EraRanges[I].EndDate); EraYearFormats[I] := ''; end; end; {$ENDIF} { Exception handling routines } var OutOfMemory: EOutOfMemory; InvalidPointer: EInvalidPointer; { Convert physical address to logical address } { Format and return an exception error message } function ExceptionErrorMessage(ExceptObject: TObject; ExceptAddr: Pointer; Buffer: PChar; Size: Integer): Integer; {$IFDEF MSWINDOWS} function ConvertAddr(Address: Pointer): Pointer; assembler; asm TEST EAX,EAX { Always convert nil to nil } JE @@1 SUB EAX, $1000 { offset from code start; code start set by linker to $1000 } @@1: end; var MsgPtr: PChar; MsgEnd: PChar; MsgLen: Integer; ModuleName: array[0..MAX_PATH] of Char; Temp: array[0..MAX_PATH] of Char; //Format: array[0..255] of Char; Info: TMemoryBasicInformation; ConvertedAddress: Pointer; begin VirtualQuery(ExceptAddr, Info, sizeof(Info)); if (Info.State <> MEM_COMMIT) or (GetModuleFilename(THandle(Info.AllocationBase), Temp, SizeOf(Temp)) = 0) then begin GetModuleFileName(HInstance, Temp, SizeOf(Temp)); ConvertedAddress := ConvertAddr(ExceptAddr); end else Integer(ConvertedAddress) := Integer(ExceptAddr) - Integer(Info.AllocationBase); StrLCopy(ModuleName, AnsiStrRScan(Temp, '\') + 1, SizeOf(ModuleName) - 1); MsgPtr := ''; MsgEnd := ''; if ExceptObject is Exception then begin MsgPtr := PChar(Exception(ExceptObject).Message); MsgLen := StrLen(MsgPtr); if (MsgLen <> 0) and (MsgPtr[MsgLen - 1] <> '.') then MsgEnd := '.'; end; {LoadString(FindResourceHInstance(HInstance), PResStringRec(@SException).Identifier, Format, SizeOf(Format)); StrLFmt(Buffer, Size, Format, [ExceptObject.ClassName, ModuleName, ConvertedAddress, MsgPtr, MsgEnd]); } StrPCopy(Buffer, kol.Format(SException, [ExceptObject.ClassName, ModuleName, ConvertedAddress, MsgPtr, MsgEnd]) ); Result := StrLen(Buffer); end; {$ENDIF} {$IFDEF LINUX} const UnknownModuleName = ''; var MsgPtr: PChar; MsgEnd: PChar; MsgLen: Integer; ModuleName: array[0..MAX_PATH] of Char; Info: TDLInfo; begin MsgPtr := ''; MsgEnd := ''; if ExceptObject is Exception then begin MsgPtr := PChar(Exception(ExceptObject).Message); MsgLen := StrLen(MsgPtr); if (MsgLen <> 0) and (MsgPtr[MsgLen - 1] <> '.') then MsgEnd := '.'; end; if (dladdr(ExceptAddr, Info) <> 0) and (Info.dli_fname <> nil) then StrLCopy(ModuleName, AnsiStrRScan(Info.dli_fname, PathDelim) + 1, SizeOf(ModuleName) - 1) else StrLCopy(ModuleName, UnknownModuleName, SizeOf(ModuleName) - 1); StrLFmt(Buffer, Size, PChar(SException), [ExceptObject.ClassName, ModuleName, ExceptAddr, MsgPtr, MsgEnd]); Result := StrLen(Buffer); end; {$ENDIF} { Display exception message box } procedure ShowException(ExceptObject: TObject; ExceptAddr: Pointer); {$IFDEF MSWINDOWS} var //Title: array[0..63] of Char; Buffer: array[0..1023] of Char; Dummy: Cardinal; begin ExceptionErrorMessage(ExceptObject, ExceptAddr, Buffer, SizeOf(Buffer)); if IsConsole then begin Flush(Output); CharToOemA(Buffer, Buffer); WriteFile(GetStdHandle(STD_ERROR_HANDLE), Buffer, StrLen(Buffer), Dummy, nil); WriteFile(GetStdHandle(STD_ERROR_HANDLE), sLineBreak, 2, Dummy, nil); end else begin { LoadString(FindResourceHInstance(HInstance), PResStringRec(@SExceptTitle).Identifier, Title, SizeOf(Title)); MessageBox(0, Buffer, Title, MB_OK or MB_ICONSTOP or MB_TASKMODAL); } MessageBox(0, Buffer, PChar(SExceptTitle), MB_OK or MB_ICONSTOP or MB_TASKMODAL); end; end; {$ENDIF} {$IFDEF LINUX} var Buffer: array[0..1023] of Char; begin ExceptionErrorMessage(ExceptObject, ExceptAddr, Buffer, Sizeof(Buffer)); if TTextRec(ErrOutput).Mode = fmOutput then Flush(ErrOutput); __write(STDERR_FILENO, Buffer, StrLen(Buffer)); end; {$ENDIF} { Raise abort exception } procedure Abort; function ReturnAddr: Pointer; asm MOV EAX,[EBP + 4] end; begin raise EAbort.CreateRes(SOperationAborted) at ReturnAddr; end; { Raise out of memory exception } procedure OutOfMemoryError; begin raise OutOfMemory; end; { Exception class } constructor Exception.Create(const Msg: string); begin FMessage := Msg; end; constructor Exception.CreateFmt(const Msg: string; const Args: array of const); begin FMessage := Format(Msg, Args); end; constructor Exception.CreateRes(Ident: Integer); begin FMessage := LoadStr(Ident); end; constructor Exception.CreateRes(const ResStringRec: string); begin FMessage := ResStringRec; end; constructor Exception.CreateResFmt(Ident: Integer; const Args: array of const); begin FMessage := Format(LoadStr(Ident), Args); end; constructor Exception.CreateResFmt(const ResStringRec: string; const Args: array of const); begin FMessage := Format(ResStringRec, Args); end; constructor Exception.CreateHelp(const Msg: string; AHelpContext: Integer); begin FMessage := Msg; FHelpContext := AHelpContext; end; constructor Exception.CreateFmtHelp(const Msg: string; const Args: array of const; AHelpContext: Integer); begin FMessage := Format(Msg, Args); FHelpContext := AHelpContext; end; constructor Exception.CreateResHelp(Ident: Integer; AHelpContext: Integer); begin FMessage := LoadStr(Ident); FHelpContext := AHelpContext; end; constructor Exception.CreateResHelp(ResStringRec: PResStringRec; AHelpContext: Integer); begin FMessage := LoadResString(ResStringRec); FHelpContext := AHelpContext; end; constructor Exception.CreateResFmtHelp(Ident: Integer; const Args: array of const; AHelpContext: Integer); begin FMessage := Format(LoadStr(Ident), Args); FHelpContext := AHelpContext; end; constructor Exception.CreateResFmtHelp(ResStringRec: PResStringRec; const Args: array of const; AHelpContext: Integer); begin FMessage := Format(LoadResString(ResStringRec), Args); FHelpContext := AHelpContext; end; { EHeapException class } procedure EHeapException.FreeInstance; begin if AllowFree then inherited FreeInstance; end; { Create I/O exception } function CreateInOutError: EInOutError; type TErrorRec = record Code: Integer; Ident: string; end; const ErrorMap: array[0..6] of TErrorRec = ( (Code: 2; Ident: SFileNotFound), (Code: 3; Ident: SInvalidFilename), (Code: 4; Ident: STooManyOpenFiles), (Code: 5; Ident: SAccessDenied), (Code: 100; Ident: SEndOfFile), (Code: 101; Ident: SDiskFull), (Code: 106; Ident: SInvalidInput)); var I: Integer; InOutRes: Integer; begin I := Low(ErrorMap); InOutRes := IOResult; // resets IOResult to zero while (I <= High(ErrorMap)) and (ErrorMap[I].Code <> InOutRes) do Inc(I); if I <= High(ErrorMap) then Result := EInOutError.Create(ErrorMap[I].Ident) else Result := EInOutError.CreateResFmt(SInOutError, [InOutRes]); Result.ErrorCode := InOutRes; end; { RTL error handler } type TExceptRec = record EClass: ExceptClass; EIdent: string; end; const ExceptMap: array[Ord(reDivByZero)..Ord(High(TRuntimeError))] of TExceptRec = ( (EClass: EDivByZero; EIdent: SDivByZero), (EClass: ERangeError; EIdent: SRangeError), (EClass: EIntOverflow; EIdent: SIntOverflow), (EClass: EInvalidOp; EIdent: SInvalidOp), (EClass: EZeroDivide; EIdent: SZeroDivide), (EClass: EOverflow; EIdent: SOverflow), (EClass: EUnderflow; EIdent: SUnderflow), (EClass: EInvalidCast; EIdent: SInvalidCast), (EClass: EAccessViolation; EIdent: SAccessViolationNoArg), (EClass: EPrivilege; EIdent: SPrivilege), (EClass: EControlC; EIdent: SControlC), (EClass: EStackOverflow; EIdent: SStackOverflow), (EClass: EVariantError; EIdent: SInvalidVarCast), (EClass: EVariantError; EIdent: SInvalidVarOp), (EClass: EVariantError; EIdent: SDispatchError), (EClass: EVariantError; EIdent: SVarArrayCreate), (EClass: EVariantError; EIdent: SVarInvalid), (EClass: EVariantError; EIdent: SVarArrayBounds), (EClass: EAssertionFailed; EIdent: SAssertionFailed), (EClass: EExternalException; EIdent: SExternalException), (EClass: EIntfCastError; EIdent: SIntfCastError), (EClass: ESafecallException; EIdent: SSafecallException) {$IFDEF LINUX} , (EClass: EQuit; EIdent: SQuit), (EClass: ECodesetConversion; EIdent: SCodesetConversionError) {$ENDIF} ); procedure ErrorHandler(ErrorCode: Byte; ErrorAddr: Pointer); export; var E: Exception; begin case ErrorCode of Ord(reOutOfMemory): E := OutOfMemory; Ord(reInvalidPtr): E := InvalidPointer; Ord(reDivByZero)..Ord(High(TRuntimeError)): begin with ExceptMap[ErrorCode] do E := EClass.Create(EIdent); end; else E := CreateInOutError; end; raise E at ErrorAddr; end; { Assertion error handler } { This is complicated by the desire to make it look like the exception } { happened in the user routine, so the debugger can give a decent stack } { trace. To make that feasible, AssertErrorHandler calls a helper function } { to create the exception object, so that AssertErrorHandler itself does } { not need any temps. After the exception object is created, the asm } { routine RaiseAssertException sets up the registers just as if the user } { code itself had raised the exception. } function CreateAssertException(const Message, Filename: string; LineNumber: Integer): Exception; var S: string; begin if Message <> '' then S := Message else S := SAssertionFailed; Result := EAssertionFailed.CreateFmt(SAssertError, [S, Filename, LineNumber]); end; { This code is based on the following assumptions: } { - Our direct caller (AssertErrorHandler) has an EBP frame } { - ErrorStack points to where the return address would be if the } { user program had called System.@RaiseExcept directly } procedure RaiseAssertException(const E: Exception; const ErrorAddr, ErrorStack: Pointer); asm MOV ESP,ECX MOV [ESP],EDX MOV EBP,[EBP] JMP System.@RaiseExcept end; { If you change this procedure, make sure it does not have any local variables } { or temps that need cleanup - they won't get cleaned up due to the way } { RaiseAssertException frame works. Also, it can not have an exception frame. } procedure AssertErrorHandler(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer); var E: Exception; begin E := CreateAssertException(Message, Filename, LineNumber); {$IF Defined(LINUX)} RaiseAssertException(E, ErrorAddr, PChar(@ErrorAddr)+8); {$ELSEIF Defined(MSWINDOWS)} RaiseAssertException(E, ErrorAddr, PChar(@ErrorAddr)+4); {$ELSE} {$MESSAGE ERROR 'AssertErrorHandler not implemented'} {$IFEND} end; {$IFNDEF PC_MAPPED_EXCEPTIONS} { Abstract method invoke error handler } procedure AbstractErrorHandler; begin raise EAbstractError.CreateRes(SAbstractError); end; {$ENDIF} {$IFDEF LINUX} const TRAP_ZERODIVIDE = 0; TRAP_SINGLESTEP = 1; TRAP_NMI = 2; TRAP_BREAKPOINT = 3; TRAP_OVERFLOW = 4; TRAP_BOUND = 5; TRAP_INVINSTR = 6; TRAP_DEVICENA = 7; TRAP_DOUBLEFAULT = 8; TRAP_FPOVERRUN = 9; TRAP_BADTSS = 10; TRAP_SEGMENTNP = 11; TRAP_STACKFAULT = 12; TRAP_GPFAULT = 13; TRAP_PAGEFAULT = 14; TRAP_RESERVED = 15; TRAP_FPE = 16; TRAP_ALIGNMENT = 17; TRAP_MACHINECHECK = 18; TRAP_CACHEFAULT = 19; TRAP_UNKNOWN = -1; function MapFPUStatus(Status: LongWord): TRuntimeError; begin if (Status and 1) = 1 then Result := System.reInvalidOp // STACK_CHECK or INVALID_OPERATION else if (Status and 2) = 2 then Result := System.reInvalidOp // DENORMAL_OPERAND else if (Status and 4) = 4 then Result := System.reZeroDivide // DIVIDE_BY_ZERO else if (Status and 8) = 8 then Result := System.reOverflow // OVERFLOW else if (Status and $10) = $10 then Result := System.reUnderflow // UNDERFLOW else if (Status and $20) = $20 then Result := System.reInvalidOp // INEXACT_RESULT else Result := System.reInvalidOp; end; function MapFPE(Context: PSigContext): TRuntimeError; begin case Context^.trapno of TRAP_ZERODIVIDE: Result := System.reDivByZero; TRAP_FPOVERRUN: Result := System.reInvalidOp; TRAP_FPE: Result := MapFPUStatus(Context^.fpstate^.sw); else Result := System.reInvalidOp; end; end; function MapFault(Context: PSigContext): TRuntimeError; begin case Context^.trapno of TRAP_OVERFLOW: Result := System.reIntOverflow; TRAP_BOUND: Result := System.reRangeError; TRAP_INVINSTR: Result := System.rePrivInstruction; // This doesn't seem right, but we don't // have an external exception to match! TRAP_STACKFAULT: Result := System.reStackOverflow; TRAP_SEGMENTNP, TRAP_GPFAULT: Result := System.reAccessViolation; TRAP_PAGEFAULT: Result := System.reAccessViolation; else Result := System.reAccessViolation; end; end; function MapSignal(SigNum: Integer; Context: PSigContext): LongWord; var Err: TRuntimeError; begin case SigNum of SIGINT: { Control-C } Err := System.reControlBreak; SIGQUIT: { Quit key (Control-\) } Err := System.reQuit; SIGFPE: { Floating Point Error } Err := MapFPE(Context); SIGSEGV: { Segmentation Violation } Err := MapFault(Context); SIGILL: { Illegal Instruction } Err := MapFault(Context); SIGBUS: { Bus Error } Err := MapFault(Context); else Err := System.reExternalException; end; Result := LongWord(Err) or (LongWord(SigNum) shl 16); end; {$ENDIF} {$IFDEF MSWINDOWS} function MapException(P: PExceptionRecord): TRuntimeError; begin case P.ExceptionCode of STATUS_INTEGER_DIVIDE_BY_ZERO: Result := System.reDivByZero; STATUS_ARRAY_BOUNDS_EXCEEDED: Result := System.reRangeError; STATUS_INTEGER_OVERFLOW: Result := System.reIntOverflow; STATUS_FLOAT_INEXACT_RESULT, STATUS_FLOAT_INVALID_OPERATION, STATUS_FLOAT_STACK_CHECK: Result := System.reInvalidOp; STATUS_FLOAT_DIVIDE_BY_ZERO: Result := System.reZeroDivide; STATUS_FLOAT_OVERFLOW: Result := System.reOverflow; STATUS_FLOAT_UNDERFLOW, STATUS_FLOAT_DENORMAL_OPERAND: Result := System.reUnderflow; STATUS_ACCESS_VIOLATION: Result := System.reAccessViolation; STATUS_PRIVILEGED_INSTRUCTION: Result := System.rePrivInstruction; STATUS_CONTROL_C_EXIT: Result := System.reControlBreak; STATUS_STACK_OVERFLOW: Result := System.reStackOverflow; else Result := System.reExternalException; end; end; function GetExceptionClass(P: PExceptionRecord): ExceptClass; var ErrorCode: Byte; begin ErrorCode := Byte(MapException(P)); Result := ExceptMap[ErrorCode].EClass; end; function GetExceptionObject(P: PExceptionRecord): Exception; var ErrorCode: Integer; function CreateAVObject: Exception; var AccessOp: string; // string ID indicating the access type READ or WRITE AccessAddress: Pointer; MemInfo: TMemoryBasicInformation; ModName: array[0..MAX_PATH] of Char; begin with P^ do begin if ExceptionInformation[0] = 0 then AccessOp := SReadAccess else AccessOp := SWriteAccess; AccessAddress := Pointer(ExceptionInformation[1]); VirtualQuery(ExceptionAddress, MemInfo, SizeOf(MemInfo)); if (MemInfo.State = MEM_COMMIT) and (GetModuleFileName(THandle(MemInfo.AllocationBase), ModName, SizeOf(ModName)) <> 0) then Result := EAccessViolation.CreateFmt(sModuleAccessViolation, [ExceptionAddress, ExtractFileName(ModName), AccessOp, AccessAddress]) else Result := EAccessViolation.CreateFmt(SAccessViolationArg3, [ExceptionAddress, AccessOp, AccessAddress]); end; end; begin ErrorCode := Byte(MapException(P)); case ErrorCode of 3..10, 12..21: with ExceptMap[ErrorCode] do Result := EClass.Create(EIdent); 11: Result := CreateAVObject; else Result := EExternalException.CreateFmt(SExternalException, [P.ExceptionCode]); end; if Result is EExternal then EExternal(Result).ExceptionRecord := P; end; {$ENDIF} { WIN32 } {$IFDEF LINUX} { The ErrorCode has the translated error code in the low byte and the original signal number in the high word. } function GetExceptionObject(ExceptionAddress: LongWord; AccessAddress: LongWord; ErrorCode: LongWord): Exception; begin case (ErrorCode and $ff) of 3..10, 12..21, 25: begin with ExceptMap[ErrorCode and $ff] do Result := EClass.Create(EIdent); end; 11: Result := EAccessViolation.CreateFmt(SAccessViolationArg2, [Pointer(ExceptionAddress), Pointer(AccessAddress)]); else // Result := EExternalException.CreateFmt(SExternalException, [P.ExceptionCode]); { Not quite right - we need the original trap code, but that's lost } Result := EExternalException.CreateFmt(SExternalException, [ErrorCode and $ff]); end; EExternal(Result).ExceptionAddress := ExceptionAddress; EExternal(Result).AccessAddress := AccessAddress; EExternal(Result).SignalNumber := ErrorCode shr 16; end; {$ENDIF} { RTL exception handler } procedure ExceptHandler(ExceptObject: TObject; ExceptAddr: Pointer); far; begin ShowException(ExceptObject, ExceptAddr); Halt(1); end; {$IFDEF LINUX} {$IFDEF DEBUG} { Used for debugging the signal handlers. } procedure DumpContext(SigNum: Integer; context : PSigContext); var Buff: array [0..128] of char; begin StrFmt(Buff, 'Context for signal: %d', [SigNum]); Writeln(Buff); StrFmt(Buff, 'CS = %04X DS = %04X ES = %04X FS = %04X GS = %04X SS = %04X', [context^.cs, context^.ds, context^.es, context^.fs, context^.gs, context^.ss]); WriteLn(Buff); StrFmt(Buff, 'EAX = %08X EBX = %08X ECX = %08X EDX = %08X', [context^.eax, context^.ebx, context^.ecx, context^.edx]); WriteLn(Buff); StrFmt(Buff, 'EDI = %08X ESI = %08X EBP = %08X ESP = %08X', [context^.edi, context^.esi, context^.ebp, context^.esp]); WriteLn(Buff); StrFmt(Buff, 'EIP = %08X EFLAGS = %08X ESP(signal) = %08X CR2 = %08X', [context^.eip, context^.eflags, context^.esp_at_signal, context^.cr2]); WriteLn(Buff); StrFmt(Buff, 'trapno = %d, err = %08x', [context^.trapno, context^.err]); WriteLn(Buff); end; {$ENDIF} { RaiseSignalException is called from SignalConverter, once we've made things look like there's a legitimate stack frame above us. Now we will just create an exception object, and raise it via a software raise. } procedure RaiseSignalException(ExceptionEIP: LongWord; FaultAddr: LongWord; ErrorCode: LongWord); begin raise GetExceptionObject(ExceptionEIP, FaultAddr, ErrorCode); end; { SignalConverter is where we come when a signal is raised that we want to convert to an exception. This function stands the best chance of being called with a useable stack frame behind it for the purpose of stack unwinding. We can't guarantee that, though. The stack was modified by the baseline signal handler to make it look as though we were called by the faulting instruction. That way the unwinder stands a chance of being able to clean things up. } procedure SignalConverter(ExceptionEIP: LongWord; FaultAddr: LongWord; ErrorCode: LongWord); asm { Here's the tricky part. We arrived here directly by virtue of our signal handler tweaking the execution context with our address. That means there's no return address on the stack. The unwinder needs to have a return address so that it can unwind past this function when we raise the Delphi exception. We will use the faulting instruction pointer as a fake return address. Because of the fencepost conditions in the Delphi unwinder, we need to have an address that is strictly greater than the actual faulting instruction, so we increment that address by one. This may be in the middle of an instruction, but we don't care, because we will never be returning to that address. Finally, the way that we get this address onto the stack is important. The compiler will generate unwind information for SignalConverter that will attempt to undo any stack modifications that are made by this function when unwinding past it. In this particular case, we don't want that to happen, so we use some assembly language tricks to get around the compiler noticing the stack modification. } MOV EBX, ESP // Get the current stack pointer SUB EBX, 4 // Effectively decrement the stack by 4 MOV ESP, EBX // by doing a move to ESP with a register value MOV [ESP], EAX // Store the instruction pointer into the new stack loc INC [ESP] // Increment by one to keep the unwinder happy { Reset the FPU, or things can go south down the line from here } FNINIT FWAIT {$IFDEF PIC} PUSH EAX PUSH ECX CALL GetGOT MOV EAX, [EAX].offset Default8087CW FLDCW [EAX] POP ECX POP EAX {$ELSE} FLDCW Default8087CW {$ENDIF} PUSH EBP MOV EBP, ESP CALL RaiseSignalException end; function TlsGetValue(Key: Integer): Pointer; cdecl; external libpthreadmodulename name 'pthread_getspecific'; { Under Linux, we crawl out from underneath the OS signal handler before we attempt to do anything with the signal. This is because the stack has a bunch of OS frames on there that we cannot possibly unwind from. So we use this routine to accomplish the dispatch, and then another routine to handle the language level of the exception handling. } procedure SignalDispatcher(SigNum: Integer; SigInfo: PSigInfo; UContext: PUserContext); cdecl; type PGeneralRegisters = ^gregset_t; var GeneralRegisters: PGeneralRegisters; begin //DumpContext(SigNum, @context); { Some of the ways that we get here are can lead us to big trouble. For example, if the signal is SIGINT or SIGQUIT, these will commonly be raised to all threads in the process if the user generated them from the keyboard. This is handled well by the Delphi threads, but if a non-Delphi thread lets one of these get by unhandled, terrible things will happen. So we look for that case, and eat SIGINT and SIGQUIT that have been issued on threads that are not Delphi threads. If the signal is a SIGSEGV, or other fatal sort of signal, and the thread that we're running on is not a Delphi thread, then we are completely without options. We have no recovery means, and we have to take the app down hard, right away. } if TlsGetValue(TlsIndex) = nil then begin if (SigNum = SIGINT) or (SigNum = SIGQUIT) then Exit; RunError(232); end; { If we are processing another exception right now, we definitely do not want to be dispatching any exceptions that are async, like SIGINT and SIGQUIT. So we have check to see if OS signals are blocked. If they are, we have to eat this signal right now. } if AreOSExceptionsBlocked and ((SigNum = SIGINT) or (SigNum = SIGQUIT)) then Exit; { If someone wants to delay the handling of SIGINT or SIGQUIT until such time as it's safe to handle it, they set DeferUserInterrupts to True. Then we just set a global variable saying that a SIGINT or SIGQUIT was issued. It is the responsibility of some other body of code at this point to poll for changes to SIG(INT/QUIT)Issued } if DeferUserInterrupts then begin if SigNum = SIGINT then begin SIGINTIssued := True; Exit; end; if SigNum = SIGQUIT then begin SIGQUITIssued := True; Exit; end; end; BlockOSExceptions; GeneralRegisters := @UContext^.uc_mcontext.gregs; GeneralRegisters^[REG_EAX] := GeneralRegisters^[REG_EIP]; GeneralRegisters^[REG_EDX] := UContext^.uc_mcontext.cr2; GeneralRegisters^[REG_ECX] := MapSignal(SigNum, PSigContext(GeneralRegisters)); GeneralRegisters^[REG_EIP] := LongWord(@SignalConverter); end; type TSignalMap = packed record SigNum: Integer; Abandon: Boolean; OldAction: TSigAction; Hooked: Boolean; end; var Signals: array [0..RTL_SIGLAST] of TSignalMap = ( (SigNum: SIGINT;), (SigNum: SIGFPE;), (SigNum: SIGSEGV;), (SigNum: SIGILL;), (SigNum: SIGBUS;), (SigNum: SIGQUIT;) ); function InquireSignal(RtlSigNum: Integer): TSignalState; var Action: TSigAction; begin if sigaction(Signals[RtlSigNum].SigNum, nil, @Action) = -1 then raise Exception.CreateRes(@SSigactionFailed); if (@Action.__sigaction_handler <> @SignalDispatcher) then begin if Signals[RtlSigNum].Hooked then Result := ssOverridden else Result := ssNotHooked; end else Result := ssHooked; end; procedure AbandonSignalHandler(RtlSigNum: Integer); var I: Integer; begin if RtlSigNum = RTL_SIGDEFAULT then begin for I := 0 to RTL_SIGLAST do AbandonSignalHandler(I); Exit; end; Signals[RtlSigNum].Abandon := True; end; procedure HookSignal(RtlSigNum: Integer); var Action: TSigAction; I: Integer; begin if RtlSigNum = RTL_SIGDEFAULT then begin for I := 0 to RTL_SIGLAST do HookSignal(I); Exit; end; FillChar(Action, SizeOf(Action), 0); Action.__sigaction_handler := @SignalDispatcher; Action.sa_flags := SA_SIGINFO; sigaddset(Action.sa_mask, SIGINT); sigaddset(Action.sa_mask, SIGQUIT); if sigaction(Signals[RtlSigNum].SigNum, @Action, @Signals[RtlSigNum].OldAction) = -1 then raise Exception.CreateRes(@SSigactionFailed); Signals[RtlSigNum].Hooked := True; end; procedure UnhookSignal(RtlSigNum: Integer; OnlyIfHooked: Boolean); var I: Integer; begin if RtlSigNum = RTL_SIGDEFAULT then begin for I := 0 to RTL_SIGLAST do UnhookSignal(I, OnlyIfHooked); Exit; end; if not Signals[RtlSigNum].Abandon then begin if OnlyIfHooked and (InquireSignal(RtlSigNum) <> ssHooked) then Exit; if sigaction(Signals[RtlSigNum].SigNum, @Signals[RtlSigNum].OldAction, Nil) = -1 then raise Exception.CreateRes(@SSigactionFailed); Signals[RtlSigNum].Hooked := False; end; end; procedure UnhookOSExceptions; begin if not Assigned(HookOSExceptionsProc) then UnhookSignal(RTL_SIGDEFAULT, True); end; procedure HookOSExceptions; begin if Assigned(HookOSExceptionsProc) then HookOSExceptionsProc else begin HookSignal(RTL_SIGDEFAULT); end; end; {$ENDIF} // LINUX procedure InitExceptions; begin OutOfMemory := EOutOfMemory.CreateRes(SOutOfMemory); InvalidPointer := EInvalidPointer.CreateRes(SInvalidPointer); ErrorProc := ErrorHandler; ExceptProc := @ExceptHandler; ExceptionClass := Exception; {$IFDEF MSWINDOWS} ExceptClsProc := @GetExceptionClass; ExceptObjProc := @GetExceptionObject; {$ENDIF} AssertErrorProc := @AssertErrorHandler; {$IFNDEF PC_MAPPED_EXCEPTIONS} // We don't hook this under PC mapped exceptions, because // we have no idea what the parameters were to the procedure // in question. Hence we cannot hope to unwind the stack in // our handler. Since we just throw an exception from our // handler, that pretty much rules out using this without // exorbitant compiler support. If you do hook AbstractErrorProc, // you must make sure that you never throw an exception from // your handler if PC_MAPPED_EXCEPTIONS is defined. AbstractErrorProc := @AbstractErrorHandler; {$ENDIF} {$IFDEF LINUX} if not IsLibrary then HookOSExceptions; {$ENDIF} end; procedure DoneExceptions; begin if Assigned(OutOfMemory) then begin OutOfMemory.AllowFree := True; OutOfMemory.FreeInstance; OutOfMemory := nil; end; if Assigned(InvalidPointer) then begin InvalidPointer.AllowFree := True; InvalidPointer.Free; InvalidPointer := nil; end; ErrorProc := nil; ExceptProc := nil; ExceptionClass := nil; {$IFDEF MSWINDOWS} ExceptClsProc := nil; ExceptObjProc := nil; {$ENDIF} AssertErrorProc := nil; {$IFDEF LINUX} if not IsLibrary then UnhookOSExceptions; {$ENDIF} end; {$IFDEF MSWINDOWS} procedure InitPlatformId; var OSVersionInfo: TOSVersionInfo; begin OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo); if GetVersionEx(OSVersionInfo) then with OSVersionInfo do begin Win32Platform := dwPlatformId; Win32MajorVersion := dwMajorVersion; Win32MinorVersion := dwMinorVersion; if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then Win32BuildNumber := dwBuildNumber and $FFFF else Win32BuildNumber := dwBuildNumber; Win32CSDVersion := szCSDVersion; end; end; function CheckWin32Version(AMajor: Integer; AMinor: Integer = 0): Boolean; begin Result := (Win32MajorVersion > AMajor) or ((Win32MajorVersion = AMajor) and (Win32MinorVersion >= AMinor)); end; function GetFileVersion(const AFileName: string): Cardinal; var FileName: string; InfoSize, Wnd: DWORD; VerBuf: Pointer; FI: PVSFixedFileInfo; VerSize: DWORD; begin Result := Cardinal(-1); // GetFileVersionInfo modifies the filename parameter data while parsing. // Copy the string const into a local variable to create a writeable copy. FileName := AFileName; UniqueString(FileName); InfoSize := GetFileVersionInfoSize(PChar(FileName), Wnd); if InfoSize <> 0 then begin GetMem(VerBuf, InfoSize); try if GetFileVersionInfo(PChar(FileName), Wnd, InfoSize, VerBuf) then if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then Result:= FI.dwFileVersionMS; finally FreeMem(VerBuf); end; end; end; procedure Beep; begin MessageBeep(0); end; {$ENDIF} {$IFDEF LINUX} procedure Beep; var ch: Char; FileDes: Integer; begin if isatty(STDERR_FILENO) = 1 then FileDes := STDERR_FILENO else if isatty(STDOUT_FILENO) = 1 then FileDes := STDOUT_FILENO else begin // Neither STDERR_FILENO nor STDOUT_FILENO are open // terminals (TTYs). It is not possible to safely // write the beep character. Exit; end; ch := #7; __write(FileDes, ch, 1); end; {$ENDIF} { MBCS functions } function ByteTypeTest(P: PChar; Index: Integer): TMbcsByteType; {$IFDEF MSWINDOWS} var I: Integer; begin Result := mbSingleByte; if (P = nil) or (P[Index] = #$0) then Exit; if (Index = 0) then begin if P[0] in LeadBytes then Result := mbLeadByte; end else begin I := Index - 1; while (I >= 0) and (P[I] in LeadBytes) do Dec(I); if ((Index - I) mod 2) = 0 then Result := mbTrailByte else if P[Index] in LeadBytes then Result := mbLeadByte; end; end; {$ENDIF} {$IFDEF LINUX} var I, L: Integer; begin Result := mbSingleByte; if (P = nil) or (P[Index] = #$0) then Exit; I := 0; repeat if P[I] in LeadBytes then L := StrCharLength(P + I) else L := 1; Inc(I, L); until (I > Index); if (L <> 1) then if (I - L = Index) then Result := mbLeadByte else Result := mbTrailByte; end; {$ENDIF} function ByteType(const S: string; Index: Integer): TMbcsByteType; begin Result := mbSingleByte; if SysLocale.FarEast then Result := ByteTypeTest(PChar(S), Index-1); end; function StrByteType(Str: PChar; Index: Cardinal): TMbcsByteType; begin Result := mbSingleByte; if SysLocale.FarEast then Result := ByteTypeTest(Str, Index); end; function ByteToCharLen(const S: string; MaxLen: Integer): Integer; begin if Length(S) < MaxLen then MaxLen := Length(S); Result := ByteToCharIndex(S, MaxLen); end; function ByteToCharIndex(const S: string; Index: Integer): Integer; var I: Integer; begin Result := 0; if (Index <= 0) or (Index > Length(S)) then Exit; Result := Index; if not SysLocale.FarEast then Exit; I := 1; Result := 0; while I <= Index do begin if S[I] in LeadBytes then I := NextCharIndex(S, I) else Inc(I); Inc(Result); end; end; procedure CountChars(const S: string; MaxChars: Integer; var CharCount, ByteCount: Integer); var C, L, B: Integer; begin L := Length(S); C := 1; B := 1; while (B < L) and (C < MaxChars) do begin Inc(C); if S[B] in LeadBytes then B := NextCharIndex(S, B) else Inc(B); end; if (C = MaxChars) and (B < L) and (S[B] in LeadBytes) then B := NextCharIndex(S, B) - 1; CharCount := C; ByteCount := B; end; function CharToByteIndex(const S: string; Index: Integer): Integer; var Chars: Integer; begin Result := 0; if (Index <= 0) or (Index > Length(S)) then Exit; if (Index > 1) and SysLocale.FarEast then begin CountChars(S, Index-1, Chars, Result); if (Chars < (Index-1)) or (Result >= Length(S)) then Result := 0 // Char index out of range else Inc(Result); end else Result := Index; end; function CharToByteLen(const S: string; MaxLen: Integer): Integer; var Chars: Integer; begin Result := 0; if MaxLen <= 0 then Exit; if MaxLen > Length(S) then MaxLen := Length(S); if SysLocale.FarEast then begin CountChars(S, MaxLen, Chars, Result); if Result > Length(S) then Result := Length(S); end else Result := MaxLen; end; { MBCS Helper functions } function StrCharLength(const Str: PChar): Integer; begin {$IFDEF LINUX} Result := mblen(Str, MB_CUR_MAX); if (Result = -1) then Result := 1; {$ENDIF} {$IFDEF MSWINDOWS} if SysLocale.FarEast then Result := Integer(CharNext(Str)) - Integer(Str) else Result := 1; {$ENDIF} end; function StrNextChar(const Str: PChar): PChar; begin {$IFDEF LINUX} Result := Str + StrCharLength(Str); {$ENDIF} {$IFDEF MSWINDOWS} Result := CharNext(Str); {$ENDIF} end; function CharLength(const S: string; Index: Integer): Integer; begin Result := 1; assert((Index > 0) and (Index <= Length(S))); if SysLocale.FarEast and (S[Index] in LeadBytes) then Result := StrCharLength(PChar(S) + Index - 1); end; function NextCharIndex(const S: string; Index: Integer): Integer; begin Result := Index + 1; assert((Index > 0) and (Index <= Length(S))); if SysLocale.FarEast and (S[Index] in LeadBytes) then Result := Index + StrCharLength(PChar(S) + Index - 1); end; function IsPathDelimiter(const S: string; Index: Integer): Boolean; begin Result := (Index > 0) and (Index <= Length(S)) and (S[Index] = PathDelim) and (ByteType(S, Index) = mbSingleByte); end; function IsDelimiter(const Delimiters, S: string; Index: Integer): Boolean; begin Result := False; if (Index <= 0) or (Index > Length(S)) or (ByteType(S, Index) <> mbSingleByte) then exit; Result := StrScan(PChar(Delimiters), S[Index]) <> nil; end; function IncludeTrailingBackslash(const S: string): string; begin Result := IncludeTrailingPathDelimiter(S); end; function IncludeTrailingPathDelimiter(const S: string): string; begin Result := S; if not IsPathDelimiter(Result, Length(Result)) then Result := Result + PathDelim; end; function ExcludeTrailingBackslash(const S: string): string; begin Result := ExcludeTrailingPathDelimiter(S); end; function ExcludeTrailingPathDelimiter(const S: string): string; begin Result := S; if IsPathDelimiter(Result, Length(Result)) then SetLength(Result, Length(Result)-1); end; function AnsiPos(const Substr, S: string): Integer; var P: PChar; begin Result := 0; P := AnsiStrPos(PChar(S), PChar(SubStr)); if P <> nil then Result := Integer(P) - Integer(PChar(S)) + 1; end; function AnsiCompareFileName(const S1, S2: string): Integer; begin {$IFDEF MSWINDOWS} Result := AnsiCompareStr(AnsiLowerCaseFileName(S1), AnsiLowerCaseFileName(S2)); {$ENDIF} {$IFDEF LINUX} Result := AnsiCompareStr(S1, S2); {$ENDIF} end; function SameFileName(const S1, S2: string): Boolean; begin Result := AnsiCompareFileName(S1, S2) = 0; end; function AnsiLowerCaseFileName(const S: string): string; {$IFDEF MSWINDOWS} var I,L: Integer; begin if SysLocale.FarEast then begin L := Length(S); SetLength(Result, L); I := 1; while I <= L do begin Result[I] := S[I]; if S[I] in LeadBytes then begin Inc(I); Result[I] := S[I]; end else if Result[I] in ['A'..'Z'] then Inc(Byte(Result[I]), 32); Inc(I); end; end else Result := AnsiLowerCase(S); end; {$ENDIF} {$IFDEF LINUX} begin Result := AnsiLowerCase(S); end; {$ENDIF} function AnsiUpperCaseFileName(const S: string): string; {$IFDEF MSWINDOWS} var I,L: Integer; begin if SysLocale.FarEast then begin L := Length(S); SetLength(Result, L); I := 1; while I <= L do begin Result[I] := S[I]; if S[I] in LeadBytes then begin Inc(I); Result[I] := S[I]; end else if Result[I] in ['a'..'z'] then Dec(Byte(Result[I]), 32); Inc(I); end; end else Result := AnsiUpperCase(S); end; {$ENDIF} {$IFDEF LINUX} begin Result := AnsiUpperCase(S); end; {$ENDIF} function AnsiStrPos(Str, SubStr: PChar): PChar; var L1, L2: Cardinal; ByteType : TMbcsByteType; begin Result := nil; if (Str = nil) or (Str^ = #0) or (SubStr = nil) or (SubStr^ = #0) then Exit; L1 := StrLen(Str); L2 := StrLen(SubStr); Result := StrPos(Str, SubStr); while (Result <> nil) and ((L1 - Cardinal(Result - Str)) >= L2) do begin ByteType := StrByteType(Str, Integer(Result-Str)); {$IFDEF MSWINDOWS} if (ByteType <> mbTrailByte) and (CompareString(LOCALE_USER_DEFAULT, 0, Result, L2, SubStr, L2) = CSTR_EQUAL) then Exit; if (ByteType = mbLeadByte) then Inc(Result); {$ENDIF} {$IFDEF LINUX} if (ByteType <> mbTrailByte) and (strncmp(Result, SubStr, L2) = 0) then Exit; {$ENDIF} Inc(Result); Result := StrPos(Result, SubStr); end; Result := nil; end; function AnsiStrRScan(Str: PChar; Chr: Char): PChar; begin Str := AnsiStrScan(Str, Chr); Result := Str; if Chr <> #$0 then begin while Str <> nil do begin Result := Str; Inc(Str); Str := AnsiStrScan(Str, Chr); end; end end; function AnsiStrScan(Str: PChar; Chr: Char): PChar; begin Result := StrScan(Str, Chr); while Result <> nil do begin {$IFDEF MSWINDOWS} case StrByteType(Str, Integer(Result-Str)) of mbSingleByte: Exit; mbLeadByte: Inc(Result); end; {$ENDIF} {$IFDEF LINUX} if StrByteType(Str, Integer(Result-Str)) = mbSingleByte then Exit; {$ENDIF} Inc(Result); Result := StrScan(Result, Chr); end; end; {$IFDEF MSWINDOWS} function LCIDToCodePage(ALcid: LCID): Integer; var Buffer: array [0..6] of Char; begin GetLocaleInfo(ALcid, LOCALE_IDEFAULTANSICODEPAGE, Buffer, SizeOf(Buffer)); Result:= StrToIntDef(Buffer, GetACP); end; {$ENDIF} procedure InitSysLocale; {$IFDEF MSWINDOWS} var DefaultLCID: LCID; DefaultLangID: LANGID; AnsiCPInfo: TCPInfo; // I: Integer; // BufferA: array [128..255] of Char; // BufferW: array [128..256] of Word; // PCharA: PChar; procedure InitLeadBytes; var I: Integer; J: Byte; begin GetCPInfo(CP_ACP, AnsiCPInfo); with AnsiCPInfo do begin I := 0; while (I < MAX_LEADBYTES) and ((LeadByte[I] or LeadByte[I + 1]) <> 0) do begin for J := LeadByte[I] to LeadByte[I + 1] do Include(LeadBytes, Char(J)); Inc(I, 2); end; end; end; begin { Set default to English (US). } SysLocale.DefaultLCID := $0409; SysLocale.PriLangID := LANG_ENGLISH; SysLocale.SubLangID := SUBLANG_ENGLISH_US; DefaultLCID := GetThreadLocale; if DefaultLCID <> 0 then SysLocale.DefaultLCID := DefaultLCID; DefaultLangID := Word(DefaultLCID); if DefaultLangID <> 0 then begin SysLocale.PriLangID := DefaultLangID and $3ff; SysLocale.SubLangID := DefaultLangID shr 10; end; LeadBytes := []; if (Win32MajorVersion > 4) and (Win32Platform = VER_PLATFORM_WIN32_NT) then SysLocale.MiddleEast := True else SysLocale.MiddleEast := GetSystemMetrics(SM_MIDEASTENABLED) <> 0; SysLocale.FarEast := GetSystemMetrics(SM_DBCSENABLED) <> 0; if SysLocale.FarEast then InitLeadBytes; end; {$ENDIF} {$IFDEF LINUX} var I: Integer; buf: array [0..3] of char; begin FillChar(SysLocale, sizeof(SysLocale), 0); SysLocale.FarEast := MB_CUR_MAX <> 1; if not SysLocale.FarEast then Exit; buf[1] := #0; for I := 1 to 255 do begin buf[0] := Chr(I); if mblen(buf, 1) <> 1 then Include(LeadBytes, Char(I)); end; end; {$ENDIF} procedure GetFormatSettings; {$IFDEF MSWINDOWS} var HourFormat, TimePrefix, TimePostfix: string; DefaultLCID: Integer; begin InitSysLocale; GetMonthDayNames; if SysLocale.FarEast then GetEraNamesAndYearOffsets; DefaultLCID := GetThreadLocale; CurrencyString := GetLocaleStr(DefaultLCID, LOCALE_SCURRENCY, ''); CurrencyFormat := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ICURRENCY, '0'), 0); NegCurrFormat := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_INEGCURR, '0'), 0); ThousandSeparator := GetLocaleChar(DefaultLCID, LOCALE_STHOUSAND, ','); DecimalSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDECIMAL, '.'); CurrencyDecimals := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ICURRDIGITS, '0'), 0); DateSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDATE, '/'); ShortDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SSHORTDATE, 'm/d/yy')); LongDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SLONGDATE, 'mmmm d, yyyy')); TimeSeparator := GetLocaleChar(DefaultLCID, LOCALE_STIME, ':'); TimeAMString := GetLocaleStr(DefaultLCID, LOCALE_S1159, 'am'); TimePMString := GetLocaleStr(DefaultLCID, LOCALE_S2359, 'pm'); TimePrefix := ''; TimePostfix := ''; if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITLZERO, '0'), 0) = 0 then HourFormat := 'h' else HourFormat := 'hh'; if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITIME, '0'), 0) = 0 then if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITIMEMARKPOSN, '0'), 0) = 0 then TimePostfix := ' AMPM' else TimePrefix := 'AMPM '; ShortTimeFormat := TimePrefix + HourFormat + ':mm' + TimePostfix; LongTimeFormat := TimePrefix + HourFormat + ':mm:ss' + TimePostfix; ListSeparator := GetLocaleChar(DefaultLCID, LOCALE_SLIST, ','); end; {$ELSE} {$IFDEF LINUX} const //first boolean is p_cs_precedes, second is p_sep_by_space CurrencyFormats: array[boolean, boolean] of byte = ((1, 3),(0, 2)); //first boolean is n_cs_precedes, second is n_sep_by_space and finally n_sign_posn NegCurrFormats: array[boolean, boolean, 0..4] of byte = (((4,5,7,6,7),(15,8,10,13,10)),((0,1,3,1,2),(14,9,11,9,12))); function TranslateFormat(s: PChar; const Default: string): string; begin Result := ''; while s^ <> #0 do begin if s^ = '%' then begin inc(s); case s^ of 'a': Result := Result + 'ddd'; 'A': Result := Result + 'dddd'; 'b': Result := Result + 'MMM'; 'B': Result := Result + 'MMMM'; 'c': Result := Result + 'c'; // 'C': year / 100 not supported 'd': Result := Result + 'dd'; 'D': Result := Result + 'MM/dd/yy'; 'e': Result := Result + 'd'; // 'E': alternate format not supported 'g': Result := Result + 'yy'; 'G': Result := Result + 'yyyy'; 'h': Result := Result + 'MMM'; 'H': Result := Result + 'HH'; 'I': Result := Result + 'hh'; // 'j': day of year not supported 'k': Result := Result + 'H'; 'l': Result := Result + 'h'; 'm': Result := Result + 'MM'; 'M': Result := Result + 'nn'; // minutes! not months! 'n': Result := Result + sLineBreak; // line break // 'O': alternate format not supported 'P', // P's implied lowercasing of locale string is not supported 'p': Result := Result + 'AMPM'; 'r': Result := Result + TranslateFormat(nl_langInfo(T_FMT_AMPM),''); 'R': Result := Result + 'HH:mm'; // 's': number of seconds since Epoch not supported 'S': Result := Result + 'ss'; 't': Result := Result + #9; // tab char 'T': Result := Result + 'HH:mm:ss'; // 'u': day of week 1..7 not supported // 'U': week number of the year not supported // 'V': week number of the year not supported // 'w': day of week 0..6 not supported // 'W': week number of the year not supported 'x': Result := Result + TranslateFormat(nl_langInfo(D_FMT),''); 'X': Result := Result + TranslateFormat(nl_langinfo(T_FMT),''); 'y': Result := Result + 'yy'; 'Y': Result := Result + 'yyyy'; // 'z': GMT offset is not supported '%': Result := Result + '%'; end; end else Result := Result + s^; Inc(s); end; if Result = '' then Result := Default; end; function GetFirstCharacter(const SrcString, match: string): char; var i, p: integer; begin result := match[1]; for i := 1 to length(SrcString) do begin p := Pos(SrcString[i], match); if p > 0 then begin result := match[p]; break; end; end; end; var P: PLConv; begin InitSysLocale; GetMonthDayNames; if SysLocale.FarEast then InitEras; CurrencyString := ''; CurrencyFormat := 0; NegCurrFormat := 0; ThousandSeparator := ','; DecimalSeparator := '.'; CurrencyDecimals := 0; P := localeconv; if P <> nil then begin if P^.currency_symbol <> nil then CurrencyString := P^.currency_symbol; if (Byte(P^.p_cs_precedes) in [0..1]) and (Byte(P^.p_sep_by_space) in [0..1]) then begin CurrencyFormat := CurrencyFormats[P^.p_cs_precedes, P^.p_sep_by_space]; if P^.p_sign_posn in [0..4] then NegCurrFormat := NegCurrFormats[P^.n_cs_precedes, P^.n_sep_by_space, P^.n_sign_posn]; end; // #0 is valid for ThousandSeparator. Indicates no thousand separator. ThousandSeparator := P^.thousands_sep^; // #0 is not valid for DecimalSeparator. if P^.decimal_point <> #0 then DecimalSeparator := P^.decimal_point^; CurrencyDecimals := P^.frac_digits; end; ShortDateFormat := TranslateFormat(nl_langinfo(D_FMT),'m/d/yy'); LongDateFormat := TranslateFormat(nl_langinfo(D_T_FMT), ShortDateFormat); ShortTimeFormat := TranslateFormat(nl_langinfo(T_FMT), 'hh:mm AMPM'); LongTimeFormat := TranslateFormat(nl_langinfo(T_FMT_AMPM), ShortTimeFormat); DateSeparator := GetFirstCharacter(ShortDateFormat, '/.-'); TimeSeparator := GetFirstCharacter(ShortTimeFormat, ':.'); TimeAMString := nl_langinfo(AM_STR); TimePMString := nl_langinfo(PM_STR); ListSeparator := ','; end; {$ELSE} var HourFormat, TimePrefix, TimePostfix: string; begin InitSysLocale; GetMonthDayNames; CurrencyString := ''; CurrencyFormat := 0; NegCurrFormat := 0; ThousandSeparator := ','; DecimalSeparator := '.'; CurrencyDecimals := 0; DateSeparator := '/'; ShortDateFormat := 'm/d/yy'; LongDateFormat := 'mmmm d, yyyy'; TimeSeparator := ':'; TimeAMString := 'am'; TimePMString := 'pm'; TimePrefix := ''; TimePostfix := ''; HourFormat := 'h'; TimePostfix := ' AMPM'; ShortTimeFormat := TimePrefix + HourFormat + ':mm' + TimePostfix; LongTimeFormat := TimePrefix + HourFormat + ':mm:ss' + TimePostfix; ListSeparator := ','; end; {$ENDIF} {$ENDIF} {$IFDEF MSWINDOWS} procedure GetLocaleFormatSettings(LCID: Integer; var FormatSettings: TFormatSettings); var HourFormat, TimePrefix, TimePostfix: string; DefaultLCID: Integer; begin if IsValidLocale(LCID, LCID_INSTALLED) then DefaultLCID := LCID else DefaultLCID := GetThreadLocale; GetLocaleMonthDayNames(LCID, FormatSettings); with FormatSettings do begin CurrencyString := GetLocaleStr(DefaultLCID, LOCALE_SCURRENCY, ''); CurrencyFormat := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ICURRENCY, '0'), 0); NegCurrFormat := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_INEGCURR, '0'), 0); ThousandSeparator := GetLocaleChar(DefaultLCID, LOCALE_STHOUSAND, ','); DecimalSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDECIMAL, '.'); CurrencyDecimals := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ICURRDIGITS, '0'), 0); DateSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDATE, '/'); ShortDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SSHORTDATE, 'm/d/yy')); LongDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SLONGDATE, 'mmmm d, yyyy')); TimeSeparator := GetLocaleChar(DefaultLCID, LOCALE_STIME, ':'); TimeAMString := GetLocaleStr(DefaultLCID, LOCALE_S1159, 'am'); TimePMString := GetLocaleStr(DefaultLCID, LOCALE_S2359, 'pm'); TimePrefix := ''; TimePostfix := ''; if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITLZERO, '0'), 0) = 0 then HourFormat := 'h' else HourFormat := 'hh'; if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITIME, '0'), 0) = 0 then if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITIMEMARKPOSN, '0'), 0) = 0 then TimePostfix := ' AMPM' else TimePrefix := 'AMPM '; ShortTimeFormat := TimePrefix + HourFormat + ':mm' + TimePostfix; LongTimeFormat := TimePrefix + HourFormat + ':mm:ss' + TimePostfix; ListSeparator := GetLocaleChar(DefaultLCID, LOCALE_SLIST, ','); end; end; {$ENDIF} function StringReplace(const S, OldPattern, NewPattern: string; Flags: TReplaceFlags): string; var SearchStr, Patt, NewStr: string; Offset: Integer; begin if rfIgnoreCase in Flags then begin SearchStr := AnsiUpperCase(S); Patt := AnsiUpperCase(OldPattern); end else begin SearchStr := S; Patt := OldPattern; end; NewStr := S; Result := ''; while SearchStr <> '' do begin Offset := AnsiPos(Patt, SearchStr); if Offset = 0 then begin Result := Result + NewStr; Break; end; Result := Result + Copy(NewStr, 1, Offset - 1) + NewPattern; NewStr := Copy(NewStr, Offset + Length(OldPattern), MaxInt); if not (rfReplaceAll in Flags) then begin Result := Result + NewStr; Break; end; SearchStr := Copy(SearchStr, Offset + Length(Patt), MaxInt); end; end; function WrapText(const Line, BreakStr: string; const BreakChars: TSysCharSet; MaxCol: Integer): string; const QuoteChars = ['''', '"']; var Col, Pos: Integer; LinePos, LineLen: Integer; BreakLen, BreakPos: Integer; QuoteChar, CurChar: Char; ExistingBreak: Boolean; L: Integer; begin Col := 1; Pos := 1; LinePos := 1; BreakPos := 0; QuoteChar := #0; ExistingBreak := False; LineLen := Length(Line); BreakLen := Length(BreakStr); Result := ''; while Pos <= LineLen do begin CurChar := Line[Pos]; if CurChar in LeadBytes then begin L := CharLength(Line, Pos) - 1; Inc(Pos, L); Inc(Col, L); end else begin if CurChar in QuoteChars then if QuoteChar = #0 then QuoteChar := CurChar else if CurChar = QuoteChar then QuoteChar := #0; if QuoteChar = #0 then begin if CurChar = BreakStr[1] then begin ExistingBreak := StrLComp(Pointer(BreakStr), Pointer(@Line[Pos]), BreakLen) = 0; if ExistingBreak then begin Inc(Pos, BreakLen-1); BreakPos := Pos; end; end; if not ExistingBreak then if CurChar in BreakChars then BreakPos := Pos; end; end; Inc(Pos); Inc(Col); if not (QuoteChar in QuoteChars) and (ExistingBreak or ((Col > MaxCol) and (BreakPos > LinePos))) then begin Col := 1; Result := Result + Copy(Line, LinePos, BreakPos - LinePos + 1); if not (CurChar in QuoteChars) then begin while Pos <= LineLen do begin if Line[Pos] in BreakChars then begin Inc(Pos); ExistingBreak := False; end else begin if StrLComp(Pointer(@Line[Pos]), sLineBreak, Length(sLineBreak)) = 0 then begin Inc(Pos, Length(sLineBreak)); ExistingBreak := True; end else Break; end; end; end; if (Pos <= LineLen) and not ExistingBreak then Result := Result + BreakStr; Inc(BreakPos); LinePos := BreakPos; Pos := LinePos; ExistingBreak := False; end; end; Result := Result + Copy(Line, LinePos, MaxInt); end; function WrapText(const Line: string; MaxCol: Integer): string; begin Result := WrapText(Line, sLineBreak, [' ', '-', #9], MaxCol); { do not localize } end; function FindCmdLineSwitch(const Switch: string; const Chars: TSysCharSet; IgnoreCase: Boolean): Boolean; var I: Integer; S: string; begin for I := 1 to ParamCount do begin S := ParamStr(I); if (Chars = []) or (S[1] in Chars) then if IgnoreCase then begin if (AnsiCompareText(Copy(S, 2, Maxint), Switch) = 0) then begin Result := True; Exit; end; end else begin if (AnsiCompareStr(Copy(S, 2, Maxint), Switch) = 0) then begin Result := True; Exit; end; end; end; Result := False; end; function FindCmdLineSwitch(const Switch: string): Boolean; begin Result := FindCmdLineSwitch(Switch, SwitchChars, True); end; function FindCmdLineSwitch(const Switch: string; IgnoreCase: Boolean): Boolean; begin Result := FindCmdLineSwitch(Switch, SwitchChars, IgnoreCase); end; { Package info structures } type PPkgName = ^TPkgName; TPkgName = packed record HashCode: Byte; Name: array[0..255] of Char; end; { PackageUnitFlags: bit meaning ----------------------------------------------------------------------------------------- 0 | main unit 1 | package unit (dpk source) 2 | $WEAKPACKAGEUNIT unit 3 | original containment of $WEAKPACKAGEUNIT (package into which it was compiled) 4 | implicitly imported 5..7 | reserved } PUnitName = ^TUnitName; TUnitName = packed record Flags : Byte; HashCode: Byte; Name: array[0..255] of Char; end; { Package flags: bit meaning ----------------------------------------------------------------------------------------- 0 | 1: never-build 0: always build 1 | 1: design-time only 0: not design-time only on => bit 2 = off 2 | 1: run-time only 0: not run-time only on => bit 1 = off 3 | 1: do not check for dup units 0: perform normal dup unit check 4..25 | reserved 26..27| (producer) 0: pre-V4, 1: undefined, 2: c++, 3: Pascal 28..29| reserved 30..31| 0: EXE, 1: Package DLL, 2: Library DLL, 3: undefined } PPackageInfoHeader = ^TPackageInfoHeader; TPackageInfoHeader = packed record Flags: Cardinal; RequiresCount: Integer; {Requires: array[0..9999] of TPkgName; ContainsCount: Integer; Contains: array[0..9999] of TUnitName;} end; function PackageInfoTable(Module: HMODULE): PPackageInfoHeader; var ResInfo: HRSRC; Data: THandle; begin Result := nil; ResInfo := FindResource(Module, 'PACKAGEINFO', RT_RCDATA); if ResInfo <> 0 then begin Data := LoadResource(Module, ResInfo); if Data <> 0 then try Result := LockResource(Data); UnlockResource(Data); finally FreeResource(Data); end; end; end; function GetModuleName(Module: HMODULE): string; var ModName: array[0..MAX_PATH] of Char; begin SetString(Result, ModName, GetModuleFileName(Module, ModName, SizeOf(ModName))); end; var Reserved: Integer; procedure CheckForDuplicateUnits(Module: HMODULE); var ModuleFlags: Cardinal; function IsUnitPresent(HC: Byte; UnitName: PChar; Module: HMODULE; const ModuleName: string; var UnitPackage: string): Boolean; var I: Integer; InfoTable: PPackageInfoHeader; LibModule: PLibModule; PkgName: PPkgName; UName : PUnitName; Count: Integer; begin Result := True; if (StrIComp(UnitName, 'SysInit') <> 0) and (StrIComp(UnitName, PChar(ModuleName)) <> 0) then begin LibModule := LibModuleList; while LibModule <> nil do begin if LibModule.Instance <> Cardinal(Module) then begin InfoTable := PackageInfoTable(HMODULE(LibModule.Instance)); if (InfoTable <> nil) and (InfoTable.Flags and pfModuleTypeMask = pfPackageModule) and ((InfoTable.Flags and pfIgnoreDupUnits) = (ModuleFlags and pfIgnoreDupUnits)) then begin PkgName := PPkgName(Integer(InfoTable) + SizeOf(InfoTable^)); Count := InfoTable.RequiresCount; { Skip the Requires list } for I := 0 to Count - 1 do Inc(Integer(PkgName), StrLen(PkgName.Name) + 2); Count := Integer(Pointer(PkgName)^); UName := PUnitName(Integer(PkgName) + 4); for I := 0 to Count - 1 do begin with UName^ do // Test Flags to ignore weak package units if ((HashCode = HC) or (HashCode = 0) or (HC = 0)) and ((Flags and $06) = 0) and (StrIComp(UnitName, Name) = 0) then begin UnitPackage := ChangeFileExt(ExtractFileName( GetModuleName(HMODULE(LibModule.Instance))), ''); Exit; end; Inc(Integer(UName), StrLen(UName.Name) + 3); end; end; end; LibModule := LibModule.Next; end; end; Result := False; end; function FindLibModule(Module: HModule): PLibModule; begin Result := LibModuleList; while Result <> nil do begin if Result.Instance = Cardinal(Module) then Exit; Result := Result.Next; end; end; procedure InternalUnitCheck(Module: HModule); var I: Integer; InfoTable: PPackageInfoHeader; UnitPackage: string; ModuleName: string; PkgName: PPkgName; UName: PUnitName; Count: Integer; LibModule: PLibModule; begin InfoTable := PackageInfoTable(Module); if (InfoTable <> nil) and (InfoTable.Flags and pfModuleTypeMask = pfPackageModule) then begin if ModuleFlags = 0 then ModuleFlags := InfoTable.Flags; ModuleName := ChangeFileExt(ExtractFileName(GetModuleName(Module)), ''); PkgName := PPkgName(Integer(InfoTable) + SizeOf(InfoTable^)); Count := InfoTable.RequiresCount; for I := 0 to Count - 1 do begin with PkgName^ do {$IFDEF MSWINDOWS} InternalUnitCheck(GetModuleHandle(PChar(ChangeFileExt(Name, '.bpl')))); {$ENDIF} {$IFDEF LINUX} InternalUnitCheck(GetModuleHandle(Name)); {$ENDIF} Inc(Integer(PkgName), StrLen(PkgName.Name) + 2); end; LibModule := FindLibModule(Module); if (LibModule = nil) or ((LibModule <> nil) and (LibModule.Reserved <> Reserved)) then begin if LibModule <> nil then LibModule.Reserved := Reserved; Count := Integer(Pointer(PkgName)^); UName := PUnitName(Integer(PkgName) + 4); for I := 0 to Count - 1 do begin with UName^ do // Test Flags to ignore weak package units if ((Flags and ufWeakPackageUnit) = 0 ) and IsUnitPresent(HashCode, Name, Module, ModuleName, UnitPackage) then raise EPackageError.CreateResFmt(SDuplicatePackageUnit, [ModuleName, Name, UnitPackage]); Inc(Integer(UName), StrLen(UName.Name) + 3); end; end; end; end; begin Inc(Reserved); ModuleFlags := 0; InternalUnitCheck(Module); end; {$IFDEF LINUX} function LoadLibrary(ModuleName: PChar): HMODULE; begin Result := HMODULE(dlopen(ModuleName, RTLD_LAZY)); end; function FreeLibrary(Module: HMODULE): LongBool; begin Result := LongBool(dlclose(Pointer(Module))); end; function GetProcAddress(Module: HMODULE; Proc: PChar): Pointer; var Info: TDLInfo; Error: PChar; ModHandle: HMODULE; begin // dlsym doesn't clear the error state when the function succeeds dlerror; Result := dlsym(Pointer(Module), Proc); Error := dlerror; if Error <> nil then Result := nil else if dladdr(Result, Info) <> 0 then begin { In glibc 2.1.3 and earlier, dladdr returns a nil dli_fname for addresses in the main program file. In glibc 2.1.91 and later, dladdr fills in the dli_fname for addresses in the main program file, but dlopen will segfault when given the main program file name. Workaround: Check the symbol base address against the main program file's base address, and only call dlopen with a nil filename to get the module name of the main program. } if Info.dli_fbase = ExeBaseAddress then Info.dli_fname := nil; ModHandle := HMODULE(dlopen(Info.dli_fname, RTLD_LAZY)); if ModHandle <> 0 then begin dlclose(Pointer(ModHandle)); if ModHandle <> Module then Result := nil; end; end else Result := nil; end; type plink_map = ^link_map; link_map = record l_addr: Pointer; l_name: PChar; l_ld: Pointer; l_next, l_prev: plink_map; end; pr_debug = ^r_debug; r_debug = record r_version: Integer; r_map: plink_map; r_brk: Pointer; r_state: Integer; r_ldbase: Pointer; end; var _r_debug: pr_debug = nil; function ScanLinkMap(Func: Pointer): plink_map; var linkmap: plink_map; function Eval(linkmap: plink_map; Func: Pointer): Boolean; asm // MOV ECX,[EBP] PUSH EBP CALL EDX POP ECX end; begin if _r_debug = nil then _r_debug := dlsym(RTLD_DEFAULT, '_r_debug'); if _r_debug = nil then begin Assert(False, 'Unable to locate ''_r_debug'' symbol'); // do not localize Result := nil; Exit; end; linkmap := _r_debug.r_map; while linkmap <> nil do begin if not Eval(linkmap, Func) then Break; linkmap := linkmap.l_next; end; Result := linkmap; end; function InitModule(linkmap: plink_map): HMODULE; begin if linkmap <> nil then begin Result := HMODULE(dlopen(linkmap.l_name, RTLD_LAZY)); if Result <> 0 then dlclose(Pointer(Result)); end else Result := 0; end; function GetModuleHandle(ModuleName: PChar): HMODULE; function CheckModuleName(linkmap: plink_map): Boolean; var BaseName: PChar; begin Result := True; if ((ModuleName = nil) and ((linkmap.l_name = nil) or (linkmap.l_name[0] = #0))) or ((ModuleName[0] = PathDelim) and (StrComp(ModuleName, linkmap.l_name) = 0)) then begin Result := False; Exit; end else begin // Locate the start of the actual filename BaseName := StrRScan(linkmap.l_name, PathDelim); if BaseName = nil then BaseName := linkmap.l_name else Inc(BaseName); // The filename is actually located at BaseName+1 if StrComp(ModuleName, BaseName) = 0 then begin Result := False; Exit; end; end; end; begin Result := InitModule(ScanLinkMap(@CheckModuleName)); end; function GetPackageModuleHandle(PackageName: PChar): HMODULE; var PkgName: array[0..MAX_PATH] of Char; function CheckPackageName(linkmap: plink_map): Boolean; var BaseName: PChar; begin Result := True; if linkmap.l_name <> nil then begin // Locate the start of the actual filename BaseName := StrRScan(linkmap.l_name, PathDelim); if BaseName = nil then BaseName := linkmap.l_name // If there is no path info, just use the whole name else Inc(BaseName); // The filename is actually located at BaseName+1 Result := StrPos(BaseName, PkgName) = nil; end; end; procedure MakePkgName(Prefix, Name: PChar); begin StrCopy(PkgName, Prefix); StrLCat(PkgName, Name, sizeof(PkgName)-1); PkgName[High(PkgName)] := #0; end; begin if (PackageName = nil) or (StrScan(PackageName, PathDelim) <> nil) then Result := 0 else begin MakePkgName('bpl', PackageName); // First check the default prefix Result := InitModule(ScanLinkMap(@CheckPackageName)); if Result = 0 then begin MakePkgName('dcl', PackageName); // Next check the design-time prefix Result := InitModule(ScanLinkMap(@CheckPackageName)); if Result = 0 then begin MakePkgName('', PackageName); // finally check without a prefix Result := InitModule(ScanLinkMap(@CheckPackageName)); end; end; end; end; {$ENDIF} {$IFDEF MSWINDOWS} procedure Sleep; external kernel32 name 'Sleep'; stdcall; {$ENDIF} {$IFDEF LINUX} procedure Sleep(milliseconds: Cardinal); begin usleep(milliseconds * 1000); // usleep is in microseconds end; {$ENDIF} { InitializePackage } procedure InitializePackage(Module: HMODULE); type TPackageLoad = procedure; var PackageLoad: TPackageLoad; begin CheckForDuplicateUnits(Module); @PackageLoad := GetProcAddress(Module, 'Initialize'); //Do not localize if Assigned(PackageLoad) then PackageLoad else raise EPackageError.CreateFmt(sInvalidPackageFile, [GetModuleName(Module)]); end; { FinalizePackage } procedure FinalizePackage(Module: HMODULE); type TPackageUnload = procedure; var PackageUnload: TPackageUnload; begin @PackageUnload := GetProcAddress(Module, 'Finalize'); //Do not localize if Assigned(PackageUnload) then PackageUnload else raise EPackageError.CreateRes(sInvalidPackageHandle); end; { LoadPackage } function LoadPackage(const Name: string): HMODULE; {$IFDEF LINUX} var DLErrorMsg: string; {$ENDIF} begin {$IFDEF MSWINDOWS} Result := SafeLoadLibrary(Name); {$ENDIF} {$IFDEF LINUX} Result := HMODULE(dlOpen(PChar(Name), PkgLoadingMode)); {$ENDIF} if Result = 0 then begin {$IFDEF LINUX} DLErrorMsg := dlerror; {$ENDIF} raise EPackageError.CreateResFmt(sErrorLoadingPackage, [Name, {$IFDEF MSWINDOWS}SysErrorMessage(GetLastError){$ENDIF} {$IFDEF LINUX}DLErrorMsg{$ENDIF}]); end; try InitializePackage(Result); except {$IFDEF MSWINDOWS} FreeLibrary(Result); {$ENDIF} {$IFDEF LINUX} dlclose(Pointer(Result)); {$ENDIF} raise; end; end; { UnloadPackage } procedure UnloadPackage(Module: HMODULE); begin FinalizePackage(Module); {$IFDEF MSWINDOWS} FreeLibrary(Module); {$ENDIF} {$IFDEF LINUX} dlclose(Pointer(Module)); InvalidateModuleCache; {$ENDIF} end; { GetPackageInfo } procedure GetPackageInfo(Module: HMODULE; Param: Pointer; var Flags: Integer; InfoProc: TPackageInfoProc); var InfoTable: PPackageInfoHeader; I: Integer; PkgName: PPkgName; UName: PUnitName; Count: Integer; begin InfoTable := PackageInfoTable(Module); if not Assigned(InfoTable) then raise EPackageError.CreateFmt(SCannotReadPackageInfo, [ExtractFileName(GetModuleName(Module))]); Flags := InfoTable.Flags; with InfoTable^ do begin PkgName := PPkgName(Integer(InfoTable) + SizeOf(InfoTable^)); Count := RequiresCount; for I := 0 to Count - 1 do begin InfoProc(PkgName.Name, ntRequiresPackage, 0, Param); Inc(Integer(PkgName), StrLen(PkgName.Name) + 2); end; Count := Integer(Pointer(PkgName)^); UName := PUnitName(Integer(PkgName) + 4); for I := 0 to Count - 1 do begin InfoProc(UName.Name, ntContainsUnit, UName.Flags, Param); Inc(Integer(UName), StrLen(UName.Name) + 3); end; if Flags and pfPackageModule <> 0 then begin PkgName := PPkgName(UName); InfoProc(PkgName.Name, ntDcpBpiName, 0, Param); end; end; end; function GetPackageDescription(ModuleName: PChar): string; var ResModule: HMODULE; ResInfo: HRSRC; ResData: HGLOBAL; {$IFDEF LINUX} DLErrorMsg: string; {$ENDIF} begin Result := ''; ResModule := LoadResourceModule(ModuleName); if ResModule = 0 then begin {$IFDEF MSWINDOWS} ResModule := LoadLibraryEx(ModuleName, 0, LOAD_LIBRARY_AS_DATAFILE); {$ENDIF} {$IFDEF LINUX} ResModule := HMODULE(dlopen(ModuleName, RTLD_LAZY)); {$ENDIF} if ResModule = 0 then begin {$IFDEF LINUX} DLErrorMsg := dlerror; {$ENDIF} raise EPackageError.CreateResFmt(sErrorLoadingPackage, [ModuleName, {$IFDEF MSWINDOWS}SysErrorMessage(GetLastError){$ENDIF} {$IFDEF LINUX}DLErrorMsg{$ENDIF}]); end; end; try ResInfo := FindResource(ResModule, 'DESCRIPTION', RT_RCDATA); if ResInfo <> 0 then begin ResData := LoadResource(ResModule, ResInfo); if ResData <> 0 then try Result := PWideChar(LockResource(ResData)); UnlockResource(ResData); finally FreeResource(ResData); end; end; finally {$IFDEF MSWINDOWS} FreeLibrary(ResModule); {$ENDIF} {$IFDEF LINUX} dlclose(Pointer(ResModule)); {$ENDIF} end; end; procedure RaiseLastOSError; begin RaiseLastOSError(GetLastError); end; procedure RaiseLastOSError(LastError: Integer); var Error: EOSError; begin if LastError <> 0 then Error := EOSError.CreateResFmt(SOSError, [LastError, SysErrorMessage(LastError)]) else Error := EOSError.CreateRes(SUnkOSError); Error.ErrorCode := LastError; raise Error; end; {$IFDEF MSWINDOWS} { RaiseLastWin32Error } procedure RaiseLastWin32Error; begin RaiseLastOSError; end; { Win32Check } function Win32Check(RetVal: BOOL): BOOL; begin if not RetVal then RaiseLastOSError; Result := RetVal; end; {$ENDIF} type PTerminateProcInfo = ^TTerminateProcInfo; TTerminateProcInfo = record Next: PTerminateProcInfo; Proc: TTerminateProc; end; var TerminateProcList: PTerminateProcInfo = nil; procedure AddTerminateProc(TermProc: TTerminateProc); var P: PTerminateProcInfo; begin New(P); P^.Next := TerminateProcList; P^.Proc := TermProc; TerminateProcList := P; end; function CallTerminateProcs: Boolean; var PI: PTerminateProcInfo; begin Result := True; PI := TerminateProcList; while Result and (PI <> nil) do begin Result := PI^.Proc; PI := PI^.Next; end; end; procedure FreeTerminateProcs; var PI: PTerminateProcInfo; begin while TerminateProcList <> nil do begin PI := TerminateProcList; TerminateProcList := PI^.Next; Dispose(PI); end; end; { --- } function AL1(const P): LongWord; asm MOV EDX,DWORD PTR [P] XOR EDX,DWORD PTR [P+4] XOR EDX,DWORD PTR [P+8] XOR EDX,DWORD PTR [P+12] MOV EAX,EDX end; function AL2(const P): LongWord; asm MOV EDX,DWORD PTR [P] ROR EDX,5 XOR EDX,DWORD PTR [P+4] ROR EDX,5 XOR EDX,DWORD PTR [P+8] ROR EDX,5 XOR EDX,DWORD PTR [P+12] MOV EAX,EDX end; const AL1s: array[0..3] of LongWord = ($FFFFFFF0, $FFFFEBF0, 0, $FFFFFFFF); AL2s: array[0..3] of LongWord = ($42C3ECEF, $20F7AEB6, $D1C2F74E, $3F6574DE); procedure ALV; begin raise Exception.CreateRes(SNL); end; function ALR: Pointer; var LibModule: PLibModule; begin if MainInstance <> 0 then Result := Pointer(LoadResource(MainInstance, FindResource(MainInstance, 'DVCLAL', RT_RCDATA))) else begin Result := nil; LibModule := LibModuleList; while LibModule <> nil do begin with LibModule^ do begin Result := Pointer(LoadResource(Instance, FindResource(Instance, 'DVCLAL', RT_RCDATA))); if Result <> nil then Break; end; LibModule := LibModule.Next; end; end; end; function GDAL: LongWord; type TDVCLAL = array[0..3] of LongWord; PDVCLAL = ^TDVCLAL; var P: Pointer; A1, A2: LongWord; PAL1s, PAL2s: PDVCLAL; ALOK: Boolean; begin P := ALR; if P <> nil then begin A1 := AL1(P^); A2 := AL2(P^); Result := A1; PAL1s := @AL1s; PAL2s := @AL2s; ALOK := ((A1 = PAL1s[0]) and (A2 = PAL2s[0])) or ((A1 = PAL1s[1]) and (A2 = PAL2s[1])) or ((A1 = PAL1s[2]) and (A2 = PAL2s[2])); FreeResource(Integer(P)); if not ALOK then ALV; end else Result := AL1s[3]; end; procedure RCS; var P: Pointer; ALOK: Boolean; begin P := ALR; if P <> nil then begin ALOK := (AL1(P^) = AL1s[2]) and (AL2(P^) = AL2s[2]); FreeResource(Integer(P)); end else ALOK := False; if not ALOK then ALV; end; procedure RPR; var AL: LongWord; begin AL := GDAL; if (AL <> AL1s[1]) and (AL <> AL1s[2]) then ALV; end; {$IFDEF MSWINDOWS} procedure InitDriveSpacePtr; var Kernel: THandle; begin Kernel := GetModuleHandle(Windows.Kernel32); if Kernel <> 0 then @GetDiskFreeSpaceEx := GetProcAddress(Kernel, 'GetDiskFreeSpaceExA'); if not Assigned(GetDiskFreeSpaceEx) then GetDiskFreeSpaceEx := @BackfillGetDiskFreeSpaceEx; end; {$ENDIF} // Win95 does not return the actual value of the result. // These implementations are consistent on all platforms. function InterlockedIncrement(var I: Integer): Integer; asm MOV EDX,1 XCHG EAX,EDX LOCK XADD [EDX],EAX INC EAX end; function InterlockedDecrement(var I: Integer): Integer; asm MOV EDX,-1 XCHG EAX,EDX LOCK XADD [EDX],EAX DEC EAX end; function InterlockedExchange(var A: Integer; B: Integer): Integer; asm XCHG [EAX],EDX MOV EAX,EDX end; // The InterlockedExchangeAdd Win32 API is not available on Win95. function InterlockedExchangeAdd(var A: Integer; B: Integer): Integer; asm XCHG EAX,EDX LOCK XADD [EDX],EAX end; { TSimpleRWSync } constructor TSimpleRWSync.Create; begin inherited Create; InitializeCriticalSection(FLock); end; destructor TSimpleRWSync.Destroy; begin inherited Destroy; DeleteCriticalSection(FLock); end; function TSimpleRWSync.BeginWrite: Boolean; begin EnterCriticalSection(FLock); Result := True; end; procedure TSimpleRWSync.EndWrite; begin LeaveCriticalSection(FLock); end; procedure TSimpleRWSync.BeginRead; begin EnterCriticalSection(FLock); end; procedure TSimpleRWSync.EndRead; begin LeaveCriticalSection(FLock); end; { TThreadLocalCounter } const Alive = High(Integer); destructor TThreadLocalCounter.Destroy; var P, Q: PThreadInfo; I: Integer; begin for I := 0 to High(FHashTable) do begin P := FHashTable[I]; FHashTable[I] := nil; while P <> nil do begin Q := P; P := P^.Next; FreeMem(Q); end; end; inherited Destroy; end; function TThreadLocalCounter.HashIndex: Byte; var H: Word; begin H := Word(GetCurrentThreadID); Result := (WordRec(H).Lo xor WordRec(H).Hi) and 15; end; procedure TThreadLocalCounter.Open(var Thread: PThreadInfo); var P: PThreadInfo; CurThread: Cardinal; H: Byte; begin H := HashIndex; CurThread := GetCurrentThreadID; P := FHashTable[H]; while (P <> nil) and (P.ThreadID <> CurThread) do P := P.Next; if P = nil then begin P := Recycle; if P = nil then begin P := PThreadInfo(AllocMem(sizeof(TThreadInfo))); P.ThreadID := CurThread; P.Active := Alive; // Another thread could start traversing the list between when we set the // head to P and when we assign to P.Next. Initializing P.Next to point // to itself will make others spin until we assign the tail to P.Next. P.Next := P; P.Next := PThreadInfo(InterlockedExchange(Integer(FHashTable[H]), Integer(P))); end; end; Thread := P; end; procedure TThreadLocalCounter.Close(var Thread: PThreadInfo); begin Thread := nil; end; procedure TThreadLocalCounter.Delete(var Thread: PThreadInfo); begin Thread.ThreadID := 0; Thread.Active := 0; end; function TThreadLocalCounter.Recycle: PThreadInfo; var Gen: Integer; begin Result := FHashTable[HashIndex]; while (Result <> nil) do begin Gen := InterlockedExchange(Result.Active, Alive); if Gen <> Alive then begin Result.ThreadID := GetCurrentThreadID; Exit; end else Result := Result.Next; end; end; {$IFDEF MSWINDOWS} { TMultiReadExclusiveWriteSynchronizer } const mrWriteRequest = $FFFF; // 65535 concurrent read requests (threads) // 32768 concurrent write requests (threads) // only one write lock at a time // 2^32 lock recursions per thread (read and write combined) constructor TMultiReadExclusiveWriteSynchronizer.Create; begin inherited Create; FSentinel := mrWriteRequest; FReadSignal := CreateEvent(nil, True, True, nil); // manual reset, start signaled FWriteSignal := CreateEvent(nil, False, False, nil); // auto reset, start blocked FWaitRecycle := INFINITE; tls := TThreadLocalCounter.Create; end; destructor TMultiReadExclusiveWriteSynchronizer.Destroy; begin BeginWrite; inherited Destroy; CloseHandle(FReadSignal); CloseHandle(FWriteSignal); tls.Free; end; procedure TMultiReadExclusiveWriteSynchronizer.BlockReaders; begin ResetEvent(FReadSignal); end; procedure TMultiReadExclusiveWriteSynchronizer.UnblockReaders; begin SetEvent(FReadSignal); end; procedure TMultiReadExclusiveWriteSynchronizer.UnblockOneWriter; begin SetEvent(FWriteSignal); end; procedure TMultiReadExclusiveWriteSynchronizer.WaitForReadSignal; begin WaitForSingleObject(FReadSignal, FWaitRecycle); end; procedure TMultiReadExclusiveWriteSynchronizer.WaitForWriteSignal; begin WaitForSingleObject(FWriteSignal, FWaitRecycle); end; {$IFDEF DEBUG_MREWS} var x: Integer; procedure TMultiReadExclusiveWriteSynchronizer.Debug(const Msg: string); begin OutputDebugString(PChar(Format('%d %s Thread=%x Sentinel=%d, FWriterID=%x', [InterlockedIncrement(x), Msg, GetCurrentThreadID, FSentinel, FWriterID]))); end; {$ENDIF} function TMultiReadExclusiveWriteSynchronizer.BeginWrite: Boolean; var Thread: PThreadInfo; HasReadLock: Boolean; ThreadID: Cardinal; Test: Integer; OldRevisionLevel: Cardinal; begin { States of FSentinel (roughly - during inc/dec's, the states may not be exactly what is said here): mrWriteRequest: A reader or a writer can get the lock 1 - (mrWriteRequest-1): A reader (possibly more than one) has the lock 0: A writer (possibly) just got the lock, if returned from the main write While loop < 0, but not a multiple of mrWriteRequest: Writer(s) want the lock, but reader(s) have it. New readers should be blocked, but current readers should be able to call BeginRead < 0, but a multiple of mrWriteRequest: Writer(s) waiting for a writer to finish } {$IFDEF DEBUG_MREWS} Debug('Write enter------------------------------------'); {$ENDIF} Result := True; ThreadID := GetCurrentThreadID; if FWriterID <> ThreadID then // somebody or nobody has a write lock begin // Prevent new readers from entering while we wait for the existing readers // to exit. BlockReaders; OldRevisionLevel := FRevisionLevel; tls.Open(Thread); // We have another lock already. It must be a read lock, because if it // were a write lock, FWriterID would be our threadid. HasReadLock := Thread.RecursionCount > 0; if HasReadLock then // acquiring a write lock requires releasing read locks InterlockedIncrement(FSentinel); {$IFDEF DEBUG_MREWS} Debug('Write before loop'); {$ENDIF} // InterlockedExchangeAdd returns prev value while InterlockedExchangeAdd(FSentinel, -mrWriteRequest) <> mrWriteRequest do begin {$IFDEF DEBUG_MREWS} Debug('Write loop'); Sleep(1000); // sleep to force / debug race condition Debug('Write loop2a'); {$ENDIF} // Undo what we did, since we didn't get the lock Test := InterlockedExchangeAdd(FSentinel, mrWriteRequest); // If the old value (in Test) was 0, then we may be able to // get the lock (because it will now be mrWriteRequest). So, // we continue the loop to find out. Otherwise, we go to sleep, // waiting for a reader or writer to signal us. if Test <> 0 then begin {$IFDEF DEBUG_MREWS} Debug('Write starting to wait'); {$ENDIF} WaitForWriteSignal; end {$IFDEF DEBUG_MREWS} else Debug('Write continue') {$ENDIF} end; // At the EndWrite, first Writers are awoken, and then Readers are awoken. // If a Writer got the lock, we don't want the readers to do busy // waiting. This Block resets the event in case the situation happened. BlockReaders; // Put our read lock marker back before we lose track of it if HasReadLock then InterlockedDecrement(FSentinel); FWriterID := ThreadID; Result := Integer(OldRevisionLevel) = (InterlockedIncrement(Integer(FRevisionLevel)) - 1); end; Inc(FWriteRecursionCount); {$IFDEF DEBUG_MREWS} Debug('Write lock-----------------------------------'); {$ENDIF} end; procedure TMultiReadExclusiveWriteSynchronizer.EndWrite; var Thread: PThreadInfo; begin {$IFDEF DEBUG_MREWS} Debug('Write end'); {$ENDIF} assert(FWriterID = GetCurrentThreadID); tls.Open(Thread); Dec(FWriteRecursionCount); if FWriteRecursionCount = 0 then begin FWriterID := 0; InterlockedExchangeAdd(FSentinel, mrWriteRequest); {$IFDEF DEBUG_MREWS} Debug('Write about to UnblockOneWriter'); {$ENDIF} UnblockOneWriter; {$IFDEF DEBUG_MREWS} Debug('Write about to UnblockReaders'); {$ENDIF} UnblockReaders; end; if Thread.RecursionCount = 0 then tls.Delete(Thread); {$IFDEF DEBUG_MREWS} Debug('Write unlock'); {$ENDIF} end; procedure TMultiReadExclusiveWriteSynchronizer.BeginRead; var Thread: PThreadInfo; WasRecursive: Boolean; SentValue: Integer; begin {$IFDEF DEBUG_MREWS} Debug('Read enter'); {$ENDIF} tls.Open(Thread); Inc(Thread.RecursionCount); WasRecursive := Thread.RecursionCount > 1; if FWriterID <> GetCurrentThreadID then begin {$IFDEF DEBUG_MREWS} Debug('Trying to get the ReadLock (we did not have a write lock)'); {$ENDIF} // In order to prevent recursive Reads from causing deadlock, // we need to always WaitForReadSignal if not recursive. // This prevents unnecessarily decrementing the FSentinel, and // then immediately incrementing it again. if not WasRecursive then begin // Make sure we don't starve writers. A writer will // always set the read signal when it is done, and it is initially on. WaitForReadSignal; while (InterlockedDecrement(FSentinel) <= 0) do begin {$IFDEF DEBUG_MREWS} Debug('Read loop'); {$ENDIF} // Because the InterlockedDecrement happened, it is possible that // other threads "think" we have the read lock, // even though we really don't. If we are the last reader to do this, // then SentValue will become mrWriteRequest SentValue := InterlockedIncrement(FSentinel); // So, if we did inc it to mrWriteRequest at this point, // we need to signal the writer. if SentValue = mrWriteRequest then UnblockOneWriter; // This sleep below prevents starvation of writers Sleep(0); {$IFDEF DEBUG_MREWS} Debug('Read loop2 - waiting to be signaled'); {$ENDIF} WaitForReadSignal; {$IFDEF DEBUG_MREWS} Debug('Read signaled'); {$ENDIF} end; end; end; {$IFDEF DEBUG_MREWS} Debug('Read lock'); {$ENDIF} end; procedure TMultiReadExclusiveWriteSynchronizer.EndRead; var Thread: PThreadInfo; Test: Integer; begin {$IFDEF DEBUG_MREWS} Debug('Read end'); {$ENDIF} tls.Open(Thread); Dec(Thread.RecursionCount); if (Thread.RecursionCount = 0) then begin tls.Delete(Thread); // original code below commented out if (FWriterID <> GetCurrentThreadID) then begin Test := InterlockedIncrement(FSentinel); // It is possible for Test to be mrWriteRequest // or, it can be = 0, if the write loops: // Test := InterlockedExchangeAdd(FSentinel, mrWriteRequest) + mrWriteRequest; // Did not get executed before this has called (the sleep debug makes it happen faster) {$IFDEF DEBUG_MREWS} Debug(Format('Read UnblockOneWriter may be called. Test=%d', [Test])); {$ENDIF} if Test = mrWriteRequest then UnblockOneWriter else if Test <= 0 then // We may have some writers waiting begin if (Test mod mrWriteRequest) = 0 then UnblockOneWriter; // No more readers left (only writers) so signal one of them end; end; end; {$IFDEF DEBUG_MREWS} Debug('Read unlock'); {$ENDIF} end; {$ENDIF} //MSWINDOWS for TMultiReadExclusiveWriteSynchronizer procedure FreeAndNil(var Obj); var Temp: TObject; begin Temp := TObject(Obj); Pointer(Obj) := nil; Temp.Free; end; { Interface support routines } function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean; begin Result := (Instance <> nil) and (Instance.QueryInterface(IID, Intf) = 0); end; function Supports(const Instance: TObject; const IID: TGUID; out Intf): Boolean; var LUnknown: IUnknown; begin Result := (Instance <> nil) and ((Instance.GetInterface(IUnknown, LUnknown) and Supports(LUnknown, IID, Intf)) or Instance.GetInterface(IID, Intf)); end; function Supports(const Instance: IInterface; const IID: TGUID): Boolean; var Temp: IInterface; begin Result := Supports(Instance, IID, Temp); end; function Supports(const Instance: TObject; const IID: TGUID): Boolean; var Temp: IInterface; begin Result := Supports(Instance, IID, Temp); end; function Supports(const AClass: TClass; const IID: TGUID): Boolean; begin Result := AClass.GetInterfaceEntry(IID) <> nil; end; {$IFDEF MSWINDOWS} { TLanguages } var FTempLanguages: TLanguages; function EnumLocalesCallback(LocaleID: PChar): Integer; stdcall; begin Result := FTempLanguages.LocalesCallback(LocaleID); end; { Query the OS for information for a specified locale. Unicode version. Works correctly on Asian WinNT. } function GetLocaleDataW(ID: LCID; Flag: DWORD): string; var Buffer: array[0..1023] of WideChar; begin Buffer[0] := #0; GetLocaleInfoW(ID, Flag, Buffer, SizeOf(Buffer) div 2); Result := Buffer; end; { Query the OS for information for a specified locale. ANSI Version. Works correctly on Asian Win95. } function GetLocaleDataA(ID: LCID; Flag: DWORD): string; var Buffer: array[0..1023] of Char; begin Buffer[0] := #0; SetString(Result, Buffer, GetLocaleInfoA(ID, Flag, Buffer, SizeOf(Buffer)) - 1); end; { Called for each supported locale. } function TLanguages.LocalesCallback(LocaleID: PChar): Integer; stdcall; var AID: LCID; ShortLangName: string; GetLocaleDataProc: function (ID: LCID; Flag: DWORD): string; begin if Win32Platform = VER_PLATFORM_WIN32_NT then GetLocaleDataProc := @GetLocaleDataW else GetLocaleDataProc := @GetLocaleDataA; AID := StrToInt('$' + Copy(LocaleID, 5, 4)); ShortLangName := GetLocaleDataProc(AID, LOCALE_SABBREVLANGNAME); if ShortLangName <> '' then begin SetLength(FSysLangs, Length(FSysLangs) + 1); with FSysLangs[High(FSysLangs)] do begin FName := GetLocaleDataProc(AID, LOCALE_SLANGUAGE); FLCID := AID; FExt := ShortLangName; end; end; Result := 1; end; constructor TLanguages.Create; begin inherited Create; FTempLanguages := Self; EnumSystemLocales(@EnumLocalesCallback, LCID_SUPPORTED); end; function TLanguages.GetCount: Integer; begin Result := High(FSysLangs) + 1; end; function TLanguages.GetExt(Index: Integer): string; begin Result := FSysLangs[Index].FExt; end; function TLanguages.GetID(Index: Integer): string; begin Result := HexDisplayPrefix + IntToHex(FSysLangs[Index].FLCID, 8); end; function TLanguages.GetLCID(Index: Integer): LCID; begin Result := FSysLangs[Index].FLCID; end; function TLanguages.GetName(Index: Integer): string; begin Result := FSysLangs[Index].FName; end; function TLanguages.GetNameFromLocaleID(ID: LCID): string; var Index: Integer; begin Result := sUnknown; Index := IndexOf(ID); if Index <> - 1 then Result := Name[Index]; if Result = '' then Result := sUnknown; end; function TLanguages.GetNameFromLCID(const ID: string): string; begin Result := NameFromLocaleID[StrToIntDef(ID, 0)]; end; function TLanguages.IndexOf(ID: LCID): Integer; begin for Result := Low(FSysLangs) to High(FSysLangs) do if FSysLangs[Result].FLCID = ID then Exit; Result := -1; end; var FLanguages: TLanguages; function Languages: TLanguages; begin if FLanguages = nil then FLanguages := TLanguages.Create; Result := FLanguages; end; function SafeLoadLibrary(const Filename: string; ErrorMode: UINT): HMODULE; var OldMode: UINT; FPUControlWord: Word; begin OldMode := SetErrorMode(ErrorMode); try asm FNSTCW FPUControlWord end; try Result := LoadLibrary(PChar(Filename)); finally asm FNCLEX FLDCW FPUControlWord end; end; finally SetErrorMode(OldMode); end; end; {$ENDIF} {$IFDEF LINUX} function SafeLoadLibrary(const FileName: string; Dummy: LongWord): HMODULE; var FPUControlWord: Word; begin asm FNSTCW FPUControlWord end; try Result := LoadLibrary(PChar(Filename)); finally asm FNCLEX FLDCW FPUControlWord end; end; end; {$ENDIF} {$IFDEF MSWINDOWS} function GetEnvironmentVariable(const Name: string): string; const BufSize = 1024; var Len: Integer; Buffer: array[0..BufSize - 1] of Char; begin Result := ''; Len := Windows.GetEnvironmentVariable(PChar(Name), @Buffer, BufSize); if Len < BufSize then SetString(Result, PChar(@Buffer), Len) else begin SetLength(Result, Len - 1); Windows.GetEnvironmentVariable(PChar(Name), PChar(Result), Len); end; end; {$ENDIF} {$IFDEF LINUX} function GetEnvironmentVariable(const Name: string): string; begin Result := getenv(PChar(Name)); end; {$ENDIF} {$IFDEF LINUX} procedure CheckLocale; var P,Q: PChar; begin P := gnu_get_libc_version(); Q := getenv('LC_ALL'); if (Q = nil) or (Q[0] = #0) then Q := getenv('LANG'); // 2.1.3 <= current version < 2.1.91 if (strverscmp('2.1.3', P) <= 0) and (strverscmp(P, '2.1.91') < 0) and ((Q = nil) or (Q[0] = #0)) then begin // GNU libc 2.1.3 will segfault in towupper() if environment variables don't // specify a locale. This can happen when Apache launches CGI subprocesses. // Solution: set a locale if the environment variable is missing. // Works in 2.1.2, fixed in glibc 2.1.91 and later setlocale(LC_ALL, 'POSIX'); end else // Configure the process locale settings according to // the system environment variables (LC_CTYPE, LC_COLLATE, etc) setlocale(LC_ALL, ''); // Note: // POSIX/C is the default locale on many Unix systems, but its 7-bit charset // causes char to widechar conversions to fail on any high-ascii // character. To support high-ascii charset conversions, set the // LC_CTYPE environment variable to something else or call setlocale to set // the LC_CTYPE information for this process. It doesn't matter what // you set it to, as long as it's not POSIX. if StrComp(nl_langinfo(_NL_CTYPE_CODESET_NAME), 'ANSI_X3.4-1968') = 0 then setlocale(LC_CTYPE, 'en_US'); // selects codepage ISO-8859-1 end; procedure PropagateSignals; var Exc: TObject; begin { If there is a current exception pending, then we're shutting down because it went unhandled. If that exception is the result of a signal, then we need to propagate that back out to the world as a real signal death. See the discussion at http://www2.cons.org/cracauer/sigint.html for more info. } Exc := ExceptObject; if (Exc <> nil) and (Exc is EExternal) then kill(getpid, EExternal(Exc).SignalNumber); end; { Under Win32, SafeCallError is implemented in ComObj. Under Linux, we don't have ComObj, so we've substituted a similar mechanism here. } procedure SafeCallError(ErrorCode: Integer; ErrorAddr: Pointer); var ExcMsg: String; begin ExcMsg := GetSafeCallExceptionMsg; SetSafeCallExceptionMsg(''); if ExcMsg <> '' then begin raise ESafeCallException.Create(ExcMsg) at GetSafeCallExceptionAddr; end else raise ESafeCallException.CreateRes(@SSafecallException); end; {$ENDIF} initialization if ModuleIsCpp then HexDisplayPrefix := '0x'; InitExceptions; {$IFDEF LINUX} SafeCallErrorProc := @SafeCallError; ExitProcessProc := PropagateSignals; CheckLocale; {$ENDIF} {$IFDEF MSWINDOWS} InitPlatformId; InitDriveSpacePtr; {$ENDIF} GetFormatSettings; { Win implementation uses platform id } finalization {$IFDEF MSWINDOWS} FreeAndNil(FLanguages); {$ENDIF} {$IFDEF LINUX} if libuuidHandle <> nil then dlclose(libuuidHandle); {$ENDIF} FreeTerminateProcs; DoneExceptions; end.