How to access a variable by its name(string)? - delphi

I have some global string variables.
I have to create the function that I could pass & store them in some structure.
Later I need to enumerate them and check their values.
how can this be easily achieved?
(I think I would need some kind of reflection, or store array of pointers).
Anyway, any help will be appreciated.
Thanks!

First of all you can't use Delphi's RTTI for that purpose, because Delphi 7's RTTI only covers published members of classes. Even if you were on Delphi XE, there's still no RTTI for global variables (because RTTI is tied to Types, not to "units").
The only workable solution is to create your own variable registry and register your globals using a name and a pointer to the var itself.
Example:
unit Test;
interface
var SomeGlobal: Integer;
SomeOtherGlobal: string;
implementation
begin
RegisterGlobal('SomeGlobal', SomeGlobal);
RegisterGlobal('SomeOtherGlobal', SomeOtherGlobal);
end.
were the RegisterXXX types would need to be defined somewhere, probably in there own unit, like this:
unit GlobalsRegistrar;
interface
procedure RegisterGlobal(const VarName: string; var V: Integer); overload;
procedure RegisterGlobal(const VarName: string; var V: String); overload;
// other RegisterXXX routines
procedure SetGlobal(const VarName: string; const Value: Integer); overload;
procedure SetGlobal(const VarName:string; const Value:string); overload;
// other SetGlobal variants
function GetGlobalInteger(const VarName: string): Integer;
function GetGlobalString(const VarName:string): string;
// other GetGlobal variants
implementation
// ....
end.

You could also have a global TStringList variable holding a list of name-value pairs.

On Delphi 7, I would follow Cosmin's idea for the interface, and for the implementation, I would use a dictionary type based on Julian Bucknall's excellent data structures code for Delphi, ezDSL.
Later versions of delphi like XE not only have more advanced RTTI they also include a pretty great dictionary type, using generics, so the dictionary can contain any type you like. The esDSL dictionary is pretty easy to use but since it's pointer based, it isnt as type safe as the delphi generics dictionary.
Since what you need to do is look up string "variable names" in very fast time (O(1) we like to call it), what you need is a string-to-variable dictionary. You could have Strings for the keys, and Variants as the values in the dictionary, and just get rid of the original global variables, or you could attempt some rather complex pointers-to-globals logic, but I really think you'd be better off with a simple dictionary of <string,variant> key,value tuples.

Related

Is the [Ref] attribute for const record parameters useful?

With the latest Delphi version (Berlin/10.1/24), is the [Ref] attribute really necessary?
I ask this because the online doc says:
Constant parameters may be passed to the function by value or by
reference, depending on the specific compiler used. To force the
compiler to pass a constant parameter by reference, you can use the
[Ref] decorator with the const keyword.
It's pretty much as described by the documentation. You'd use [ref] if you had a reason to enforce the argument to be passed by reference. One example that I can think of is for interop. Imagine you are calling an API function that is defined like this:
typedef struct {
int foo;
} INFO;
int DoStuff(const INFO *lpInfo);
In Pascal you might wish to import it like this:
type
TInfo = record
foo: Integer;
end;
function DoStuff(const Info: TInfo): Integer; cdecl; external libname;
But because TInfo is small, the compiler might choose to pass the structure by value. So you can annotate with [ref] to force the compiler to pass the parameter as a reference.
function DoStuff(const [ref] Info: TInfo): Integer; cdecl; external libname;
Another example is the new FreeAndNil procedure declaration in Delphi 10.4 in SysUtils.pas, which now finally ensures that only TObject descendants can be used with FreeAndNil. In previous Delphi versions, you could pass anything to this function, even if it didn't make sense.

What is exact signature of System.Assign (for use in procedural expression)?

Unfortunately, exact Assign signature is not available in the RTL source, and my attempts to guess, like:
const
Assign: procedure (var F; const FileName: string) = System.Assign;
{ or }
Assign: function (var F; const FileName: string): Integer = System.Assign;
{ also I tried "internal" one from _AssignFile }
didn't yield any positive results, and compiler refuses to treat this contant expression as procedural and complains about right-value (E2029 '(' expected but ';' found)).
So, which type should I use to match Delphi RTL exactly?
Assign is a language remnant of Delphi tracing its origins to the original Turbo Pascal syntax. Long before function overloading was added to the language syntax, long before the RTL's text file and "file of type" internal data structures were documented, there was Assign.
Assign is a simple enough procedure to use, but when you look at what it has to do you'll see that it's pretty much impossible to implement without some sort of compiler magic. File of TFoo creates a file type that is distinct and incompatible with File of Integer. And yet, both can be passed as the first parameter to Assign.
Today, you could probably implement Assign using a generic type param for the file parameter. The generic type would conform to the type of the variable passed in. That's great and all, but we needed a way to associate a typed file variable with a string filename 25 years before generics were added to the mix.
Assign is a compiler intrinsic function. That means it lives outside of the Delphi/Pascal syntax space. Assign is essentially a type conforming procedure, which can't be represented in the strongly typed Delphi/Pascal language (without modern and complex language extensions such as generic types). It's not typeless, and it's not dynamically typed because the actual parameter type is fully determined at compile time.
You can't take the address of the Assign function. Even if you could, because it is amorphous, you won't be able to define a procedure type that accurately represents all of its possible call signatures.
Compiler magic functions exist outside the language to take care of tasks that could not be easily represented in the syntax available at the time. Assign is one example. Writeln() is another.
System.Assign is an intrinsic function. The official documentation of it is completely hopeless. It says:
function Assign(var F: File; FileName: String; [CodePage: Word]): Integer; overload;
function Assign(var F: File; FileName: String; [CodePage: Word]): Integer; overload;
function Assign(var F: File; FileName: String; [CodePage: Word]): Integer; overload;
I can only guess as to why the documentation generator cannot cope with this function. But the three identical overloads are clearly bogus. And it's not a function, rather it is a procedure.
No matter. Because it is an intrinsic, you cannot assign it to a function pointer. The solution is to wrap it up in a function of your own.
procedure MyAssign(var F: File; const FileName: string);
begin
Result := System.Assign(F, FileName);
end;
You can then assign that function to a variable or constant of procedural type.
const
AssignProc: procedure(var F: File; const FileName: string) = MyAssign;
The other variant of Assign takes a third parameter that specifies the code page, passed as a Word. You can only call that function if the first argument to Assign is a TextFile.
So, intrinsics are really a law unto themselves.
Note that the documentation does state that Assign should no longer be used. Instead you should use AssignFile. The documentation is no better there mind you!
If to open System.pas file and read it, in XE2 you have those internal procedures:
function _AssignFile(var t: TFileRec; const s: PChar): Integer;
function _AssignText(var t: TTextRec; const s: PChar; const CP: word): Integer;
And i don't think you have any global stable procedure w/o underscore in names - those listed are Delphi compiler internals, like invisible procedures, that are covertly called when you declare string or dynarray variable.
http://en.wikipedia.org/wiki/Intrinsic_function
Like a lot of procedures like _Write0LString which are implementing usual WriteLn calls.
So you would have to make a wrapper function that would call AssignFile and put reference to your wrapper into your variable instead;
However you did not put tag with your Delphi version, and different vesions may have different declarations for intrinsics. So look into sources of your particular Delphi.
Also, why would you need it ? I remember i did such a thing in TurboPascal 5.5 to make string-based file - make WriteLn act like C sprintf. But why would you need it in modern Delphi, when you can use TStream instead?

store array of TPoint inside TObjectList

I defined a Objectlist to store several Polygons as TFPolygon = array of TPoint inside this TObjectList; but with the add function of my objectlist I get an Access violation error
:
type
TFPolygon = array of TPoint;
TFPolygonList = class(TObjectList)
private
procedure SetPolygon(Index: Integer; Value: TFPolygon);
function GetPolygon(Index: Integer): TFPolygon;
public
procedure Add(p: TFPolygon);
property Items[index: Integer]: TFPolygon read GetPolygon write SetPolygon; default;
end;
implementation
procedure TFPolygonList.SetPolygon(Index: Integer; Value: TFPolygon);
begin
inherited Items[Index] := Pointer(Value);
end;
function TFPolygonList.GetPolygon(Index: Integer): TFPolygon;
begin
Result := TFPolygon(inherited Items[Index]);
end;
procedure TFPolygonList.Add(p: TFPolygon);
begin
inherited Add(Pointer(p));
end;
I can not understand the error inside this code sample ? Can I only store classes inside a TObjectList or is my approach to store arrays of TPoints also valid ?
Your approach is not valid. Dynamic arrays are managed types. Their lifetimes are managed by the compiler. For that to work you must not cast away the fact that they are managed types, which is exactly what you did.
You cast the dynamic array to Pointer. At that point you have taken a new reference to the dynamic array, but the compiler is not aware of it because a Pointer is not a managed type.
You've got a few options to solve your problem.
If you are on a modern Delphi then stop using TObjectList. Instead use the generic type safe containers in Generics.Collections. In your case TList<TFPolygon> is what you need. Because this is compile time type safe, all the lifetime of the managed types is taken care of.
If you are on an older Delphi, then you can wrap your dynamic array inside a class. Then add instances of those classes to your TObjectList. Make sure that your list is configured to own its objects. It's perfectly possible for you to do that wrapping purely in the implementation of TFPolygonList which will encapsulate things well.

Is it safe to pass Delphi const string params across memory manager boundaries?

Subj. I'd like to use strings instead of PChar because that spares me much casting, but if I just do
procedure SomeExternalProc(s: string); external SOMEDLL_DLL;
and then implement it in some other project with non-shared memory manager:
library SeparateDll;
procedure SomeExternalProc(s: string);
begin
//a bla bla bla
//code here code here
end;
I have (formally) no guarantee Delphi does not decide for whatever reason to alter the string, modify its reference counter, duplicate or unique it, or whatever else. For example
var InternalString: string;
procedure SomeExternalProc(s: string);
begin
InternalString := s;
end;
Delphi increments refcounter and copies a pointer, that's it. I'd like Delphi to copy the data. Does declaring the parameter as "const" make it safe for that reason? If not, is there a way to do it? Declaring parameter as PChar doesn't seem to be a solution because you need to cast it every time:
procedure SomeExternalProc(s: Pchar); forward;
procedure LocalProc;
var local_s: string;
begin
SomeExternalProc(local_s); //<<--- incompatible types: 'string' and 'PAnsiChar'
end;
That would probably work, as long as you only ever use your DLL from code compiled in the same version of Delphi. The internal format of string has been known to change between releases, and you have no formal guarantee that it won't change again.
If you want to avoid having to cast everywhere you use it, try wrapping the function, like this:
procedure SomeExternalProc(s: Pchar); external dllname;
procedure MyExternalProc(s: string); inline;
begin
SomeExternalProc(PChar(local_s));
end;
Then in your code, you call MyExternalProc instead of SomeExternalProc, and everyone's happy.
If both the app and the DLL are written in the same Delphi release, just use shared memory manager (more details here).
If one side is written in a different language than there's no other way but to use PChar or WideString (WideStrings are managed by the COM memory manager).
Or you can write a wrapper function:
procedure MyExternalProc(const s: string);
begin
SomeExternalProc(PChar(s));
end;
Just to add a single fact:
Delphi allows you to simply assign PChar to a string so on the DLL side you don't need any typecast:
function MyDllFunction(_s: PChar): integer;
var
s: string;
begin
s := _s; // implicit conversion to string
// now work with s instead of the _s parameter
end;
This also applies for passing PChar as a parameter to a function that expects a (by value) string.
I recommend to use an alternative memory manager such as RecyclerMM or FastMM. They doesn't require any external shared MM dll's and allows you to pass strings to the dlls safely. As a bonus, you may get a nice performance improvement in whole application.
FastMM is used as a default memory manager in Delphi 2006 and above. Also it's a good tool to search the memory-leaks.

Untyped / typeless parameters in Delphi

What type are parameters without type like in the class TStringStream:
function Read(var Buffer; Count: Longint): Longint; override;
What is the type of Buffer parameter (is it a type of Pointer ?).
I wrote an article about this very topic a few years ago:
What is an untyped parameter?
Untyped parameters are used in a few situations; the TStream.Read method you ask about most closely matches with the Move procedure I wrote about. Here's an excerpt:
procedure Move(const Source; var Dest; Count: Integer);
The Move procedure copies data from an arbitrary variable
into any other variable. It needs to accept sources and destinations of
all types, which means it cannot require any single type. The procedure
does not modify the value of the variable passed for Source, so that
parameter’s declaration uses const instead of var, which is the
more common modifier for untyped parameters.
In the case of TStream.Read, the source is the stream's contents, so you don't pass that in as a parameter, but the destination is the Buffer parameter shown in the question. You can pass any variable type you want for that parameter, but that means you need to be careful. It's your job, not the compiler's, to ensure that the contents of the stream really are a valid value for the type of parameter you provide.
Read the rest of my article for more situations where Delphi uses untyped parameters.
Check out the Delphi help for "Untyped parameters"
You can pass in any type, but you have to cast it in the implementation. Help says that you cannot pass it a numeral or untyped numeric constant. So basically you have to be know what type to expect, and compiler can not help you, so you need a good reason to do it this way. I suppose it could be of use if you need the method to handle incompatible types, but then again you could write several overloaded versions for each expected type, I would suggest that as a better solution.
Perhaps surprisingly, it is legal to pass a dereferenced pointer as an untyped parameter. And the pointer itself doesn't even have to have a type.
procedure SomeMethod(var aParameter);
∶
procedure CallSomeMethod(aIsInteger : Boolean);
type
buffer : Pointer;
intValue : Integer;
realValue : Single;
begin
if aIsInteger then
begin
buffer := #intValue;
end
else
begin
buffer := #realValue;
end;
SomeMethod(buffer^);
Of course it would probably have been easier if the parameter to SomeMethod() had been a pointer, but this might not be under your control.
var in a parameter list is the Delphi syntax for call by reference. It can be typed as e.g. the AllowChange parameter in the OnChanging handler of an Listview:
procedure TSomeForm.LVOnChanging(Sender: TObject; ...; var AllowChange: Boolean);
begin
if SomeProblemOccurred then
AllowChange := False;
end;
or untyped as in your example.

Resources