git-svn-id: https://svn.code.sf.net/p/kolmck/code@17 91bb2d04-0c0c-4d2d-88a5-bbb6f4c1fa07
16720 lines
482 KiB
ObjectPascal
16720 lines
482 KiB
ObjectPascal
{ *********************************************************************** }
|
|
{ }
|
|
{ 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 '<drive>:'.
|
|
For filenames with a UNC path, the resulting string is in the form
|
|
'\\<servername>\<sharename>'. 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 '\\<servername>\<sharename> 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 <X11/Xlib.h>, 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<Byte, 0, 255> 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 <oldpattern> with <newpattern> 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 = '<unknown>';
|
|
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.
|
|
|