Function & Procedures in Delphi (Defaults & Optional Parameters) - delphi

EDIT:
Is there a better way of doing this ?
TPendingBets = class(TDataModule)
private
public
function GetBdy(out IdEvent : Integer ) : Boolean; overload;
function GetBdy(out IdEvent : Integer; out idBetType : TBetTypes) : Boolean; overload;
function GetBdy(out IdEvent : Integer; out idBetType : TBetTypes; Out TotalOrgStake,Price : Double; out PriceError :Boolean): Boolean; overload;
end;
implementation
////////////////////
function TPendingBets.GetBdy(out IdEvent : Integer ): Boolean;
var idBetType : TBetTypes;
TotalOrgStake,Price : Double;
PriceError :Boolean;
begin
result := GetBdy(IdEvent,idBetType,TotalOrgStake,Price,PriceError);
end;
////////////////////
function TPendingBets.GetBdy(out IdEvent : Integer; out idBetType : TBetTypes): Boolean;
var TotalOrgStake,Price : Double;
PriceError :Boolean;
begin
result := GetBdy(IdEvent,idBetType,TotalOrgStake,Price,PriceError);
end;
////////////////////
function TPendingBets.GetBdy(out IdEvent : Integer; out idBetType : TBetTypes; Out TotalOrgStake,Price : Double; out PriceError :Boolean): Boolean;
begin
result := false;
if cdBdy.Eof = False then
begin
IdEvent := cdBdy.FieldByName(IdEvent ).AsInteger);
idBetType := TBetTypes(cdBdy.FieldByName(idBetType ).AsInteger);
//.
//.
result := True;
end;
end;
NOTE:
As my initial question was not very clear I have removed part of it

Overloading does not mean having 5 copies of the code in the function. You might have 5 functions, but 4 of them just call the main function with the correct parameters.
I generally don't like out parameters. Have you considered returning a record containing all the results?
Fn1Result = record
A: Extended;
B: Integer;
C, D, E, F: Extended
S: String;
end;
Then declare your function like:
function Fn1: Fn1Result;
begin
Fn1.A := C_ConstA;
// etc. . . .
end;
Naturally I am assuming you are not using output parameters as input. Per the Delphi help:
An out parameter, like a variable parameter, is passed by reference. With an out parameter, however, the initial value of the referenced variable is discarded by the routine it is passed to. The out parameter is for output only; that is, it tells the function or procedure where to store output, but doesn't provide any input.
I get a funny feeling you are using output parameters as input parameters. Don't. Pass them as var if you want them to go both ways.

I think the overload solution is quite a good solution, at least it is one. Other programming languages require you to declare functions with different names.
Another approach may be a dynamic array parameter like:
type
TExtendedDynArray = array of Extended; // if not already declared elsewhere
function Fn1(out arr: TExtendedDynArray): Boolean;

In addition to what Uwe wrote (and I agree that overloading is a better solution here; up voted), you should never presume an out parameter to have any value at all at the start of the function. That's what out means.

You can use overloaded function something like this
function Fn1(Out A:string; B:integer=5) : boolean; overload;
function Fn1(Out A:string) : boolean; overload;
you must to define both functions.

As Jim showed, you don't need to copy the code around when overloading.
I don't like much to mix parameters with default values and overloading, because (doing it without care) you can create a mess.
Normally, if I have to support a lot of syntaxes:
1) I define a complete signature, with ALL PARAMETERS. Let's call it the
'master' one.
2) All overloads call that one. Or call one to another, but in the end every
overload chain must call the 'master' one to execute the task.
For me, overloads are "parameter crunchers" or "translators". Later, if I need
something to modify the master behavior, I create new parameters on 'master' (which can have default values - in fact, mostly have).
If I need a overload with the new parameter, I create a new one and it simply pass it to
the "master" procedure. The side effect is that the old ones continue to behave the same.

Default values can only be used for input parameters (that is: By value and const). For var and out parameters, default values are not possible.
As Jim already pointed out: You can avoid duplicating the code by having most overloaded functions call the one which takes all parameters, so they are merely thin layers over the original function. From Delphi 2005 on you can declare them inline, so the compiler might actually generate more efficient code.

Related

How to create a type that is string?

A blog entry from Raymond Chen today made me realize the elegant solution to a problem i'm having.
Various shell functions, rather than all taking ITEM­ID­LIST structure, can be made to only accept:
ITEM­ID_CHILD
ID­LIST_RELATIVE
ID­LIST_ABSOLUTE
ITEM­ID_CHILD_ARRAY
structures. The structures are all identical, but now you can enforce conceptual types at the compiler level.
Back to Delphi
i have a set of functions:
some only take a path: (e.g. C:\Users\Ian\Desktop\AllisonAngel.jpg)
some only take a filename: (e.g. AllisonAngel.jpg)
some only take a folder: (e.g. C:\Users\Ian\Desktop)
And right now they're all declared as string, e.g.:
function GetFilenames(Folder: string; ...): ...
function IsValidBatchFilename(Path: string): ...
function GetReportType(Filename: string): ...
and i have to trust that i'm passing the right stuff; and i'm trusting that developers (e.g. me), know the difference between:
a path
a filename
and a folder
i want to change the functions to use "typed" strings:
function GetFilenames(Folder: TFolderOnly; ...): ...
function IsValidBatchFilename(Path: TFullPath): ...
function GetReportType(Filename: TFilenameOnly): ...
Where:
type
TFullPath = type string;
TFolderOnly = type string;
TFilenameOnly = type string;
Except that there's no actual typing happening:
var
dropFolder: string;
begin
dropFolder := GetDropFolderPath(LCT);
GetFilenames(dropFolder); <-- no compile error
end;
What i want is a "distinct" type of string; that is string insofar that it is length prefixed, reference counted, null terminated.
You can use advanced records to accomplish this. For instance, you could do
type
TFileName = record
FFileName: string;
public
class function IsValidFileName(const S: string): boolean; static;
class operator Implicit(const S: string): TFileName;
class operator Implicit(const S: TFileName): string;
end;
implementation
class function TFileName.IsValidFileName(const S: string): boolean;
begin
result := true {TODO};
end;
class operator TFileName.Implicit(const S: string): TFileName;
begin
if IsValidFileName(S) then
result.FFileName := S
else
raise Exception.CreateFmt('Invalid file name: "%s"', [S]);
end;
class operator TFileName.Implicit(const S: TFileName): string;
begin
result := S.FFileName;
end;
and similarly for TPath and TFolder. Advantages:
A function expecting TPath will not accept a TFileName (or some other combination).
You can still assign a TPath to/from a regular string. If you cast from a string to a TPath, you will automatically check the string to see if it contains a valid path.
Optionally, you can specify how a TPath can be assigned to a TFileName (or some other combination), by writing more Implicit class operators.
Create different record types for each string type. Distinct record types are not assignment-compatible, even though string types are.
type
TFullPath = record value: string end;
TFolderOnly = record value: string end;
Chen's article compares the new shell feature to the classic STRICT macro that makes distinct handle types, and as I recall, declaring distinct structs is exactly how the STRICT macro works.
With the way Delphi handles basic types, I'm pretty this is a case of "you can't get there from here."
Your string type declarations are all going to satisfy Delphi's rules for type compatibility and assignment compatibility. What they will restrict is procedural declarations with var parameters. If you changed your function declarations to be var parameters instead of reference or value parameters, you would get a compile error in your final example.
All that said, it's all useless anyway. You still have no way to ensure that the input is only a filename, even with the TFilenameOnly type, and must test the contents in your procedures anyway.

Dealing with overloaded functions that have ambiguous parameters

Take this small example class (not my real code, but it exposes the problem):
Convert = class(TObject)
public
class function ToString(value: Double): String; overload;
class function ToString(value: TDateTime): String; overload;
end;
It compiles fine until you try to use the Double or TDateTime functions As
In:
var
d: Double;
begin
d := 99.99;
ShowMessage(Convert.ToString(d));
You will get this compile error: Ambiguous overloaded call to 'ToString'.
The problem boils down to the fact that TDateTime is a type of Double
My Question: how do You deal with this type of problem?
EDIT - I am NOT looking for a solution for the example given
I have found 3 Solutions so far:
Rename one of the 2 functions
Add a "Dummy" parameter to one of the 2 functions
Change the parameters to Var types, this has the disadvantage that I can no longer call this function with constants
are there any other solutions out there?
Overloaded methods can be very effective. However, as soon as there is a hint of ambiguity they become a liability. A good example of this are the new TStream overloads introduced in XE3. It's not hard to fall into a trap where the compiler chooses an overload that you weren't expecting. At least in your code the compiler stopped. In that sense you were lucky.
So my advice, in your situation, is to abandon overloads. Express the different input types in the method name. Yes it's a little more verbose, but you won't make any mistakes, and you code will compile!
Your posted example compiles and executes fine in XE.
In a comment you give this example instead:
ShowMessage( Convert.ToString( 99.99 )); // <- gives compiler error 2251
In this particular case the solution is to explicitly define the type( I thought):
ShowMessage( Convert.ToString( Double(99.99) )); // <- E2089, Invalid Typecast
Looking into the documentation:
This error message is issued for type casts not allowed by the rules. The following kinds of casts are allowed:
Ordinal or pointer type to another ordinal or pointer type
A character, string, array of character or pchar to a string
An ordinal, real, string or variant to a variant
A variant to an ordinal, real, string or variant
A variable reference to any type of the same size.
So, to explicitly tell the compiler to select the Double overloaded function:
ShowMessage( Convert.ToString( Double(Variant(99.99)))); // Ok
A bit convoluted perhaps. But for the other overloaded function it is simpler:
ShowMessage( Convert.ToString( EncodeDate(2013,1,5));
Update
To make this a generic solution working for all classes, consider adding class functions to resolve your ambiguous types.
Convert = Class(TObject)
...
class function AsDouble( value: Double) : Double; inline; static;
class function AsTDateTime( value: TDateTime) : TDateTime; inline; static;
end;
class function Convert.AsDouble(value: Double): Double;
begin
Result := Value;
end;
class function Convert.AsDateTime(value: TDateTime): TDateTime;
begin
Result := Value;
end;
Now you can call your overloaded class function with constants:
ShowMessage( Convert.ToString( Convert.AsDouble(99.99)));
How about collapsing it all?:
class function Convert.ToString(value: Variant): String;
begin
Result := VarToStr(Value);
end;

Should I use a class or a method to wrap a dynamic remote procedure call?

Situation
I'd like to make an RPC interface easier to use. This is a custom interface so there is no readily available wrapper.
I have to write several wrappers around functions which often have many arguments.
Possible solutions
Solution 1 - Using a class for each function:
TDoSomethingFunction = class
public
property Arg1: Integer;
property Arg2: string;
property Arg3: Boolean;
procedure Run;
end;
The caller has to create an object to call the function:
var
DoSomething: TDoSomethingFunction;
begin
DoSomething := TDoSomethingFunction.Create;
try
DoSomething.Arg1 := 0;
...
DoSomething.Run;
finally
free;
end;
Method 2 - Using a wrapper method for each function:
procedure TRPCInterface.DoSomething(AArg1: Integer; AArg2: string; AArg3: Boolean);
The caller can simply call it:
TRPCInterface.DoSomething(0, ...);
Pro and contra
Method 1 - Class for each function
Contra
More code required.
An object must be created which takes up memory.
Pro
Reading the code is easier, you don't have to look at the declaration to see what the arguments are.
Method 2 - Wrapper method
Contra
You can't tell which arguments are used by just looking at the code.
Pro
Much less code to write.
The wrapper is thinner (no object has to be created).
Which method should I use?
There is an intermediate solution that is calling the wrapper methods passing an object argument.
TDoSomethingArgs = class
public
property Arg1: Integer;
property Arg2: string;
property Arg3: Boolean;
end;
procedure TRPCInterface.DoSomething(Args: TDoSomethingArgs);
one advantage of this method is that you still use methods, but still it's more readable.
One advantage of using classes (you can also use records) in arguments is that you can later change the arguments (add more, change behavior) and if you choose it well, it does not break backward compatibility - in summary you can change method signature without breaking code.
You haven't specified Delphi version, but if yours supports generics, I would go with:
type
TArgList = class( TDictionary< String, Variant > );
type
TBaseFunc = class
private
FArgs: TArgList;
public
function Run: Boolean; virtual; abstract;
public
property Args: TVarList read FArgs write FArgs;
end;
type
TSpecialFunc = class( TBaseFunc )
public
function Run: Boolean; override;
end;
implementation
function TSpecialFunc.Run: Boolean;
begin
// here's where you can access args as variants
end;
you can use:
ASpecialFunc.Args.AddOrSetValue('ArgumentName', 2012);
in this way you will have to write more code, but it's much more readable IMHO and easy to be picked up by other developers in the future.
NOTE: that I haven't tested this code, so chances are that it won't compile.
That's my two cents, I'm very curios as to what others come up with (:

How to overload a function based on the result type?

just a question, i have:
myclass = class
public
function Funct1: String;
function Funct2: Integer;
end;
It turn me error, so i have tried with:
myclass = class
public
function Funct1: String; overload;
function Funct2: Integer; overload;
end;
but same problem; delphi tell me that has same parameter.
Now, i ask, is possible to do in mode to have more function with same name but with different output as in example?
Thanks very much for help.
UPDATE
Sorry, i done a error, not funct1 and funct2, but both funct1, so:
myclass = class
public
function Funct1: String; overload;
function Funct1: Integer; overload;
end;
Doing so, compiler return me this error:
[DCC Error] Project1.dpr(15): E2252 Method 'funct1' with identical parameters already exists
[DCC Error] Project1.dpr(22): E2037 Declaration of 'funct1' differs from previous declaration
Of course, i know becouse give error and need change name to one of both function (for it me confused before) but i wanted know if there was some trick or other solution for to have a situation as this without error.
Thanks again.
You could turn the function into a procedure taking a var parameter:
myclass = class
public
procedure Funct1(var AResult: String); overload;
procedure Funct1(var AResult: Integer); overload;
end;
Firstly, for functions to be overloaded, they have to be named the same.
Secondly, this is not possible to do. Overloaded functions must have different parameters.
In your case, there is no way the compiler can tell which of your functions to call (assumed that both are renamed to Funct1):
var
v: Variant;
mc: myclass;
begin
v := mc.Funct1;
end;
What you post doesn't make sense. The first example should compile without any problem, as the functions have different names, Funct1 and Funct2.
Problems only arise when methods (or functions) have the same name. Then, normally, an overload directive would be in order, but overload can't distinguish functions on return value alone.
So assuming the names are the same, what you want is impossible. There is no way to overload these functions, if they don't have a different parameter signature. You can just give them different names, which is preferrable anyway, as they apparently do different things.
FWIW, your question is flawed by the fact that you apparently did not post the exact code with which you are actually having problems. Please always post the exact code that causes your problems, and if there are error messages, always post the exact error message (they can usually be copied using the usual copy keystrokes, e.g. Ctrl+C, even in most parts of the IDE or in message dialogs in Delphi). If there are any line numbers in the error message, indicate this in the source code you post, as we don't always have the same line numbers as you have.
if you want to overload methods with different return types, just add a dummy parameter with default value to allow the overload execution, but don't forget the parameter type should be different so the overload logic works e.g:
type
myclass = class
public
function Funct1(dummy: string = EmptyStr): String; overload;
function Funct1(dummy: Integer = -1): Integer; overload;
end;
use it like this
procedure tester;
var yourobject : myclass;
iValue: integer;
sValue: string;
begin
yourobject:= myclass.create;
iValue:= yourobject.Funct1(); //this will call the func with integer result
sValue:= yourobject.Funct1(); //this will call the func with string result
end;
also don't forget that this way your gonna have a problem with variants like in this example:
procedure tester;
var yourobject : myclass;
vValue: variant;
begin
yourobject:= myclass.create;
vValue:= yourobject.Funct1();
end;
until you implement the variant function too:
function Funct1(dummy: Variant = Unassigned): Variant; overload;
As was already stated, you cannot do it as you are wanting to do. However, you could just as easily implement each function with a different name such as "AsInteger" or "AsString". Your code will be clearer and this is generally the way it is done within the VCL.
TmyClass = class (TObject)
public
function AsString : string;
function AsInteger : Integer;
end;

Writing Delphi/FreePascal DLL to be called from gcc application

I need to make parts of my Win32 Delphi app available to another company's Linux gcc program.
Throughput and deployment requirements make any sort of remote service unsuitable so I'm looking at using FreePascal to build a .SO (Linux equivalent of a DLL) that the gcc app can call.
It's a long time since I used C/C++ and never on Linux so I'm a little unsure as to how best to structure the DLL/SO interface for compatibility with a gcc caller.
Here's a representation of my data structures
TFoo = record
x, y : double;
a : smallint;
b : string;
end;
TBar = record
a : double;
b : longint;
c : string;
end;
TFooBar = record
foo : array of TFoo;
bar : array of TBar;
end;
procedure Process(const inFooBar : TFooBar);
To make this Process method externally available via a FreePascal .SO how do I need to modify these declarations? I'm thinking something along the lines of
TFoo = record
x, y : double;
a : smallint;
b : PChar;
end;
TBar = record
a : double;
b : longint;
c : PChar;
end;
TFooBar = record
foo : ^TFoo;
foo_count : longint;
bar : ^TBar;
bar_count : longint;
end;
procedure Process(const inFooBar : TFooBar);
Am I on the right track? I don't have to get this exactly right, the other company's programmers will very likely fix up my mistakes. I just don't want them to laugh too hard when they see what I've sent them.
Use PAnsiChar instead of PChar, and integer instead of longint/smallint (since you use aligned records, there is no benefit of using a smallint field).
Define some pointer types like PFoo and PBar, which will be better written than ^TFoo e.g.
If you need to access some arrays, you could define TFooArray and TBarArray, as in the code below.
Don't forget the cdecl or stdcall calling convention for any function/procedure.
type
TFoo = record
x, y : double;
a : integer;
b : PAnsiChar;
end;
TBar = record
a : double;
b : integer;
c : PAnsiChar;
end;
TFooArray = array[0..maxInt div sizeof(TFoo)-1] of TFoo;
TBarArray = array[0..maxInt div sizeof(TBar)-1] of TBar;
PBar = ^TBar;
PFoo = ^TFoo;
PFooArray = ^TFooArray;
PBarArray = ^TBarArray;
TFooBar = record
foo : PFooArray;
foo_count : integer;
bar : PBarArray;
bar_count : integer;
end;
procedure Process(const inFooBar : TFooBar); cdecl; external 'ProcessGCCLibrary.so';
Will be your process read-only?
If the C part need to add some items, you'll have to provide some memory reallocation methods to the external library, mapping at least reallocmem().
Note that Delphi dynamic arrays can be mapped easily into C compatible structure, as such:
type
TFooDynArray: array of TFoo;
TBarDynArray: array of TBar;
procedure CallProcess(const aFoo: TFooDynArray; const aBar: TBarDynArray);
var tmp: TFooBar;
begin
tmp.foo := pointer(aFoo);
tmp.foo_count := length(aFoo);
tmp.bar := pointer(aBar);
tmp.bar_count := length(aBar);
Process(tmp);
end;
This could make your Delphi code much more readable. Take a look at our TDynArray wrapper if you want high-level access to such a dynamic array of records, with TList-like methods.
Since the AnsiString type maps a PAnsiChar, from the binary point of view, you can even define your records as such:
type
TFoo = record
x, y : double;
a : integer;
b : AnsiString;
end;
TBar = record
a : double;
b : integer;
c : AnsiString;
end;
When mapped to the gcc application, it will be read as a regular *char.
Using AnsiString here you won't need to handle memory allocation from your Delphi code. With an associated dynamic array, it could make your Delphi code much easier to maintain. Note that our TDynArray wrapper will handle nested AnsiString in records as expected, even for the highest level methods (e.g. binary serialization or hashing).
To ensure that the record packing/alignment/padding is as GCC expects, add {$packrecords c} to your Pascal source. Note that this directive is specific to the Free Pascal Compiler, Delphi does not support it.
Looks pretty good. Thoughts:
Your arrays (declared as a pointer and count) are going to be fully manually managed - that's fine for you?
You should change:
procedure Process(const inFooBar : TFooBar);
to include a C-compatible calling convention. I'm not sure what FreePascal and GCC both support, but something like cdecl should work fine.
You should also (for safety) specify the structure alignment / packing.
PChar should be explicitly narrow or wide (wide is normal these days.)

Resources