Is there any way to skip the default params, say suppose my method declaration is like this:
procedure MyProc1(var isAttr1: Boolean = FALSE;
var isAttr2: Boolean = FALSE; var isAttr3: Boolean = FALSE);
I can't call the function like this:
Self.MyProc1( , , Attr3);
because I don't want unnecessary var declarations, at the same time I want the last param return value (it is a var type)
Thank for help in advance.
Sorry, you can't do this. What's more you can't have a var parameter with a default as you have with isAttr1.
If your parameters had sufficiently different parameter types, then you could use overloaded methods as an alternative to default parameters, which is a technique that I personally prefer. But your parameters are all Boolean and so you can't do that here.
Other posters have suggested something like this:
procedure Myfunc1(var isAttr1, isAttr2, isAttr3: Boolean); overload;
procedure Myfunc1(var isAttr3: Boolean); overload;
This will compile and work but would be counter to the principle of least surprise. For overloaded procedures like this, you would expect, for the procedure with only a single parameter, for that single parameter to be the first parameter of the procedure with multiple parameters. In other words you would expect:
procedure Myfunc1(var isAttr1, isAttr2, isAttr3: Boolean); overload;
procedure Myfunc1(var isAttr1: Boolean); overload;
In this case you should use the overload directive:
The Overload directive allows you to
have different versions of the same
named function or procedure with
different arguments.
Yes, make an overloaded version of MyFunc1(isAttr3 : boolean = FALSE); overload;
Have it make up the dummy params and pass them for you.
As the other poster points out, you can do this with VAR.
Also, it's wrong to call it MyFunc if it's not a Function. Call it MyProc!
My solution:
function ov(p1 : boolean; p2 : boolean; p3 : boolean) : boolean; overload;
begin
result := p1 or p2 or p3;
end;
function ov(p3 : boolean) : boolean; overload;
begin
result := ov(false, false, p3);
end;
Now you can have your choice of:
ov(TestBool3)
or
ov(TestBool1, TestBool2, TestBool3)
Related
So given this function, I get the error "Identifier Expected" on the GetRoot := ROOTPage.Values[0]; line. I expect it is telling me that the ROOTPage is not defined?
const
DefaultRoot = 'C:\IAmGRoot';
Var
ROOTPage : TInputQueryWizardPage;
procedure SetupRoot;
begin
ROOTPage := CreateInputQueryPage(wpUserInfo,
ExpandConstant('{cm:RootTitle}'),
ExpandConstant('{cm:RootInstructions}'),
ExpandConstant('{cm:RootDescription}') + ' "' + DefaultRoot + '"'
);
ROOTPage.Add(ExpandConstant('{cm:SSRoot}') + ':', False);
ROOTPage.Values[0] := ExpandConstant('{DefaultRoot}');
// add SSROOT to path
end;
function GetRoot : string;
begin
GetRoot := ROOTPage.Values[0];
end;
How should I interpret this error. What is an identifier in Pascal?
This page tells me that identifiers are variable names. Perhaps I need to expand the ROOTPage.Values[0] in some way since I am referencing an array from an Inno Setup object?
Or maybe I need to return the value differently. I saw one page on Pascal that said that you needed to avoid assigning to the function value on parameter less functions to avoid a recursive loop. Does this mean I should pass in a dummy value? or is there a different syntax? That page did not explain.
I secretly think my real issue is that I am not defining my function correctly... but well. That much compiles at least. This question could become: How do you handle a parameter-less function in Pascal?
I do not think Inno Setup is part of the issue, but I am working with Inno Setup in case that is important.
Update:
It doesn't seem to be the array because this gets the same error:
const
DefaultRoot = 'C:\IAmGRoot';
function GetRoot : string;
begin
GetRoot := DefaultRoot;
end;
Update:
This link has said that the function name can be replaced/ should be replaced with the key word Result such as the following code. I actually knew this, but the Inno Setup compiler does not recognize this as valid syntax. It then tells me my function is an invalid prototype.
function GetRoot : string;
begin
Result := DefaultRoot;
end;
Update:
If I do this I get "Invalid Prototype for GetRoot"
function GetRoot : boolean;
begin
Result := False;
end;
Update for #Martin Prikryl:
Well I use it a few places but the typical use would be like this:
[Files]
Source: "C:\ValidPath\Release\*"; DestDir: "{app}\bin"; Components: DefinedComponent
Source: "C:\ValidPath\Deployment\*"; DestDir: "{code:GetRoot}\"; Flags: ignoreversion recursesubdirs; Components: DefinedComponent
Identifier expected
Your code would be correct in a Pascal, but it does not compile in Pascal Script.
In Pascal, when you want to assign a return value of a function, you either assign the value to a "variable" with a name of the function or to Result variable.
So this is correct:
function GetRoot: string;
begin
GetRoot := ROOTPage.Values[0];
end;
And this too (both are equivalent):
function GetRoot: string;
begin
Result := ROOTPage.Values[0];
end;
In the Pascal Script, only the Result works. When you use the name of the function, you get the "Identifier expected."
Invalid prototype
You get this when the function is called from outside of the Code section and a specific parameter list/return value is required. But you didn't tell us, what you use the GetRoot function for.
There are two places, where you can use a custom function in Inno Setup:
Check parameter: For this the function must return a Boolean and take either no parameter or one parameter (the parameter type is determined by a value you provide in the Check parameter).
function MyProgCheck(): Boolean;
function MyDirCheck(DirName: String): Boolean;
Scripted Constants: The function must return a string and take one string parameter, even if no parameter is provided in the scripted constant. I assume this is your use case. If you do not need any parameter, just declare it, but do not use it:
function GetRoot(Param: String): string;
begin
Result := ROOTPage.Values[0];
end;
So, I'm trying to make a component that will do the job on setting the settings of a excel, libreoffice, etc... cells. At first I just wanted to set the value, but now, I need to change cell background color, change font name, style, set a formula, etc... So for that, I decided to do a type that will hold all the things I want to change and so, I did this:
type
TMyCell = class
private
FBgColor: TColor;
FValue: String;
FFormula: String;
FFormat: String;
FFont: TFont;
public
constructor Create;
destructor Destroy;
property Value: String read FValue write FValue;
property Formula: String read FFormula write FFormula;
property Format: String read FFormat write FFormat;
property BgColor: TColor read FBgColor write FBgColor;
property Font: TFont read FFont write FFont;
end;
{ TMyCell }
constructor TMyCell.Create;
begin
FFont := TFont.Create;
end;
destructor TMyCell.Destroy;
begin
FFont.Free;
end;
And my component look like this:
type
TMyPlan = class(TComponent)
private
FExcel: Variant;
procedure SetMyCell(Row, Column: Integer; Value: TMyCell);
function GetMyCell(Row, Column: Integer): TMyCell;
public
constructor Create(AOwner: TComponent);
destructor Destroy;
property Cell[Row, Column: Integer]: TMyCell read GetMyCell write SetMyCell;
end;
{ TMyPlan }
constructor TMyPlan.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FExcel := CreateOleObject('Excel.Application');
FExcel.Workbooks.Add(1);
end;
destructor TMyPlan.Destroy;
begin
FExcel := Unassigned;
inherited;
end;
function TMyPlan.GetMyCell(Row, Column: Integer): TMyCell;
begin
Result := TMyCell.Create;
Result.Value := FExcel.Cells[Row, Column];;
end;
procedure TMyPlan.SetMyCell(Row, Column: Integer; Value: TMyCell);
begin
FExcel.Cells[Row, Column] := Value.Value;
end;
Just to let you know, I already did some components, and I'm still learning how to do them properly, so this may have a not decent structure, anyway, this is the first time that I'm trying to do something like this, a property that has input parameters with subproperties, and it doesn't seem to work as I though it would.
Back to the topic, it doesn't matter how I call my property
Set: MyPlan.Cell[1, 1].Value := '1';
Get: ShowMessage(MyPlan.Cell[1, 1].Value);
Either way only the GetMyCell function is triggered. Why's that?
See my answer to this question: "Left side cannot be assigned to" for record type properties in Delphi
While what you're doing isn't quite the same thing, it is similar. However, in your case, you're allocating a new instance of TMyCell for every access to GetMyCell. This "temporary" instance is isn't being freed and will leak (Unless you're doing this on one of the ARC platforms).
The reason your SetMyCell isn't being called is because you're not actually setting the cell itself, rather you're setting a value on the cell instance (that I explained above is leaking).
What am I doign wrong with the following code
function CompareFloat(List: TStringList; Index1, Index2: Integer): Integer;
and I call it as :
var
SL :TstringList;
SL.CustomSort(CompareFloat);
//SL.CustomSort(#CompareFloat); // Tried this one also
The first function call 'SL.CustomSort(CompareFloat)' retrieves that error from compiler "Error: Wrong number of parameters specified for call to "CompareFloat"
Second function call 'SL.CustomSort(#CompareFloat)' retrieves that error from compiler Error: Only class methods can be referred with class references
SL.CustomSort(CompareFloat); works if you add {$mode delphi} directive to somewhere to the beginning of a unit.
However SL.CustomSort(#CompareFloat); should work fine. Make sure the error message is not caused by something else.
Example:
program Project1;
//{$mode delphi}
uses
Classes,
SysUtils;
function CompareFloat(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := StrToInt(List[Index1]) - StrToInt(List[Index2]);
end;
var
SL: TStringList;
begin
SL := TStringList.Create;
try
SL.Add('3');
SL.Add('2');
SL.Add('1');
SL.CustomSort(#CompareFloat);
//SL.CustomSort(CompareFloat);
Writeln(SL[0], SL[1], SL[2]);
Readln;
finally
SL.Free;
end;
end.
This code
procedure MyThreadTestA(const AStr: string);
Is faster than
procedure MyThreadTestB(AStr: string);
Whilst doing the same work, both pass a pointer.
However version B 'correctly' updates the referencecount of AStr and makes a copy if I change it.
Version A passes just a pointer and only the compiler prevents me from changing AStr.
Version A is not safe if I do dirty tricks in Assembler or otherwise to circumvent the compiler protection, this is well known but...
Is passed AStr by reference as a const parameters thread safe?
What happens if AStr's reference count in some other thread goes to zero and the string is destroyed?
No, such tricks are not thread-safe. Const prevents the add-ref, so changes by another thread will affect the value in unpredictable ways. Sample program, try altering the const in the definition of P:
{$apptype console}
uses SysUtils, Classes, SyncObjs;
type
TObj = class
public
S: string;
end;
TWorker = class(TThread)
public
procedure Execute; override;
end;
var
lock: TCriticalSection;
obj: TObj;
procedure P(const x: string);
// procedure P(x: string);
begin
Writeln('P(1): x = ', x);
Writeln('Releasing obj');
lock.Release;
Sleep(10); // give worker a chance to run
Writeln('P(2): x = ', x);
end;
procedure TWorker.Execute;
begin
// wait until TMonitor is freed up
Writeln('Worker started...');
lock.Acquire;
Writeln('worker fiddling with obj.S');
obj.S := 'bar';
TMonitor.Exit(obj);
end;
procedure Go;
begin
lock := TCriticalSection.Create;
obj := TObj.Create;
obj.S := 'foo';
UniqueString(obj.S);
lock.Acquire;
TWorker.Create(False);
Sleep(10); // give worker a chance to run and block
P(obj.S);
end;
begin
Go;
end.
But it's not just limited to threads; modifying the underlying variable location has similar effects:
{$apptype console}
uses SysUtils, Classes, SyncObjs;
type
TObj = class
public
S: string;
end;
var
obj: TObj;
procedure P(const x: string);
begin
Writeln('P(1): x = ', x);
obj.S := 'bar';
Writeln('P(2): x = ', x);
end;
procedure Go;
begin
obj := TObj.Create;
obj.S := 'foo';
UniqueString(obj.S);
P(obj.S);
end;
begin
Go;
end.
To add to Barry's answer: It is definitely thread-safe if the string that got passed came from a local variable inside the callers scope.
In that case that local variable will hold a valid reference and the only way (assuming just valid pascal code, no fiddling around in asm) for that local variable to be changed is if your call returns.
This also includes all cases where the source of the string variable is the result of a function call (including property access, e.g. TStrings.Strings[]) because in this case the compiler has to store the string in a local temp variable.
Thread-safety problems can only result if you are directly passing a string from a location where that string can be changed (by the same or another thread) before your call returns.
I've got a almost completed app now and the next feature I want to implement is threading. I chose to go with BeginThread(), although am aware of TThread in delphi. The problem I'm coming across is the structure of BeginThread() call. Normally the line in the program that would call the function I want to be threaded is
CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
op is a integer.
The line I've switched it out for to create a thread from it is
BeginThread(nil,0,CompareFiles,Addr('form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op'),0,x);
From the little amount of infromation I can find on how to actually use BeginThread() this should be a fine call, however on compling all I get is complier errors regarding the structure of my BeginThread() statement paramenters.
EDIT FOR INFORMATION.
The current procedure that calls CompareFiles is
procedure TForm1.Panel29Click(Sender: TObject);
var
op,x : integer;
begin
if (Form1.Edit3.Text <> '') AND (Form1.Edit4.Text <> '') then
begin
op := 3;
if RadioButton7.Checked = True then op := 0;
if RadioButton3.Checked = True then op := 1;
if RadioButton4.Checked = True then op := 2;
if RadioButton5.Checked = True then op := 3;
if RadioButton6.Checked = True then op := 4;
CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
end;
end;
If I was to use TThread as suggested by a couple of people, and as displayed by Rob below, I'm confused at how a) I would pass op,Edit3/4.Text and StringGrid2 to the CompareFiles. Guessing from the example of TThread I've seen I thought I would replace the code above with TCompareFilesThread.Executeand the put the current code from Panel29Click into TCompareFilesThread.Create and then add
FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;
to this
FEdit3Text := Form1.Edit3.Text;
FEdit4Text := Form1.Edit4.Text;
FGrid := Form1.StringGrid2;
But I've got this nagging feeling that is totally off the mark.
That's not at all the way to use BeginThread. That function expects a pointer to a function that takes one parameter, but the function you're trying to call wants four. The one parameter you're giving to BeginThread for it to forward to the thread procedure is a string, but you evidently hope that some sort of magic will turn that string of characters into the values that those variables contain.
That's not how Delphi works, and even for the languages that can do something like that, it's generally discouraged to actually do it.
To pass multiple parameters to BeginThread, define a record with all the values you'll need, and also define a record pointer:
type
PCompareFilesParams = ^TCompareFilesParams;
TCompareFilesParams = record
Edit3Text,
Edit4Text: string;
Grid: TStringGrid;
Op: Integer;
end;
Change CompareFiles to accept a pointer to that record:
function CompareFiles(Params: PCompareFilesParams): Integer;
To start the thread, you'll need to allocate an instance of that record and populate its fields:
var
Params: PCompareFilesParams;
begin
New(Params);
Params.Edit3Text := Edit3.Text;
Params.Edit4Text := Edit4.Text;
Params.Grid := StringGrid2;
Params.Op := op;
BeginThread(nil, 0, #CompareFiles, Params, 0, x);
Implement CompareFiles like this so that the record will get freed before the thread terminates:
function CompareFiles(Params: PCompareFilesParams): Integer;
begin
try
// <Normal implementation goes here.>
finally
Dispose(Params);
end;
end;
You can make it all a lot easier if you just use TThread, though. You can make your descendant class have as many parameters as you want in its constructor, so you don't have to mess around with dynamically allocating and freeing a special record.
type
TCompareFilesThread = class(TThread)
private
FEdit3Text,
FEdit4Text: string;
FGrid: TStringGrid;
FOp: Integer;
procedure Execute; override;
public
constructor Create(const Edit3Text, Edit4Text: string; Grid: TStringGrid; Op: Integer);
property ReturnValue;
end;
constructor TCompareFilesThread.Create;
begin
inherited Create(False);
FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;
FOp := Op;
end;
procedure TCompareFilesThread.Execute;
begin
ReturnValue := CompareFiles(FEdit3Text, FEdit4Text, FGrid, FOp);
end;
Instead of calling BeginThread, you just instantiate the class and let it run:
var
ThreadRef: TThread;
ThreadRef := TCompareFilesThread.Create(Edit3.Text, Edit4.Text, StringGrid2, Op);
There's more to using threads, such as knowing when the thread has finished running, but I think you have enough to get started. One last thing to beware of, though, is that TStringGrid is a VCL control. You mustn't do anything with it from this new thread you create (regardless of how you end up creating it). Eveything you do with the grid control need to be done from the main thread. Use TThread.Synchronize and TThread.Queue to shift any VCL operations onto the main thread. Your file-comparing thread will wait for the synchronized operation to complete, but it will keep running without waiting for a queued operation to complete.