Innosetup is killing me. I am getting a RUNTIME 'Type Mismatch' error, where it is, to me, quite unexpected. I am using Inno-setup 5.5.3 (u)
(where the 'u' means the unicode version)
I am attempting to pass a two dimensional array into a method.
Here is my complete example.
[Setup]
AppName=EmptyProgram
AppVerName=EmptyProgram 1
UsePreviousAppDir=false
DefaultDirName={pf}\EmptyProgram
Uninstallable=false
OutputBaseFilename=HelloWorld
PrivilegesRequired=none
[Messages]
SetupAppTitle=My Title
[Code]
var
langMap : array[0..3] of array[0..1] of String;
function getMapVal(map : array of array[0..1] of String; key: String ) : String;
begin
Result:='not testing the body of the method';
end;
function InitializeSetup(): Boolean;
begin
MsgBox('Hello world.', mbInformation, MB_OK);
getMapVal(langMap, 'hello'); // this line here fails with type mismatch! Why?
Result := FALSE;
end;
This example would run, but for the invokation of the method:
getMapVal(langMap, 'hello');
It compiles, and therefore is happy with the declaration. But on invokation, mismatch error. What am I doing wrong?
First of all, you're not making a hash map, but a pure key value list. There's currently no way to make a real generics hash map in InnoSetup at this time. Anyway, your current code needs a complete refactor. I'd rather write it this way:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
type
TKey = string;
TValue = string;
TKeyValue = record
Key: TKey;
Value: TValue;
end;
TKeyValueList = array of TKeyValue;
function TryGetValue(const KeyValueList: TKeyValueList; const Key: TKey;
var Value: TValue): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to GetArrayLength(KeyValueList) - 1 do
if KeyValueList[I].Key = Key then
begin
Result := True;
Value := KeyValueList[I].Value;
Exit;
end;
end;
procedure InitializeWizard;
var
I: Integer;
Value: TValue;
KeyValueList: TKeyValueList;
begin
SetArrayLength(KeyValueList, 3);
for I := 0 to 2 do
begin
KeyValueList[I].Key := 'Key' + IntToStr(I);
KeyValueList[I].Value := 'Value' + IntToStr(I);
end;
if TryGetValue(KeyValueList, 'Key2', Value) then
MsgBox('Value: ' + Value, mbInformation, MB_OK);
end;
Related
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
How can I execute an external program from a Linux console application created in Delphi 10.2 Tokyo?
What I want to do is execute a shell command with parameters like
/home/test/qrencode -o /tmp/abc.png '08154711'
I do not need the output of the program but it should be executed synchronously.
It is easy in Windows environments but as 64 bit Linux support in Delphi (after Kylix) is quite new, I could not find any hints on the Web by now.
Any tip helping me to solve that is very appreciated.
Thanks in advance!
Davids hint pointed me to an example that helped creating the solution. The most tricky part was finding out how to convert a Delphi string to a MarshaledAString as the example used a const string as argument for popen. I tested on RHEL 7.3, runs like a charm.
uses
...
System.SysUtils,
Posix.Base,
Posix.Fcntl,
...;
type
TStreamHandle = pointer;
function popen(const command: MarshaledAString; const _type: MarshaledAString): TStreamHandle; cdecl;
external libc name _PU + 'popen';
function pclose(filehandle: TStreamHandle): int32; cdecl; external libc name _PU + 'pclose';
function fgets(buffer: pointer; size: int32; Stream: TStreamHandle): pointer; cdecl; external libc name _PU + 'fgets';
function runCommand(const acommand: MarshaledAString): String;
// run a linux shell command and return output
// Adapted from http://chapmanworld.com/2017/04/06/calling-linux-commands-from-delphi/
var
handle: TStreamHandle;
data: array [0 .. 511] of uint8;
function bufferToString(buffer: pointer; maxSize: uint32): string;
var
cursor: ^uint8;
endOfBuffer: nativeuint;
begin
if not assigned(buffer) then
exit;
cursor := buffer;
endOfBuffer := nativeuint(cursor) + maxSize;
while (nativeuint(cursor) < endOfBuffer) and (cursor^ <> 0) do
begin
result := result + chr(cursor^);
cursor := pointer(succ(nativeuint(cursor)));
end;
end;
begin
result := '';
handle := popen(acommand, 'r');
try
while fgets(#data[0], sizeof(data), handle) <> nil do
begin
result := result + bufferToString(#data[0], sizeof(data));
end;
finally
pclose(handle);
end;
end;
function createQRCode(id, fn: string): string;
// Create qr-code using qrencode package
begin
deletefile(fn);
if fileExists(fn) then
raise Exception.create('Old file not deleted!');
// I am targeting rhel for now, so I know the path for sure
result := runCommand(MarshaledAString(UTF8STring('/usr/bin/qrencode -o ' + fn + ' ''' + id + '''')));
if not fileExists(fn) then
raise Exception.create('New file not created!');
end;
function testqr: String;
// Test QR Code creation with error handling
// QREncode does not output anything but who knows ;-)
begin
try
result := createQRCode('08154711', '/tmp/myqrcode.png');
except
on e: Exception do
begin
result := 'Error: ' + e.message;
end;
end;
end;
I wrote this code to do this task
uses
System.SysUtils,
System.Classes,
Posix.Base,
Posix.Fcntl;
type
TStreamHandle = pointer;
TLinuxUtils = class
public
class function RunCommandLine(ACommand : string) : TStringList;overload;
class function RunCommandLine(Acommand : string; Return : TProc<String>) : boolean; overload;
class function findParameter(AParameter : string) : boolean;
end;
function popen(const command: MarshaledAString; const _type: MarshaledAString): TStreamHandle; cdecl; external libc name _PU + 'popen';
function pclose(filehandle: TStreamHandle): int32; cdecl; external libc name _PU + 'pclose';
function fgets(buffer: pointer; size: int32; Stream: TStreamHAndle): pointer; cdecl; external libc name _PU + 'fgets';
implementation
class function TLinuxUtils.RunCommandLine(ACommand : string) : TStringList;
var
Handle: TStreamHandle;
Data: array[0..511] of uint8;
M : TMarshaller;
begin
Result := TStringList.Create;
try
Handle := popen(M.AsAnsi(PWideChar(ACommand)).ToPointer,'r');
try
while fgets(#data[0],Sizeof(Data),Handle)<>nil do begin
Result.Add(Copy(UTF8ToString(#Data[0]),1,UTF8ToString(#Data[0]).Length -1));//,sizeof(Data)));
end;
finally
pclose(Handle);
end;
except
on E: Exception do
Result.Add(E.ClassName + ': ' + E.Message);
end;
end;
class function TLinuxUtils.RunCommandLine(Acommand : string; Return : TProc<string>) : boolean;
var
Handle: TStreamHandle;
Data: array[0..511] of uint8;
M : TMarshaller;
begin
Result := false;
try
Handle := popen(M.AsAnsi(PWideChar(ACommand)).ToPointer,'r');
try
while fgets(#data[0],Sizeof(Data),Handle)<>nil do begin
Return(Copy(UTF8ToString(#Data[0]),1,UTF8ToString(#Data[0]).Length -1));//,sizeof(Data)));
end;
finally
pclose(Handle);
end;
except
on E: Exception do
Return(E.ClassName + ': ' + E.Message);
end;
end;
class function TLinuxUtils.findParameter(AParameter : string) : boolean;
var
I : Integer;
begin
Result := false;
for I := 0 to Pred(ParamCount) do
begin
Result := AParameter.ToUpper = ParamStr(i).ToUpper;
if Result then
Break;
end;
end;
You do not have to worry about MarshaledString.
The RunCommandLine function has 2 ways to be called. The first you have the return on a TStringList with all the lines that the console will return.
The second you can pass an anonymous method that will treat line by line of return of the command line.
I'm having an issue here with the following:
function InitializeSetup(): Boolean;
var
ResultCode:Integer;
begin
Result := true;
if MsgBox('Wanna help?',mbConfirmation, MB_YESNO )= IDYES then
begin
CreateBatch();
Exec('cmd.exe', '/c "' +ExpandConstant('{tmp}\batch.bat'),'',SW_HIDE, ewWaitUntilTerminated, ResultCode);
Result:= false;
end;
end;
in the batch file i got the following :
#ECHO OFF
D:
cd D:\_INSTALLER\Output
"installer.exe" /SAVEINF="opt.txt"
So it basically re-open the installer , over and over again ... ( infinite loop )
Is there any way to ask : Wanna help? only for the 1st time ? and if the user clicked yes, the batch should be executed , else if the user clicked no it should continue the install normally.
Thanks in advance for the support ,
BeGiN.
With the help of TLama and his post i achieved my goal by using the following script:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
HINSTANCE = THandle;
procedure ExitProcess(exitCode:integer);external 'ExitProcess#kernel32.dll stdcall';
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE;
external 'ShellExecute{#AW}#shell32.dll stdcall';
var
withINF: Boolean;
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Break;
end;
end;
//Initialize setup
function InitializeSetup(): Boolean;
var
ResultCode:Integer;
Params: string;
RetVal: HINSTANCE;
begin
Result := true;
withINF := CmdLineParamExists('/SAVEINF=opt.txt');
if not withINF then
begin
Params := '/SAVEINF=opt.txt';
ShellExecute(0, 'open',ExpandConstant('{srcexe}'), Params, '', SW_SHOW);
ExitProcess(0);
end;
end;
L.E: A shorter code for doing the same thing also made by TLama(multilingual support added):
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
ShowLanguageDialog = yes
[Languages]
Name: "en"; MessagesFile: "compiler:Default.isl"
Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl"
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
HINSTANCE = THandle;
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE;
external 'ShellExecute{#AW}#shell32.dll stdcall';
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Break;
end;
end;
function InitializeSetup: Boolean;
var
Params: string;
begin
// prepare Params variable for reusing
if ExpandConstant('{language}') = 'en' then begin
Params := '/SAVEINF=opt.txt /LANG=en';
end;
if ExpandConstant('{language}') = 'nl' then begin
Params := '/SAVEINF=opt.txt /LANG=nl';
end;
// allow this setup to run if the expected parameter is specified; or, if it is not, allow to run it
// when ShellExecute fails; it works like this - first evaluates the CmdLineParamExists function and
// if that returns True, the second part of the statement (ShellExecute) won't run (evaluate); when
// the parameter is not found, the CmdLineParamExists returns False and statement evaluation goes on,
// ShellExecute attempts to run the setup and to the Result returns True (allow this instance to run)
// when the function fails for some reason (the returned value <= 32); in other words, you will allow
// this setup instance to run if executing of the new setup instance fails
Result := CmdLineParamExists('/SAVEINF=opt.txt') or (ShellExecute(0, '', ExpandConstant('{srcexe}'), Params, '', SW_SHOW) <= 32);
end;
Regards,
BeGiN
I see not reason for this but you can simply solve this by creating temp file.
If user clicks Yes (first time) then create some file (any file with some random content) just before CreateBatch() is called.
Then simply check for existence of this file - if it exists user already clicked Yes (once) and do what ever you want.
After reading this post How to read a text file from the Internet resource?, I've adapted the code to what I need but I have some problems.
What I want to do is that when I run the setup, it checks for new updates. 1) If there isn't a new update, don't show any message. 2) And if there is a new update, show a message asking whether you want to download it or not.
This is my code:
procedure InitializeWizard;
var
DxLastVersion: string;
DxSetupVersion: String;
begin
if DownloadFile('http://dex.wotanksmods.com/latestver.txt', DxLastVersion) then
MsgBox(DxLastVersion, mbInformation, MB_YESNO)
else
MsgBox(DxLastVersion, mbError, MB_OK)
end;
Thanks so much in advanced.
Since you've decided to use a common version string pattern, you'll need a function which will parse and compare a version string of your setup and the one downloaded from your site. And because there is no such function built-in in Inno Setup, you'll need to have your own one.
I've seen a few functions for comparing version strings, like e.g. the one used in this script, but I've decided to write my own. It can detect an invalid version string, and treats the missing version chunks as to be 0, which causes comparison of version strings like follows to be equal:
1.2.3
1.2.3.0.0.0
The following script might do what you want (the setup version is defined by the AppVersion directive):
[Setup]
AppName=My Program
AppVersion=1.2.3
DefaultDirName={pf}\My Program
[Code]
const
SetupURL = 'http://dex.wotanksmods.com/setup.exe';
VersionURL = 'http://dex.wotanksmods.com/latestver.txt';
type
TIntegerArray = array of Integer;
TCompareResult = (
crLesser,
crEquals,
crGreater
);
function Max(A, B: Integer): Integer;
begin
if A > B then Result := A else Result := B;
end;
function CompareValue(A, B: Integer): TCompareResult;
begin
if A = B then
Result := crEquals
else
if A < B then
Result := crLesser
else
Result := crGreater;
end;
function AddVersionChunk(const S: string; var A: TIntegerArray): Integer;
var
Chunk: Integer;
begin
Chunk := StrToIntDef(S, -1);
if Chunk <> -1 then
begin
Result := GetArrayLength(A) + 1;
SetArrayLength(A, Result);
A[Result - 1] := Chunk;
end
else
RaiseException('Invalid format of version string');
end;
function ParseVersionStr(const S: string; var A: TIntegerArray): Integer;
var
I: Integer;
Count: Integer;
Index: Integer;
begin
Count := 0;
Index := 1;
for I := 1 to Length(S) do
begin
case S[I] of
'.':
begin
AddVersionChunk(Copy(S, Index, Count), A);
Count := 0;
Index := I + 1;
end;
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
begin
Count := Count + 1;
end;
else
RaiseException('Invalid char in version string');
end;
end;
Result := AddVersionChunk(Copy(S, Index, Count), A);
end;
function GetVersionValue(const A: TIntegerArray; Index,
Length: Integer): Integer;
begin
Result := 0;
if (Index >= 0) and (Index < Length) then
Result := A[Index];
end;
function CompareVersionStr(const A, B: string): TCompareResult;
var
I: Integer;
VerLenA, VerLenB: Integer;
VerIntA, VerIntB: TIntegerArray;
begin
Result := crEquals;
VerLenA := ParseVersionStr(A, VerIntA);
VerLenB := ParseVersionStr(B, VerIntB);
for I := 0 to Max(VerLenA, VerLenB) - 1 do
begin
Result := CompareValue(GetVersionValue(VerIntA, I, VerLenA),
GetVersionValue(VerIntB, I, VerLenB));
if Result <> crEquals then
Exit;
end;
end;
function DownloadFile(const URL: string; var Response: string): Boolean;
var
WinHttpRequest: Variant;
begin
Result := True;
try
WinHttpRequest := CreateOleObject('WinHttp.WinHttpRequest.5.1');
WinHttpRequest.Open('GET', URL, False);
WinHttpRequest.Send;
Response := WinHttpRequest.ResponseText;
except
Result := False;
Response := GetExceptionMessage;
end;
end;
function InitializeSetup: Boolean;
var
ErrorCode: Integer;
SetupVersion: string;
LatestVersion: string;
begin
Result := True;
if DownloadFile(VersionURL, LatestVersion) then
begin
SetupVersion := '{#SetupSetting('AppVersion')}';
if CompareVersionStr(LatestVersion, SetupVersion) = crGreater then
begin
if MsgBox('There is a newer version of this setup available. Do ' +
'you want to visit the site ?', mbConfirmation, MB_YESNO) = IDYES then
begin
Result := not ShellExec('', SetupURL, '', '', SW_SHOW, ewNoWait,
ErrorCode);
end;
end;
end;
end;
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}