Is there a way to convert the two-letter Country Codes into their readable counterparts without using external ressources?
e.g. DE -> Germany, AD -> Andorra
It would be great if I could select the target language or it's using the system language, because I'd like to have them in German.
As #Uwe mentioned in his comment, you can use the EnumSystemGeoID and GetGeoInfo functions. The principle is that with EnumSystemGeoID function you'll enumerate geographical location identifiers and by the GetGeoInfo function query if the enumerated identifier's ISO 2-letter country / region code (info type GEO_ISO2) equals to the one of your interest. If so, then you can query for this identifier with the same function either a friendly name (info type GEO_FRIENDLYNAME), or the official name (info type GEO_OFFICIALNAME), return the result and stop the enumeration.
Here is an example code, which might do that (unfortunately, the enumeration function does not support passing custom data, so I've used a global record variable for passing values):
type
TEnumData = record
GeoCode: string;
GeoName: string;
Success: Boolean;
end;
GEOID = type LONG;
GEOTYPE = type DWORD;
GEOCLASS = type DWORD;
SYSGEOTYPE = (
GEO_NATION = $0001,
GEO_LATITUDE = $0002,
GEO_LONGITUDE = $0003,
GEO_ISO2 = $0004,
GEO_ISO3 = $0005,
GEO_RFC1766 = $0006,
GEO_LCID = $0007,
GEO_FRIENDLYNAME= $0008,
GEO_OFFICIALNAME= $0009,
GEO_TIMEZONES = $000A,
GEO_OFFICIALLANGUAGES = $000B,
GEO_ISO_UN_NUMBER = $000C,
GEO_PARENT = $000D
);
SYSGEOCLASS = (
GEOCLASS_NATION = 16,
GEOCLASS_REGION = 14,
GEOCLASS_ALL = 0
);
GEO_ENUMPROC = function(GeoId: GEOID): BOOL; stdcall;
function EnumSystemGeoID(GeoClass: GEOCLASS;
ParentGeoId: GEOID; lpGeoEnumProc: GEO_ENUMPROC): BOOL; stdcall;
external kernel32 name 'EnumSystemGeoID';
function GetGeoInfo(Location: GEOID; GeoType: GEOTYPE;
lpGeoData: LPTSTR; cchData: Integer; LangId: LANGID): Integer; stdcall;
external kernel32 name {$IFDEF UNICODE}'GetGeoInfoW'{$ELSE}'GetGeoInfoA'{$ENDIF};
implementation
var
// I have used this global variable due to a lack of user data parameter for the callback function
EnumData: TEnumData;
function TryGetGeoInfo(GeoId: GEOID; GeoType: GEOTYPE; out Value: string): Boolean;
var
Buffer: string;
BufferLen: Integer;
begin
Result := False;
BufferLen := GetGeoInfo(GeoId, GeoType, LPTSTR(Buffer), 0, 0);
if BufferLen <> 0 then
begin
SetLength(Buffer, BufferLen);
Result := GetGeoInfo(GeoId, GeoType, LPTSTR(Buffer), BufferLen, 0) <> 0;
if Result then
Value := Trim(Buffer);
end;
end;
function EnumGeoInfoProc(GeoId: GEOID): BOOL; stdcall;
var
S: string;
begin
Result := TryGetGeoInfo(GeoId, GEOTYPE(GEO_ISO2), S);
if Result and (S = EnumData.GeoCode) then
begin
// stop the enumeration since we've found the country by its ISO code
Result := False;
// return the success flag and try to return the friendly name of the country to the
// EnumData.GeoName record field; you can optionally query the GEO_OFFICIALNAME
EnumData.Success := TryGetGeoInfo(GeoId, GEOTYPE(GEO_FRIENDLYNAME), EnumData.GeoName);
end;
end;
function TryGetCountryNameByISO2(const Code: string; out Name: string): Boolean;
begin
// here is the brainless part using global record variable (because the function used
// here with its callback does not support passing user data); no, you cannot tune it
// up by making the callback function nested
EnumData.GeoCode := Code;
EnumData.Success := False;
if not EnumSystemGeoID(GEOCLASS(GEOCLASS_NATION), 0, EnumGeoInfoProc) then
RaiseLastOSError;
Result := EnumData.Success;
if Result then
Name := EnumData.GeoName;
end;
And a possible usage:
var
S: string;
begin
if TryGetCountryNameByISO2('DE', S) then
ShowMessage(S);
end;
You can iterate Languages (from Sysutils) and check the Ext property. The corresponding Name property will give you the localized language name.
for I := 0 to Languages.Count - 1 do begin
Writeln(Languages.Ext[I], '=', Languages.Name[I]);
end;
The sublanguage element suggested by Uwe Raabe is helping, but the results aren't that good, because It won't find all ISO codes and sometimes returns something different to a country name, like Simplified Chinese.
function _GetCountryFromISO(const aISO: string): string;
const
cStatement1 = '-(.*)';
cStatement2 = '\((.*?)\)';
var
i: Integer;
match: TMatch;
begin
Result := aISO; // default result if not found
for i := 0 to Languages.Count - 1 do begin
match := TRegEx.Match(Languages.LocaleName[i], cStatement1);
if not match.Success then
Assert(False, '');
if (aISO.Equals(match.Groups[1].Value)) then begin
match := TRegEx.Match(Languages.Name[i], cStatement2);
if not match.Success then
Assert(False, '');
Exit(match.Groups[1].Value);
end;
end;
end;
Related
I'd like to get a MAC address from an IP of a host in the same local network. I'd prefer to get this information from the local cache instead of sending a new ARP ARP request. I found that ResolveIpNetEntry2 should be what I need.
Unfortunately I didn't find any code sample for Delphi with that function. Even worse I didn't even find any Delphi headers for that function and its data types. So I tried converting them myself. Well, it compiles, but I get ERROR_INVALID_PARAMETER (87), so apparently I converted something wrong.
Could someone please tell me how to correct it?
const
IF_MAX_PHYS_ADDRESS_LENGTH = 32;
type
NET_LUID = record
case Word of
1: (Value: Int64;);
2: (Reserved: Int64;);
3: (NetLuidIndex: Int64;);
4: (IfType: Int64;);
end;
NL_NEIGHBOR_STATE = (
NlnsUnreachable=0,
NlnsIncomplete,
NlnsProbe,
NlnsDelay,
NlnsStale,
NlnsReachable,
NlnsPermanent,
NlnsMaximum);
PMIB_IPNET_ROW2 = ^MIB_IPNET_ROW2;
MIB_IPNET_ROW2 = record
Address: LPSOCKADDR; //SOCKADDR_INET
InterfaceIndex: ULONG; //NET_IFINDEX
InterfaceLuid: NET_LUID;
PhysicalAddress: array [0..IF_MAX_PHYS_ADDRESS_LENGTH - 1] of UCHAR;
PhysicalAddressLength: ULONG;
State: NL_NEIGHBOR_STATE;
Union: record
case Integer of
0: (IsRouter: Boolean;
IsUnreachable: Boolean);
1: (Flags: UCHAR);
end;
ReachabilityTime: record
case Integer of
0: (LastReachable: ULONG);
1: (LastUnreachable: ULONG);
end;
end;
function ResolveIp(const AIp: String; AIfIndex: ULONG): String;
type
TResolveIpNetEntry2Func = function (Row: PMIB_IPNET_ROW2; const SourceAddress: LPSOCKADDR): DWORD; stdcall; //NETIOAPI_API
const
IphlpApiDll = 'iphlpapi.dll';
var
hIphlpApiDll: THandle;
ResolveIpNetEntry2: TResolveIpNetEntry2Func;
dw: DWORD;
Row: PMIB_IPNET_ROW2;
SourceAddress: LPSOCKADDR;
IpAddress: LPSOCKADDR;
begin
hIphlpApiDll := LoadLibrary(IphlpApiDll);
if hIphlpApiDll = 0 then
Exit;
ResolveIpNetEntry2 := GetProcAddress(hIphlpApiDll, 'ResolveIpNetEntry2');
if (#ResolveIpNetEntry2 = nil) then
Exit;
IpAddress := AllocMem(SizeOf(IpAddress));
IpAddress.sa_family := AF_INET;
IpAddress.sa_data := PAnsiChar(AIp);
Row := AllocMem(SizeOf(Row));
Row.Address := IpAddress;
Row.InterfaceIndex := AIfIndex;
SourceAddress := 0;
dw := ResolveIpNetEntry2(Row, SourceAddress);
//...
end;
Per the ResolveIpNetEntry2() documentation:
If the function fails, the return value is one of the following error codes.
...
ERROR_INVALID_PARAMETER
An invalid parameter was passed to the function. This error is returned if a NULL pointer is passed in the Row parameter, the Address member of the MIB_IPNET_ROW2 pointed to by the Row parameter was not set to a valid IPv4 or IPv6 address, or both the InterfaceLuid or InterfaceIndex members of the MIB_IPNET_ROW2 pointed to by the Row parameter were unspecified. This error is also returned if a loopback address was passed in the Address member.
Your translation of the API structures is incorrect in general. You got several fields wrong, which affects field offsets and sizes. Such as the bitfields. But more importantly, your declaration of the MIB_IPNET_ROW2.Address field is completely wrong. It is not a pointer to an external SOCKADDR record at all. It is a SOCKADDR_INET record that exists inside of the MIB_IPNET_ROW2 itself, not externally.
You are also not allocating or initializing your memory blocks correctly, which also plays into the above documentation.
And, even if you had the API translated correctly, you are not converting your AIp string to a SOCKADDR correctly, either. You can't simply type-cast it, you need to actually convert it from string characters to a network address integer, using inet_addr(), InetPton(), or other equivalent function. So that also plays into the above documentation.
You are lucky your code did not crash altogether.
Try something more like this instead:
{$MINENUMSIZE 4}
const
IF_MAX_PHYS_ADDRESS_LENGTH = 32;
type
NET_LUID_INFO = record
Reserved: array [0..2] of UCHAR; // ULONG64:24
NetLuidIndex: array [0..2] of UCHAR; // ULONG64:24
IfType: array [0..1] of UCHAR; // ULONG64:16
//
// TODO: if you need to access these values, define
// some property getters/setters to translate them
// to/from UInt64...
end;
NET_LUID = record
case Integer of
0: (Value: UInt64);
1: (Info: NET_LUID_INFO);
end;
NL_NEIGHBOR_STATE = (
NlnsUnreachable = 0,
NlnsIncomplete,
NlnsProbe,
NlnsDelay,
NlnsStale,
NlnsReachable,
NlnsPermanent,
NlnsMaximum);
PSOCKADDR_INET = ^SOCKADDR_INET;
SOCKADDR_INET = record
case Integer of
0: (Ipv4: SOCKADDR_IN);
1: (Ipv6: SOCKADDR_IN6);
2: (si_family: ADDRESS_FAMILY);
end;
NETIO_STATUS = DWORD;
NET_IFINDEX = ULONG;
PMIB_IPNET_ROW2 = ^MIB_IPNET_ROW2;
MIB_IPNET_ROW2 = record
Address: SOCKADDR_INET;
InterfaceIndex: NET_IFINDEX;
InterfaceLuid: NET_LUID;
PhysicalAddress: array [0..IF_MAX_PHYS_ADDRESS_LENGTH - 1] of UCHAR;
PhysicalAddressLength: ULONG;
State: NL_NEIGHBOR_STATE;
Flags: UCHAR;
ReachabilityTime: record
case Integer of
0: (LastReachable: ULONG);
1: (LastUnreachable: ULONG);
end;
function IsRouter: Boolean;
function IsUnreachable;
end;
function MIB_IPNET_ROW2.IsRouter: Boolean;
begin
Result := (Flags and $01) <> 0;
end;
function MIB_IPNET_ROW2.IsUnreachable;
begin
Result := (Flags and $02) <> 0;
end;
function ResolveIp(const AIp: String; AIfIndex: ULONG): String;
type
TResolveIpNetEntry2Func = function (Row: PMIB_IPNET_ROW2; const SourceAddress: PSOCKADDR_INET): NETIO_STATUS; stdcall;
const
IphlpApiDll = 'iphlpapi.dll';
var
hIphlpApiDll: THandle;
ResolveIpNetEntry2: TResolveIpNetEntry2Func;
status: NETIO_STATUS;
Row: PMIB_IPNET_ROW2;
begin
Result := '';
hIphlpApiDll := LoadLibrary(IphlpApiDll);
if hIphlpApiDll = 0 then
Exit;
try
#ResolveIpNetEntry2 := GetProcAddress(hIphlpApiDll, 'ResolveIpNetEntry2');
if not Assigned(ResolveIpNetEntry2) then
Exit;
New(Row);
try
ZeroMemory(Row, SizeOf(MIB_IPNET_ROW2));
if InetPton(AF_INET, PChar(AIp), #(Row.Address.Ipv4.sin_addr)) = 1 then
Row.Address.Ipv4.sin_family := AF_INET
else
if InetPton(AF_INET6, PChar(AIp), #(Row.Address.Ipv6.sin6_addr)) = 1 then
Row.Address.Ipv6.sin6_family := AF_INET6
else
Exit;
Row.InterfaceIndex := AIfIndex;
status := ResolveIpNetEntry2(Row, nil);
//...
finally
Dispose(Row);
end;
finally
FreeLibrary(hIphlpApiDll);
end;
end;
Alternatively, ResolveIp() can be written without dynamically allocating the Row at all:
function ResolveIp(const AIp: String; AIfIndex: ULONG): String;
...
var
...
Row: MIB_IPNET_ROW2;
begin
...
ZeroMemory(#Row, SizeOf(Row));
...
status := ResolveIpNetEntry2(#Row, nil);
...
end;
I'm trying to list all the resources of my program with the name of the resource and resource type as "RT_BITMAP" or any other.
The code :
var
Form1: TForm1;
list_resources: string;
function EnumResNameProc(lpszName: PChar; lParam: integer; lpszType: PChar;
hModule: Cardinal): BOOL;
begin
list_resources := list_resources + sLineBreak + lpszName + ' - ' + lpszType;
Result := True;
end;
procedure TForm1.btnListResourcesClick(Sender: TObject);
begin
EnumResourceNames(0, RT_RCDATA, #EnumResNameProc, 0);
Memo1.Lines.Add(list_resources);
end;
The code work good but never show the type of resource , what is the problem ?
The first problem with your code is that the callback function has the wrong calling convention and indeed the wrong signature. It should be declared like this:
function EnumResNameProc(hModule: HMODULE; lpszType, lpszName: PChar;
lParam: LONG_PTR): BOOL; stdcall;
The output that your code was producing was completely accidental. It's really important that you get the signatures of these function correct. I don't know where your signature came from. It looks like you just made it up! The documentation on MSDN has the correct signature. Embarcadero make things hard by declaring the API functions that accept callbacks in a way that means type checking for the signatures is neglected. So the onus falls on your. Read the documentation very carefully.
Once you have fixed that problem, there is still more to be done. Resource types, and indeed resource names, can either be integers or strings. The convention is that values < 65536 are interpreted as integers, otherwise the value is a pointer to a null-terminated character array. Rather than hard-code that magic constant, you can instead call Is_IntResource, the Delphi translation of the Windows macro IS_INTRESOURCE.
In your case you will be receiving named resources, but resource types that are actually integer values. From the Windows unit:
const
RT_CURSOR = MakeIntResource(1);
RT_BITMAP = MakeIntResource(2);
RT_ICON = MakeIntResource(3);
RT_MENU = MakeIntResource(4);
RT_DIALOG = MakeIntResource(5);
RT_STRING = MakeIntResource(6);
RT_FONTDIR = MakeIntResource(7);
RT_FONT = MakeIntResource(8);
RT_ACCELERATOR = MakeIntResource(9);
RT_RCDATA = System.Types.RT_RCDATA; //MakeIntResource(10);
DIFFERENCE = 11;
RT_GROUP_CURSOR = MakeIntResource(DWORD(RT_CURSOR) + DIFFERENCE);
RT_GROUP_ICON = MakeIntResource(DWORD(RT_ICON) + DIFFERENCE);
RT_VERSION = MakeIntResource(16);
RT_DLGINCLUDE = MakeIntResource(17);
RT_PLUGPLAY = MakeIntResource(19);
RT_VXD = MakeIntResource(20);
RT_ANICURSOR = MakeIntResource(21);
RT_ANIICON = MakeIntResource(22);
RT_HTML = MakeIntResource(23);
RT_MANIFEST = MakeIntResource(24);
The other convention at play is that you use a # symbol to indicate a numeric identifier. So you could adopt the following policy:
If Is_IntResource returns True, then convert the numeric value to string and prefix with #.
Otherwise treat as a pointer to null-terminated character array.
The code is quite simple:
function ResourceNameToString(lpszName: PChar): string;
begin
if Is_IntResource(lpszName) then
Result := '#' + IntToStr(NativeUInt(lpszName))
else
Result := lpszName;
end;
It's imperative that you do this, for names as well as types. Otherwise your code will fail with runtime access violation errors when it tries to de-reference a pointer that is actually representing an integer. The code in the question will fail that way if you fixed the callback signature but make no further changes.
If you want your code to be more helpful you will detect the pre-defined resource types and give them special treatment.
function ResourceTypeToString(lpszType: PChar): string;
begin
case NativeUInt(lpszType) of
NativeUInt(RT_CURSOR):
Result := 'RT_CURSOR';
NativeUInt(RT_BITMAP):
Result := 'RT_BITMAP';
NativeUInt(RT_RCDATA):
Result := 'RT_RCDATA';
// etc.
else
Result := ResourceNameToString(lpszType);
end;
end;
I'll let you fill in the missing values.
Put it all together like so:
{$APPTYPE CONSOLE}
uses
SysUtils, Windows;
function ResourceNameToString(lpszName: PChar): string;
begin
if Is_IntResource(lpszName) then
Result := '#' + IntToStr(NativeUInt(lpszName))
else
Result := lpszName;
end;
function ResourceTypeToString(lpszType: PChar): string;
begin
case NativeUInt(lpszType) of
NativeUInt(RT_CURSOR):
Result := 'RT_CURSOR';
NativeUInt(RT_BITMAP):
Result := 'RT_BITMAP';
NativeUInt(RT_RCDATA):
Result := 'RT_RCDATA';
// etc.
else
Result := ResourceNameToString(lpszType);
end;
end;
function EnumResNameProc(hModule: HMODULE; lpszType, lpszName: PChar;
lParam: LONG_PTR): BOOL; stdcall;
begin
Writeln(ResourceTypeToString(lpszType) + ' - ' + ResourceNameToString(lpszName));
Result := True;
end;
begin
EnumResourceNames(0, RT_RCDATA, #EnumResNameProc, 0);
Readln;
end.
I'm kinda a Delphi-newbie and I don't get how the Sort method of a TList of Records is called in order to sort the records by ascending integer value.
I have a record like the following:
type
TMyRecord = record
str1: string;
str2: string;
intVal: integer;
end;
And a generic list of such records:
TListMyRecord = TList<TMyRecord>;
Have tried to find a code-example in the help files and found this one:
MyList.Sort(#CompareNames);
Which I can't use, since it uses classes. So I tried to write my own compare function with a little different parameters:
function CompareIntVal(i1, i2: TMyRecord): Integer;
begin
Result := i1.intVal - i2.intVal;
end;
But the compiler always throws a 'not enough parameters' - error when I call it with open.Sort(CompareIntVal);, which seems obvious; so I tried to stay closer to the help file:
function SortKB(Item1, Item2: Pointer): Integer;
begin
Result:=PMyRecord(Item1)^.intVal - PMyRecord(Item2)^.intVal;
end;
with PMyRecord as PMyRecord = ^TMyRecord;
I have tried different ways of calling a function, always getting some error...
The Sort overload you should be using is this one:
procedure Sort(const AComparer: IComparer<TMyRecord>);
Now, you can create an IComparer<TMyRecord> by calling TComparer<TMyRecord>.Construct. Like this:
var
Comparison: TComparison<TMyRecord>;
....
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal-Right.intVal;
end;
List.Sort(TComparer<TMyRecord>.Construct(Comparison));
I've written the Comparison function as an anonymous method, but you could also use a plain old style non-OOP function, or a method of an object.
One potential problem with your comparison function is that you may suffer from integer overflow. So you could instead use the default integer comparer.
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := TComparer<Integer>.Default.Compare(Left.intVal, Right.intVal);
end;
It might be expensive to call TComparer<Integer>.Default repeatedly so you could store it away in a global variable:
var
IntegerComparer: IComparer<Integer>;
....
initialization
IntegerComparer := TComparer<Integer>.Default;
Another option to consider is to pass in the comparer when you create the list. If you only ever sort the list using this ordering then that's more convenient.
List := TList<TMyRecord>.Create(TComparer<TMyRecord>.Construct(Comparison));
And then you can sort the list with
List.Sort;
The concise answer:
uses
.. System.Generics.Defaults // Contains TComparer
myList.Sort(
TComparer<TMyRecord>.Construct(
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal - Right.intVal;
end
)
);
I want to share my solution (based on the input I have gathered here).
It's a standard setup. A filedata class that holds data of a single file in a generic TObjectList. The list has the two private attributes fCurrentSortedColumn and fCurrentSortAscending to control the sort order. The AsString-method is the path and filename combined.
function TFileList.SortByColumn(aColumn: TSortByColums): boolean;
var
Comparison: TComparison<TFileData>;
begin
result := false;
Comparison := nil;
case aColumn of
sbcUnsorted : ;
sbcPathAndName: begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcSize : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<int64>.Default.Compare(Left.Size,Right.Size);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcDate : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TDateTime>.Default.Compare(Left.Date,Right.Date);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcState : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TFileDataTestResults>.Default.Compare(Left.FileDataResult,Right.FileDataResult);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
end;
if assigned(Comparison) then
begin
Sort(TComparer<TFileData>.Construct(Comparison));
// Control the sort order
if fCurrentSortedColumn = aColumn then
fCurrentSortAscending := not fCurrentSortAscending
else begin
fCurrentSortedColumn := aColumn;
fCurrentSortAscending := true;
end;
if not fCurrentSortAscending then
Reverse;
result := true;
end;
end;
I found a much simpler modified sort function to alphabetize a TList of records or nonstandard list of items.
Example
PList = ^TContact;
TContact = record //Record for database of user contact records
firstname1 : string[20];
lastname1 : string[20];
phonemobile : Integer; //Fields in the database for contact info
phonehome : Integer;
street1 : string;
street2 : string;
type
TListSortCompare = function (Item1,
Item2: TContact): Integer;
var
Form1: TForm1;
Contact : PList; //declare record database for contacts
arecord : TContact;
Contacts : TList; //List for the Array of Contacts
function CompareNames(i1, i2: TContact): Integer;
begin
Result := CompareText(i1.lastname1, i2.lastname1) ;
end;
and the function to call to sort your list
Contacts.Sort(#CompareNames);
Is there a method in Delphi to check if a string is a number without raising an exception?
its for int parsing.
and an exception will raise if one use the
try
StrToInt(s);
except
//exception handling
end;
function TryStrToInt(const S: string; out Value: Integer): Boolean;
TryStrToInt converts the string S, which represents an integer-type number in either decimal or hexadecimal notation, into a number, which is assigned to Value. If S does not represent a valid number, TryStrToInt returns false; otherwise TryStrToInt returns true.
To accept decimal but not hexadecimal values in the input string, you may use code like this:
function TryDecimalStrToInt( const S: string; out Value: Integer): Boolean;
begin
result := ( pos( '$', S ) = 0 ) and TryStrToInt( S, Value );
end;
var
s: String;
iValue, iCode: Integer;
...
val(s, iValue, iCode);
if iCode = 0 then
ShowMessage('s has a number')
else
ShowMessage('s has not a number');
Try this function StrToIntDef()
From help
Converts a string that represents an integer (decimal or hex notation) to a number with error default.
Pascal
function StrToIntDef(const S: string; Default: Integer): Integer;
Edit
Just now checked the source of TryStrToInt() function in Delphi 2007. If Delphi 7 dont have this function you can write like this. Its just a polished code to da-soft answer
function TryStrToInt(const S: string; out Value: Integer): Boolean;
var
E: Integer;
begin
Val(S, Value, E);
Result := E = 0;
end;
XE4 and newer:
for ch in s do
TCharacter.IsNumber(ch);
Don't forget:
uses System.Character
In delphi 7 you can use the Val procedure. From the help:
Unit: System
Delphi syntax: procedure Val(S; var V; var Code: Integer);
S is a string-type expression; it must be a sequence of characters that form a signed real number.
V is an integer-type or real-type variable. If V is an integer-type variable, S must form a whole number.
Code is a variable of type Integer.
If the string is invalid, the index of the offending character is stored in Code; otherwise, Code is set to zero. For a null-terminated string, the error position returned in Code is one larger than the actual zero-based index of the character in error.
use this function
function IsNumber(N : String) : Boolean;
var
I : Integer;
begin
Result := True;
if Trim(N) = '' then
Exit(False);
if (Length(Trim(N)) > 1) and (Trim(N)[1] = '0') then
Exit(False);
for I := 1 to Length(N) do
begin
if not (N[I] in ['0'..'9']) then
begin
Result := False;
Break;
end;
end;
end;
For older Delphi versions from delphi 5 help example:
uses Dialogs;
var
I, Code: Integer;
begin
{ Get text from TEdit control }
Val(Edit1.Text, I, Code);
{ Error during conversion to integer? }
if Code <> 0 then
MessageDlg('Error at position: ' + IntToStr(Code), mtWarning, [mbOk], 0);
else
Canvas.TextOut(10, 10, 'Value = ' + IntToStr(I));
end;
In some languages decimal separators are different (for example, '.' is used in English and ',' is used in Russian). For these cases to convert string to real number the following procedure is proposed:
function TryStrToFloatMultiLang(const S : String; out Value : Extended) : Boolean;
var
dc : char;
begin
Result := false;
dc := DecimalSeparator;
DecimalSeparator := '.';
try
Result := TryStrToFloat(S, Value);
except
DecimalSeparator := ',';
Result := TryStrToFloat(S, Value);
end;
DecimalSeparator := dc;
end;
Update
As #Pep mentioned TryStrToFloat catch exceptions, but it returns boolean value. So the correct code is:
function TryStrToFloatMultiLang(const S : String; out Value : Extended) : Boolean;
var
dc : char;
begin
Result := false;
dc := DecimalSeparator;
DecimalSeparator := '.';
Result := TryStrToFloat(S, Value);
if not Result then begin
DecimalSeparator := ',';
Result := TryStrToFloat(S, Value);
end;
DecimalSeparator := dc;
end;
When you using procedure
val(s, i, iCode);
and set value xd ....
val('xd', i, iCode)
as a result we obtain: 13
standard unit Variants
function VarIsNumeric(v:Variant):Boolean
I'm looking for the best* method to find the primary email address for the currently logged in Active Directory user (using GetUserName to get the logged in username)
I have seen How do integrate Delphi with Active Directory? but I couldn't get this to work with Delphi 2010.
(*best method: the eventual application will be run by users who do not have administrative access to the machine)
Edit 1:
Reading up on this, it appears that the email or mail field is probably not the best way to go as it seems it might not be populated, therefore I'd need to use the multivalue field of proxyaddresses
The code below works for me. It is an extract of a class I use in production code. It didn't get the proxyAddresses but I added that and it seems to work, although I get only one alternative e-mail address, looking like smtp: g.trol#mydomain.com. I can't find an example with more that one address, so you may need to test what happens then.
Also, I tested this in Delphi 2007, using a type library I found somewhere, because I had trouble importing it. In the code you see __MIDL_0010, which is a __MIDL___MIDL_itf_ads_0000_0017 record property of the field value. I noticed this was named otherwise in a different version of the type library, so you may need to make some tweaks to this code to suit your exact type library import, an maybe fix some ansi/unicode differences.
uses ActiveX, ComObj, ActiveDs_TLB;
const
NETAPI32DLL = 'netapi32.dll';
const
ACTIVEDSDLL = 'activeds.dll';
ADS_SECURE_AUTHENTICATION = $00000001;
const
// ADSI success codes
S_ADS_ERRORSOCCURRED = $00005011;
S_ADS_NOMORE_ROWS = $00005012;
S_ADS_NOMORE_COLUMNS = $00005013;
// ADSI error codes
E_ADS_BAD_PATHNAME = $80005000;
E_ADS_INVALID_DOMAIN_OBJECT = $80005001;
E_ADS_INVALID_USER_OBJECT = $80005002;
E_ADS_INVALID_COMPUTER_OBJECT = $80005003;
E_ADS_UNKNOWN_OBJECT = $80005004;
E_ADS_PROPERTY_NOT_SET = $80005005;
E_ADS_PROPERTY_NOT_SUPPORTED = $80005006;
E_ADS_PROPERTY_INVALID = $80005007;
E_ADS_BAD_PARAMETER = $80005008;
E_ADS_OBJECT_UNBOUND = $80005009;
E_ADS_PROPERTY_NOT_MODIFIED = $8000500A;
E_ADS_PROPERTY_MODIFIED = $8000500B;
E_ADS_CANT_CONVERT_DATATYPE = $8000500C;
E_ADS_PROPERTY_NOT_FOUND = $8000500D;
E_ADS_OBJECT_EXISTS = $8000500E;
E_ADS_SCHEMA_VIOLATION = $8000500F;
E_ADS_COLUMN_NOT_SET = $80005010;
E_ADS_INVALID_FILTER = $80005014;
type
TNetWkstaGetInfo = function(ServerName: PWideChar; Level: Cardinal;
out BufPtr: Pointer): Cardinal; stdcall;
TADsOpenObject = function (lpszPathName: PWideChar; lpszUserName: PWideChar;
lpszPassword: PWideChar; dwReserved: DWORD; const riid: TGUID;
out pObject): HRESULT; stdcall;
TADsGetObject = function(PathName: PWideChar; const IID: TGUID; out Void):
HRESULT; stdcall;
var
NetLibHandle: THandle;
NetWkstaGetInfo : TNetWkstaGetInfo;
AdsLibHandle: THandle;
_ADsOpenObject : TADsOpenObject;
_ADsGetObject :TADsGetObject;
// VB-like GetObject function
function GetObject(const Name: String): IDispatch;
var
Moniker: IMoniker;
Eaten: integer;
BindContext: IBindCtx;
Dispatch: IDispatch;
begin
OleCheck(CreateBindCtx(0, BindContext));
OleCheck(MkParseDisplayName(BindContext,
PWideChar(WideString(Name)),
Eaten,
Moniker));
OleCheck(Moniker.BindToObject(BindContext, nil, IDispatch, Dispatch));
Result := Dispatch;
end;
// Some network info
type
PWkstaInfo100 = ^TWkstaInfo100;
_WKSTA_INFO_100 = record
wki100_platform_id: DWORD;
wki100_computername: LPWSTR;
wki100_langroup: LPWSTR;
wki100_ver_major: DWORD;
wki100_ver_minor: DWORD;
end;
TWkstaInfo100 = _WKSTA_INFO_100;
WKSTA_INFO_100 = _WKSTA_INFO_100;
function GetCurrentDomain: String;
var
pWI: PWkstaInfo100;
begin
if Win32Platform = VER_PLATFORM_WIN32_NT then
begin
if NetWkstaGetInfo(nil, 100, Pointer(pWI)) = 0 then
Result := String(pWI.wki100_langroup);
end;
end;
// ADs...Object function wrappers
function ADsGetObject(PathName: PWideChar; const IID: TGUID;
out Void): HRESULT;
begin
if Assigned(_ADsGetObject) then
Result := _ADsGetObject(PathName, IID, Void)
else
Result := ERROR_CALL_NOT_IMPLEMENTED;
end;
function ADsOpenObject(lpszPathName, lpszUserName,
lpszPassword: PWideChar; dwReserved: DWORD; const riid: TGUID;
out pObject): HRESULT;
begin
if Assigned(_ADsOpenObject) then
Result := _ADsOpenObject(lpszPathName, lpszUserName, lpszPassword, dwReserved, riid, pObject)
else
Result := ERROR_CALL_NOT_IMPLEMENTED;
end;
// The main function
function GetUserInfo(UserAccountName: string): Boolean;
var
// Domain info: Max password age
RootDSE: Variant;
Domain: Variant;
MaxPwdNanoAge: Variant;
MaxPasswordAge: Int64;
DNSDomain: String;
// User info: User directorysearch to find the user by username
DirectorySearch: IDirectorySearch;
SearchPreferences: array[0..1] of ADS_SEARCHPREF_INFO;
Columns: array[0..6] of PWideChar;
SearchResult: Cardinal;
hr: HRESULT;
ColumnResult: ads_search_column;
// Number of user records found
RecordCount: Integer;
LastSetDateTime: TDateTime;
ExpireDateTime: TDateTime;
i: Integer;
begin
Result := False;
// If no account name is set, reading is impossible. Return false.
if (UserAccountName = '') then
Exit;
try
// Read the maximum password age from the domain.
// To do: Check if this can be done with ADsGetObject instead of the VB-like GetObject
// Get the Root DSE.
RootDSE := GetObject('LDAP://RootDSE');
DNSDomain := RootDSE.Get('DefaultNamingContext');
Domain := GetObject('LDAP://' + DNSDomain);
// Build an array of user properties to receive.
Columns[0] := StringToOleStr('AdsPath');
Columns[1] := StringToOleStr('pwdLastSet');
Columns[2] := StringToOleStr('displayName');
Columns[3] := StringToOleStr('mail');
Columns[4] := StringToOleStr('sAMAccountName');
Columns[5] := StringToOleStr('userPrincipalName');
Columns[6] := StringToOleStr('proxyAddresses');
// Bind to the directorysearch object. For some unspecified reason, the regular
// domain name (yourdomain) needs to be used instead of the AdsPath (office.yourdomain.us)
AdsGetObject(PWideChar(WideString('LDAP://' + GetCurrentDomain)), IDirectorySearch, DirectorySearch);
try
// Set search preferences.
SearchPreferences[0].dwSearchPref := ADS_SEARCHPREF_SEARCH_SCOPE;
SearchPreferences[0].vValue.dwType := ADSTYPE_INTEGER;
SearchPreferences[0].vValue.__MIDL_0010.Integer := ADS_SCOPE_SUBTREE;
DirectorySearch.SetSearchPreference(#SearchPreferences[0], 1);
// Execute search
// Search for SAM account name (g.trol) and User Principal name
// (g.trol#yourdomain.com). This allows the user to enter their username
// in both ways. Add CN=* to filter out irrelevant objects that might
// match the principal name.
DirectorySearch.ExecuteSearch(
PWideChar(WideString(
Format('(&(CN=*)(|(sAMAccountName=%0:s)(userPrincipalName=%0:s)))',
[UserAccountName]))),
nil,
$FFFFFFFF,
SearchResult);
// Get records
RecordCount := 0;
hr := DirectorySearch.GetNextRow(SearchResult);
if (hr <> S_ADS_NOMORE_ROWS) then
begin
// 1 row found
Inc(RecordCount);
// Get the column values for this row.
// To do: This code could use a more general and neater approach!
for i := Low(Columns) to High(Columns) do
begin
hr := DirectorySearch.GetColumn(SearchResult, Columns[i], ColumnResult);
if Succeeded(hr) then
begin
// Get the values for the columns.
{if SameText(ColumnResult.pszAttrName, 'AdsPath') then
Result.UserAdsPath :=
ColumnResult.pADsValues.__MIDL_0010.CaseIgnoreString
else if SameText(ColumnResult.pszAttrName, 'pwdLastSet') then
begin
LastSetDateTime := LDapTimeStampToDateTime(
ColumnResult.pAdsvalues^.__MIDL_0010.LargeInteger) +
GetTimeZoneCorrection;
ExpireDateTime := IncMilliSecond(LastSetDateTime,
LDapIntervalToMSecs(MaxPasswordAge));
Result.UserPasswordExpireDateTime := ExpireDateTime;
end
else if SameText(ColumnResult.pszAttrName, 'displayName') then
Result.UserFullName := ColumnResult.pADsValues.__MIDL_0010.CaseIgnoreString
else if SameText(ColumnResult.pszAttrName, 'mail') then
Result.UserEmail := ColumnResult.pADsValues.__MIDL_0010.CaseIgnoreString
else if SameText(ColumnResult.pszAttrName, 'sAMAccountName') then
Result.UserShortAccountName := ColumnResult.pADsValues.__MIDL_0010.CaseIgnoreString
else if SameText(ColumnResult.pszAttrName, 'userPrincipalName') then
Result.UserFullAccountName := ColumnResult.pADsValues.__MIDL_0010.CaseIgnoreString
else ..}
if SameText(ColumnResult.pszAttrName, 'proxyAddresses') then
ShowMessage(ColumnResult.pADsValues.__MIDL_0010.CaseIgnoreString);
// Free the column result
DirectorySearch.FreeColumn(ColumnResult);
end;
end;
// Small check if this account indeed is the only one found.
// No need to check the exact number. <> 1 = error
Hr := DirectorySearch.GetNextRow(SearchResult);
if (hr <> S_ADS_NOMORE_ROWS) then
Inc(RecordCount);
end;
// Close the search
DirectorySearch.CloseSearchHandle(SearchResult);
// Exactly 1 record found?
if RecordCount = 1 then
Result := True
else
ShowMessageFmt('More than one account found when searching for %s in ' +
'Active Directory.', [UserAccountName]);
finally
DirectorySearch := nil;
end;
except
Result := False;
end;
end;
initialization
NetLibHandle := LoadLibrary(NETAPI32DLL);
if NetLibHandle <> 0 then
#NetWkstaGetInfo := GetProcAddress(NetLibHandle, 'NetWkstaGetInfo');
ADsLibHandle := LoadLibrary(ACTIVEDSDLL);
if ADsLibHandle <> 0 then
begin
#_ADsOpenObject := GetProcAddress(ADsLibHandle, 'ADsOpenObject');
#_ADsGetObject := GetProcAddress(ADsLibHandle, 'ADsGetObject');
end;
finalization
FreeLibrary(ADsLibHandle);
FreeLibrary(NetLibHandle);
end.
Call like this:
GetUserInfo('g.trol' {or g.trol#yourdomain.com});
Download from My dropbox