Delphi Tokyo - Function: String to Hex and Hex to String - string

The function below works perfectly to convert string to hexadecimal:
function String2Hex(const Buffer: AnsiString): string;
begin
SetLength(Result, Length(Buffer) * 2);
BinToHex(#Buffer[1], PWideChar(#Result[1]), Length(Buffer));
end;
ShowMessage(String2Hex('stackoverflow'));
This result: "737461636B6F766572666C6F77"
The problem is in the function of converting hexadecimal to string:
function Hex2String(const Buffer: AnsiString): string;
begin
SetLength(Result, Length(Buffer) div 2);
HexToBin(PWideChar(#Buffer[1]), #Result[1], Length(Buffer));
end;
ShowMessage(Hex2String('737461636B6F766572666C6F77'));
The result should be "stackoverflow" but nothing happens.
Could someone help me?

There are a couple of problems with your code:
You are type-casting your input AnsiString incorrectly to PWideChar, so you are calling the wrong overload of HexToBin(). PWideChar should be PAnsiChar instead.
The BufSize parameter of HexToBin() specifies the number of bytes the output buffer expects to receive, but you are passing it the number of characters in the hex string instead.
Also, since String2Hex() takes an AnsiString and returns a UnicodeString, Hex2String() should take UnicodeString and return an AnsiString to match.
Try this instead:
function String2Hex(const Buffer: AnsiString): string;
begin
SetLength(Result, Length(Buffer) * 2);
BinToHex(PAnsiChar(Buffer), PChar(Result), Length(Buffer));
end;
function Hex2String(const Buffer: string): AnsiString;
begin
SetLength(Result, Length(Buffer) div 2);
HexToBin(PChar(Buffer), PAnsiChar(Result), Length(Result));
end;
var
hex: string;
str: AnsiString;
begin
hex := String2Hex('stackoverflow');
ShowMessage(hex); // shows '737461636B6F766572666C6F77'
str := Hex2String(hex);
ShowMessage(str); // shows 'stackoverflow'
end;

Related

How to convert a char array to String using RtlMoveMemory()?

Please have a look at my CharArrayToString() below.
What is the correct way to declare RtlMoveMemory function for this purpose, and how to call it?
[Setup]
AppName=EmptyProgram
AppVerName=EmptyProgram 1
UsePreviousAppDir=false
DefaultDirName={pf}\EmptyProgram
Uninstallable=false
OutputBaseFilename=HelloWorld
PrivilegesRequired=lowest
[Messages]
SetupAppTitle=My Title
[Code]
function lstrlen(lpString: array of Char
): Integer; external 'lstrlenW#kernel32.dll stdcall';
procedure RtlMoveMemory_ToString(
Dest : String;
Source: PAnsiChar;
Length: Integer
); external 'RtlMoveMemory#kernel32.dll stdcall';
//-------------------------------------------------------------------------------------------------
// This function is provided only for demonstration
//-------------------------------------------------------------------------------------------------
procedure StringToCharArray(const sStr: String; var aCharArray: array of Char);
var
iLenStr: Integer;
iIdx : Integer;
begin
iLenStr := Length(sStr);
if iLenStr = 0 then Exit;
SetArrayLength(aCharArray, iLenStr + 1); // Include a room for the null terminator
for iIdx := 1 to iLenStr do begin
aCharArray[iIdx - 1] := sStr[iIdx];
end;
aCharArray[iIdx - 1] := #0;
end; // ==> StringToCharArray()
//=================================================================================================
//-------------------------------------------------------------------------------------------------
// This function is an obvious solution to convert a char array to String.
// I do not want to use this function if possible.
//-------------------------------------------------------------------------------------------------
function CharArrayToString_deprecated(const aCharArray: array of Char): String;
var
iLenCharArray: Integer;
iIdx : Integer;
begin
iLenCharArray := lstrlen(aCharArray);
if iLenCharArray = 0 then Exit;
SetLength(Result, iLenCharArray);
for iIdx := 0 to iLenCharArray - 1 do
Result[iIdx + 1] := aCharArray[iIdx];
end; // ==> CharArrayToString_deprecated()
//=================================================================================================
//-------------------------------------------------------------------------------------------------
// I want to use RtlMoveMemory() to achieve this, but currently it does not work.
// What is the correct way to declare RtlMoveMemory() for this purpose, and how to call it?
//-------------------------------------------------------------------------------------------------
function CharArrayToString(const aCharArray: array of Char): String;
var
iLenCharArray: Integer;
iIdx : Integer;
begin
iLenCharArray := lstrlen(aCharArray); // This length is not including the null terminator
if iLenCharArray = 0 then Exit;
SetLength(Result, iLenCharArray);
RtlMoveMemory_ToString(Result, aCharArray[0], iLenCharArray * 2);
end; // ==> CharArrayToString()
//=================================================================================================
//-------------------------------------------------------------------------------------------------
procedure Test();
var
aCharArray: array of Char;
sDummy : String;
sResult : String;
begin
sDummy := '1234567';
StringToCharArray(sDummy, aCharArray);
// Let's assume aCharArray is returned by a Windows API function
// Of course, this one succeeds
// sResult := CharArrayToString_deprecated(aCharArray);
// I need an advice to make this one works
sResult := CharArrayToString(aCharArray);
// Report the resultant string from the char array
MsgBox('String: ' + sResult + #13#10 +
'Length: ' + IntToStr(Length(sResult)),
mbInformation, MB_OK);
end; // ==> Test()
//=================================================================================================
function InitializeSetup(): Boolean;
begin
Test();
Result := FALSE;
end;
Thanks in advance.
Gladly, after trying several possible combinations by trial and error, I finally managed to copy data from an array of char to a String using RtlMoveMemory function:
procedure RtlMM_CharArrayToStr(
sDest : String; // in
var achSource: Char; // in
const iLength: Integer // in
); external 'RtlMoveMemory#kernel32.dll stdcall';
function CharArrayToString(const aCharArray: array of Char): String;
var
iLenCharArray: Integer;
begin
iLenCharArray := lstrlen(aCharArray); // This length is not including the null terminator
if iLenCharArray = 0 then Exit;
SetLength(Result, iLenCharArray);
RtlMM_CharArrayToStr(Result, aCharArray[0], iLenCharArray * 2);
end; // ==> CharArrayToString()
Thanks to Martin Prikryl for the hints.
Updates
For completeness, here is my function to copy data from a String to an array of char using RtlMoveMemory function:
procedure RtlMM_StrToCharArray(
sDest : array of Char; // in
const sSource: String; // in
const iLength: Integer // in
); external 'RtlMoveMemory#kernel32.dll stdcall';
procedure StringToCharArray(const sStr: String; out aCharArray: array of Char);
var
iLenStr: Integer;
begin
iLenStr := Length(sStr);
if iLenStr = 0 then Exit;
SetArrayLength(aCharArray, iLenStr + 1); // Include a room for a null terminator
RtlMM_StrToCharArray(aCharArray, sStr, iLenStr * 2);
aCharArray[iLenStr] := #0;
end; // ==> StringToCharArray

String to PAnsiChar conversion trouble

These are a C DLL function example and its Delphi translation:
C definition:
DLL_EXPORT int AR_dll_function (const char *terminal_no, const char *description);
Delphi definition:
function Ar_Dll_Function(const TerminalNo: PAnsiChar; const Description: PAnsiChar):Integer;
...
function Ar_Dll_Function(const TerminalNo: PAnsiChar; const Description: PAnsiChar):Integer;
var
MyFunct : function(const TerminalNo: PAnsiChar; const Description: PAnsiChar):Integer;cdecl;
begin
Result := 0;
#MyFunct:=GetProcAddress(HInst,'ar_dll_function');
if Assigned(MyFunct) then
Result := MyFunct(TerminalNo, Description);
end;
I use the above Delphi function like this:
function SP(const s:string): PAnsiChar;
var
UTF8Str: RawByteString;
begin
Result := #0;
SetCodePage(UTF8Str, 0, False);
UTF8Str := UTF8Encode(s);
Result := PAnsiChar(AnsiString(UTF8Str));
end;
...
result := Ar_Dll_Function(SP(dTermNo),SP(descr));
The problem is between the two PAnsiChar parameters. When I go into the DLL function in debug mode, I see that the second PAnsiChar usually is the same as the first parameter, or the same as the function name:
//parameter examples in string :
dtermno:='AT0000058863'; descr:='NAKİT';
//parameter examples in PAnsiChar :
TerminalNo:='AT0000058863'; const Description:='AT0000058863'; //or
TerminalNo:='AT0000058863'; const Description:='ar_dll_function';
How can I solve the problem?
The problem that you have is that SP returns the address of a string buffer that belongs to a local variable in SP. So when SP returns that variable is destroyed, and the pointer is now invalid.
I would call the function like this:
var
dTermNo, descr: string;
....
dTermNo := ...;
descr := ...;
retval := Ar_Dll_Function(PAnsiChar(UTF8String(dTermNo)), PAnsiChar(UTF8String(descr)));
Alternatively you could push the UTF-8 encoding down to the Ar_Dll_Function wrapper:
function Ar_Dll_Function(const TerminalNo, Description: string): Integer;
var
MyFunct: function(TerminalNo, Description: PAnsiChar): Integer; cdecl;
begin
Result := 0;
#MyFunct := GetProcAddress(HInst, 'ar_dll_function');
if Assigned(MyFunct) then
Result := MyFunct(PAnsiChar(UTF8String(TerminalNo)),
PAnsiChar(UTF8String(Description)));
end;

Querying Version Information from executable at runtime

I am trying to query the version details of a file that the installer installs and compare it against the version details of the same file present in the installer being executed. The details are not in the FileVersion or ProductVersion field but can be in other fields like InternalName etc.
I see Win32 APIs for solving this and also some sample code like :
http://delphidabbler.com/articles?article=20
How can I read details of file?
However, some of the data types used in those code samples do not work with Inno Setup. Further, some samples and description seem to indicate that the language and codepage itself will be an array but some samples use it assuming only one entry for language and codepage.
I was stuck at trying to find the language and codepage and based on comments below, I hard coded it for en-us.
I do see this answer which has a code sample for Inno Setup Pascal but the language and codepage calculation not being based on the lplpBufferCP variable makes me doubt its correctness.
Is it possible to read generic version info properties from Inno Setup Pascal script ? If so, please help around how to find the language and code page values.
The code I have written based on the aforesaid solutions is listed below with in-line comments for the problematic portions.
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function GetFileVersionInfoSize(lptstrFilename: String; lpdwHandle: Integer): Integer;
external 'GetFileVersionInfoSize{#AW}#version.dll stdcall delayload';
function GetFileVersionInfo(lptstrFilename: String; dwHandle, dwLen: Integer; var lpData: Byte): Boolean;
external 'GetFileVersionInfo{#AW}#version.dll stdcall delayload';
function VerQueryValue(var pBlock: Byte; lpSubBlock: String; var lplpBuffer: Byte; var puLen: Integer): Boolean;
external 'VerQueryValue{#AW}#version.dll stdcall delayload';
function GetFileVersionProperty(const FileName, PropertyName: String): String;
var
VerSize: Integer;
VerInfo: array of Byte;
Dummy: Integer;
InternalNameArr: array of Byte;
begin
Result := '';
if not FileExists(FileName) then
begin
Log('File ' + FileName + ' does not exist');
Exit;
end;
VerSize := GetFileVersionInfoSize(FileName, 0);
if not VerSize > 0 then
begin
Log('File ' + FileName + ' has no version information');
Exit;
end;
SetArrayLength(VerInfo, VerSize);
if not GetFileVersionInfo(FileName, 0, VerSize, VerInfo[0]) then
begin
Log('Failed to get version info for ' + FileName);
Exit;
end;
if not GetFileVersionInfo(FileName, 0, VerSize, VerInfo[0]) then
begin
Log('Failed to get version info for ' + FileName);
Exit;
end;
{ Getting 'Version size = 2156' }
Log(Format('Version size = %d', [VerSize]));
{ Hard coded value just for testing }
SetArrayLength(InternalNameArr, 512);
{ 040904E4 hard coded for en-us }
{ Is this the correct way of querying the details ? }
{ If not, what needs to be done here }
{ TODO : InternalName hard coded. Use parameter PropertyName }
if VerQueryValue(VerInfo[0], '\StringFileInfo\040904E4\InternalName', InternalNameArr[0], Dummy) then
begin
Log('Failed to query internal name of ' + FileName);
Exit;
end
else
begin
{ What needs to be done here to convert an array of byte to string ? }
{ Do I need to iterate over the array and do the conversion ?}
{ The following does not work because of SetString() being unavailable : }
{ InternalName = SetString(AnsiStr, PAnsiChar(#InternalNameArr[0]), Len);}
{ Getting 'ProductName = 0000' and 'Dummy = 0' }
Log(Format('ProductName = %d%d', [InternalNameArr[0], InternalNameArr[1], InternalNameArr[2], InternalNameArr[3]]));
Log(Format('Dummy = %d', [Dummy]));
end;
{ TODO : Populate Result with appropriate value }
end;
An alternate approach could be to save the file properties of the installed file in registry (I am interested in 1 property of 1 of the files) and have the property available in the installer statically for the new file.
The correct code to retrieve a string from the first language of a file version info is below. The code builds on an answer by #Jens A. Koch to How to write data to an installer on the server?
The code requires Unicode version of Inno Setup.
function GetFileVersionInfoSize(
lptstrFilename: String; lpdwHandle: Integer): Integer;
external 'GetFileVersionInfoSizeW#version.dll stdcall delayload';
function GetFileVersionInfo(
lptstrFilename: String; dwHandle, dwLen: Integer; var lpData: Byte): Boolean;
external 'GetFileVersionInfoW#version.dll stdcall delayload';
function VerQueryValue(
var pBlock: Byte; lpSubBlock: String; var lplpBuffer: DWord;
var Len: Integer): Boolean;
external 'VerQueryValueW#version.dll stdcall delayload';
procedure RtlMoveMemoryAsString(Dest: string; Source: DWord; Len: Integer);
external 'RtlMoveMemory#kernel32.dll stdcall';
procedure RtlMoveMemoryAsBytes(Dest: array of Byte; Source: DWord; Len: Integer);
external 'RtlMoveMemory#kernel32.dll stdcall';
function GetFileVerInfo(FileName, VerName: String): String;
var
Len: Integer;
FileVerInfo: array of Byte;
Lang: array of Byte;
Buffer: DWord;
LangCodepage: string;
SubBlock: string;
begin
Result := '';
if FileExists(FileName) then
begin
Len := GetFileVersionInfoSize(FileName, 0);
if Len > 0 then
begin
SetArrayLength(FileVerInfo, Len);
if GetFileVersionInfo(FileName, 0, Len, FileVerInfo[0]) then
begin
if VerQueryValue(
FileVerInfo[0], '\VarFileInfo\Translation', Buffer, Len) then
begin
if Len >= 4 then
begin
SetArrayLength(Lang, 4);
RtlMoveMemoryAsBytes(Lang, Buffer, 4);
LangCodepage :=
Format('%.2x%.2x%.2x%.2x', [Lang[1], Lang[0], Lang[3], Lang[2]]);
SubBlock :=
Format('\%s\%s\%s', ['StringFileInfo', LangCodepage, VerName]);
if VerQueryValue(FileVerInfo[0], SubBlock, Buffer, Len) then
begin
SetLength(Result, Len - 1);
RtlMoveMemoryAsString(Result, Buffer, (Len - 1) * 2);
end;
end;
end;
end;
end;
end;
end;

Writing binary file in Inno Setup

How does one write to a binary file in Inno Setup script? It's a configuration file I want to edit in the PrepareToInstall step. The problem is that I'm looking at the support functions:
TStream = class(TObject)
function Read(Buffer: String; Count: Longint): Longint;
function Write(Buffer: String; Count: Longint): Longint;
function Seek(Offset: Longint; Origin: Word): Longint;
procedure ReadBuffer(Buffer: String; Count: Longint);
procedure WriteBuffer(Buffer: String; Count: Longint);
function CopyFrom(Source: TStream; Count: Longint): Longint;
property Position: Longint; read write;
property Size: Longint; read write;
end;
And it seems even the most basic write function writes strings. Should I just do it in a batch script?
Consider the string in the TStream interface as a buffer of chars/bytes.
It's bit more complicated with Unicode version of Inno Setup, where the string is an array of 2-byte chars (comparing to legacy Ansi version, as there one byte equals one char – Though as of Inno Setup 6, Unicode is actually the only version available anyway).
See also Read bytes from file at desired position with Inno Setup.
To convert a hex string to the actual binary data, you can use the CryptStringToBinary Windows API function.
The following code works both in Ansi and Unicode version of Inno Setup.
#ifndef Unicode
const CharSize = 1;
#define AW "A"
#else
const CharSize = 2;
#define AW "W"
#endif
function CryptStringToBinary(
sz: string; cch: LongWord; flags: LongWord; binary: string; var size: LongWord;
skip: LongWord; flagsused: LongWord): Integer;
external 'CryptStringToBinary{#AW}#crypt32.dll stdcall';
const
CRYPT_STRING_HEX = $04;
procedure WriteHexToFile(Hex: string; FileName: string);
var
Stream: TFileStream;
Buffer: string;
Size: LongWord;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
SetLength(Buffer, (Length(Hex) div 2*CharSize) + CharSize - 1);
Size := Length(Hex) div 2;
if (CryptStringToBinary(
Hex, Length(Hex), CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> Length(Hex) div 2) then
begin
RaiseException('Error decoding hex string');
end;
Stream.WriteBuffer(Buffer, Size);
finally
Stream.Free;
end;
end;
Use it like:
procedure WriteHexToFileTest;
var
Hex: string;
begin
Hex :=
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' +
'202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f' +
'404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f' +
'606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f' +
'808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f' +
'a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
'c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' +
'e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff';
WriteHexToFile(Hex, 'my_binary_file.dat');
end;

In Delphi can a string be converted to a set

For instance
Font.Style = StringToSet('[fsBold, fsUnderline]');
of course there would need to be some typeinfo stuff in there, but you get the idea. I'm using Delphi 2007.
check this code, is not exactly the same syntax which you propose , but works setting the value of a set from a string.
uses
TypInfo;
procedure StringToSet(Const Values,AProperty:string;Instance: TObject);
begin
if Assigned(GetPropInfo(Instance.ClassInfo, AProperty)) then
SetSetProp(Instance,AProperty,Values);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
StringToSet('[fsBold, fsUnderline, fsStrikeOut]','Style',Label1.Font);
end;
Also see my old post: SetToString, StringToSet for a solution (Delphi 2007, IIRC) without a need for published property RTTI:
uses
SysUtils, TypInfo;
function GetOrdValue(Info: PTypeInfo; const SetParam): Integer;
begin
Result := 0;
case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Result := Byte(SetParam);
otSWord, otUWord:
Result := Word(SetParam);
otSLong, otULong:
Result := Integer(SetParam);
end;
end;
procedure SetOrdValue(Info: PTypeInfo; var SetParam; Value: Integer);
begin
case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Byte(SetParam) := Value;
otSWord, otUWord:
Word(SetParam) := Value;
otSLong, otULong:
Integer(SetParam) := Value;
end;
end;
function SetToString(Info: PTypeInfo; const SetParam; Brackets: Boolean): AnsiString;
var
S: TIntegerSet;
TypeInfo: PTypeInfo;
I: Integer;
begin
Result := '';
Integer(S) := GetOrdValue(Info, SetParam);
TypeInfo := GetTypeData(Info)^.CompType^;
for I := 0 to SizeOf(Integer) * 8 - 1 do
if I in S then
begin
if Result <> '' then
Result := Result + ',';
Result := Result + GetEnumName(TypeInfo, I);
end;
if Brackets then
Result := '[' + Result + ']';
end;
procedure StringToSet(Info: PTypeInfo; var SetParam; const Value: AnsiString);
var
P: PAnsiChar;
EnumInfo: PTypeInfo;
EnumName: AnsiString;
EnumValue, SetValue: Longint;
function NextWord(var P: PAnsiChar): AnsiString;
var
I: Integer;
begin
I := 0;
// scan til whitespace
while not (P[I] in [',', ' ', #0,']']) do
Inc(I);
SetString(Result, P, I);
// skip whitespace
while P[I] in [',', ' ',']'] do
Inc(I);
Inc(P, I);
end;
begin
SetOrdValue(Info, SetParam, 0);
if Value = '' then
Exit;
SetValue := 0;
P := PAnsiChar(Value);
// skip leading bracket and whitespace
while P^ in ['[',' '] do
Inc(P);
EnumInfo := GetTypeData(Info)^.CompType^;
EnumName := NextWord(P);
while EnumName <> '' do
begin
EnumValue := GetEnumValue(EnumInfo, EnumName);
if EnumValue < 0 then
begin
SetOrdValue(Info, SetParam, 0);
Exit;
end;
Include(TIntegerSet(SetValue), EnumValue);
EnumName := NextWord(P);
end;
SetOrdValue(Info, SetParam, SetValue);
end;
Example usage:
var
A: TAlignSet;
S: AnsiString;
begin
// set to string
A := [alClient, alLeft, alTop];
S := SetToString(TypeInfo(TAlignSet), A, True);
ShowMessage(Format('%s ($%x)', [S, Byte(A)]));
// string to set
S := '[alNone, alRight, alCustom]';
StringToSet(TypeInfo(TAlignSet), A, S);
ShowMessage(Format('%s ($%x)', [SetToString(TypeInfo(TAlignSet), A, True), Byte(A)]));
end;
You have right function name already - StringToSet. However, usage is tricky:
procedure TForm1.FormClick(Sender: TObject);
type PFontStyles = ^TFontStyles; // typecast helper declaration
var Styles: Integer; // receives set bitmap after parsing
{$IF SizeOf(TFontStyles) > SizeOf(Integer)}
{$MESSAGE FATAL 'Panic. RTTI functions will work with register-sized sets only'}
{$IFEND}
begin
Styles := StringToSet( // don't forget to use TypInfo (3)
PTypeInfo(TypeInfo(TFontStyles)), // this kludge is required for overload (1)
'[fsBold, fsUnderline]'
);
Font.Style := PFontStyles(#Styles)^; // hack to bypass strict typecast rules (2)
Update(); // let form select amended font into Canvas
Canvas.TextOut(0, 0, 'ME BOLD! ME UNDERLINED!');
end;
(1) because initially borland limited this function family to PropInfo pointers and TypeInfo() intrinsic returns untyped pointer, hence the typecast
(2) typecasting requires types to be of same size, hence the referencing and dereferencing to different type (TFontStyles is a Byte)
Nitpicker special: (3) This snippet works out of the box in D2010+. Earlier versions has required dependency missing - namely StringToSet(TypeInfo: PTypeInfo; ... overload (see docwiki link above). This problem is solvable by copypasting (yeah, but TTypeInfo is lower-level than TPropInfo) original function and doing 2 (two) minor edits. By obvious reasons i'm not going to publish copyrighted code, but here is the relevant diff:
1c1,2
< function StringToSet(PropInfo: PPropInfo; const Value: string): Integer;
---
> {$IF RTLVersion < 21.0}
> function StringToSet(TypeInfo: PTypeInfo; const Value: string): Integer; overload;
37c38
< EnumInfo := GetTypeData(PropInfo^.PropType^)^.CompType^;
---
> EnumInfo := GetTypeData(TypeInfo)^.CompType^;
47a49
> {$IFEND}

Resources