delphi reference event object by variable TObject - delphi

i have component with function getsomedata (key:string;listener:Tlistener)
which listener declared like event as follow :
Tlistener = procedure (name,age,sex:string) of object ;
but in my component listener manager when i add the new listener takes listener parameter as TObject class like .
ListenerManager.addListener(key:string;Listener:TObject);
when i complile the code i got error message
Not enough actual parameters
because Event Object (TListener) and ListenerManager Parameter (TObject).
sample of full function code .
procedure getsomedata (key:string;listener:Tlistener) ;
begin
ListenerManager.addListener(key,listener); //error Here >>> addListener input parameters (key:string;Listener:TObject);
end;
how can i resolve it ?

In this code:
procedure getsomedata (key:string;listener:Tlistener) ;
begin
ListenerManager.addListener(key,listener);
end;
You are attempting to pass a variable of type TListener to the second parameter of addListener. That parameter is typed as being TObject.
Now, TListener is typed as being
procedure(name,age,sex:string) of object;
A variable of procedural type cannot be passed to a parameter of type TObject.
It's rather difficult to know exactly what the correct code would look like because the question doesn't contain enough background information. Perhaps addListener should receive a TListener rather than a TObject. But that's just a guess. If you want more complete advice, then you will need to add sufficient detail to the question.
You state in a comment that:
I can not change the parameter type TObject to TListener.
In that case you are stuck. It's simply not possible to cast a TListener to a TObject. Now, you could implement a class that had a single field of type TListener, and pass that. But I doubt very much that's really the right solution.

Related

Delphi using pointers to methods properly

If I have a Pointer focused to a FormActivate(Sender: TObject); -
MyPtr := #FormActivate; - how do I run FormActivate with a sender of my choice.
I do not understand how to add in the sender.
You should not use a Pointer in this case. I don't think that you can even do it as you need the form instance as well as the method. The FormActivate is a plugged into the OnActivate event handler, defined as TNotifyEvent:
TNotifyEvent = procedure(Sender: TObject) of object;
NB: The "of object" which the compiler interprets to require an instance (the mechnanism hidden from us as it should be).
The correct was to do what you want (not sure that you should be doing it, but hey..) is to replace your Pointer with a MyForm :TMyForm (what ever yours is) and then call:
MyForm.FormActivate(mySender);
Alternatively, you can replace Pointer with a MyEvent :TNotifyEvent, thus:
MyEvent := MyForm.OnActivate;
then call
MyEvent(mySender);
Replacing mySender with whatever you wish in both examples.
I hope that this clears things up for you.

Access Violation when handling forms

I have procedure to show/hide one element on TForm like that:
procedure ShowHideControl(const ParentForm: TForm; const ControlName: String; ShowControl: Boolean);
var
i: Integer;
begin
for i := 0 to pred(ParentForm.ComponentCount) do
begin
if (ParentForm.Components[i].Name = ControlName) then
begin
if ShowControl then
TControl(ParentForm.Components[i]).Show
else
TControl(ParentForm.Components[i]).Hide;
Break;
end;
end;
end;
then I try to use it like:
procedure TForm1.Button6Click(Sender: TObject);
begin
ShowHideEveryControl(TForm(TForm1), 'Button4', True);
end;
Why do I get Access Violation on Button6 click?
For me everything is OK... Button4 exists as a child :)
This cast is wrong:
TForm(TForm1)
You are telling the compiler to ignore the fact that TForm1 is not a TForm instance, and asking it to pretend that it is. That is fine until you actually try to use it as an instance, and then the error occurs.
You need to pass a real instance to a TForm descendent. You can write it like this:
ShowHideEveryThing(Self, 'Button4', True);
Just in case you are not clear on this, the parameter of your procedure is of type TForm. That means you need to supply an instance of a class that either is, or derives from TForm. I repeat, you must supply an instance. But you supply TForm1 which is a class.
And then the next problem comes here:
if (ParentForm.Components[i].Name = FormName) then
begin
if ShowForm then
TForm(ParentForm.Components[i]).Show
else
TForm(ParentForm.Components[i]).Hide;
Break;
end;
Again you have used an erroneous cast. When the compiler tells you that it a particular object does not have a method, you must listen to it. It's no good telling the compiler to shut up and pretend that an object of one type is really an object of a different type. Your button is categorically not a form, so don't try to cast it to TForm.
It is very hard to know what you are actually trying to do here. When you write:
ShowHideEveryThing(Self, 'Button4', True);
It would seem to me to me more sensible to write:
Button4.Show;
It is not a good idea to refer to controls using their names represented as text. It is much safer and cleaner to refer to them using reference variables. That way you let the compiler do its job and check the type safety of your program.
The names used in your function are suspect:
procedure ShowHideEveryThing(const ParentForm: TForm; const FormName: String;
ShowForm: Boolean);
Let's look at them:
ShowHideEveryThing: but you claim that the function should show/hide one element. That does not tally with the use of everything.
FormName: you actually pass a component name, and then look for components owned by ParentForm that have that name. It seems that FormName is wrong.
ShowForm: again, do you want to control visibility of the form, or a single element?
Clearly you need to step back and be clear on the intent of this function.
As an aside, you should generally never need to write:
if b then
Control.Show
else
Control.Hide;
Instead you can write:
Control.Visible := b;
My number one piece of advice to you though is to stop casting until you understand it properly. Once you understand it properly, design your code if at all possible so that you don't need to cast. If you ever really do need to cast, make sure that your cast is valid, ideally by using a checked cast with the as operator. Or at least testing first with the is operator.
Your code shows all the hallmarks of a classic mistake. The compiler objects to the code that you write. You have learnt from somewhere that casting can be used to suppress these compiler errors and now you apply this technique widely as a means to make your program compile. The problem is that the compiler invariably knows what it is talking about. When it objects, listen to it. If ever you find yourself suppressing a compiler error with a cast, take a step back and think carefully about what you are doing. The compiler is your friend.

How to save an unknown Interface to a Pointer?

Can I save an unknown interface to a pointer ?
For example I have an unknown interface and want to save it to a TreeNode Data?
var
X : Inknown;
To save :
....
Node:=TreeView1.Items.Add;
//Node.data:=x; //compiler won't allow this
Node.data:=#x;
...
To get :
...
var
//X:=Node.data; //compiler won't allow this too
Pointer(X):=Node.data; //an exception caught
...
An interface is a pointer, so you can store it as-is (don't use the # operator). However, to ensure the interface's lifetime, you have to increment/decrement its reference count manually for as long as the node refers to it, eg:
Node := TreeView1.Items.Add;
Node.Data := Pointer(x);
x._AddRef;
x := IUnknown(Node.Data);
procedure TMyForm.TreeView1Deletion(Sender: TObject; Node: TTreeNode);
begin
IUnknown(Node.Data)._Release;
end;
You need to capture the value of the interface reference rather than the address of the variable that holds the reference.
So, do it like this:
Node.Data := Pointer(X);
....
X := IInterface(Node.Data);
// use IInterface in preference to IUnknown
But you must make sure that you handle the reference counting properly since this cast subverts automatic reference counting.
So, take a reference when you put the interface into the Node:
Node.Data := Pointer(X);
X._AddRef;
and call _Release whenever you modify Node.Data, or destroy the node.
This is rather messy, and easy to get wrong, as you found out. A better way to solve the problem is to let the compiler do the work. Define a sub-class of TTreeNode, and make your tree view use that sub-class. Ignore the Data property of the node and use your own property of type IInterface, introduced in the sub-class.
The way to make this happen is to supply an OnCreateNodeClass event handler that tells the tree view control to create nodes of your sub-class rather than plain old TTreeNode. And example of that technique can be found here: https://stackoverflow.com/a/25611921/

How to get the class from a Delphi 6 Object for assigning to a metaclass variable?

I have a Delphi 6 Metaclass variable that services a component property for a design time Server component:
type
TClientClass = class of TClient;
...
FClientClass: class of TClientClass;
FClientObj: TClient;
I have another design-time component that is a Client component. At design time I assign a concrete instance of the Client component, dropped on to the same Form, to the Server component using the Server's "client" property via the IDE's Property Editor. However, I also want to assign the FClientClass variable the underlying class of the concrete component I am assigning to FClientObj via the "client" property. This is so the Server component can create new instances of the Client component at run time, using FClientClass.Create.
I can't figure out how to assign the underlying class of the "client" object to the FClientClass data member when I set the FClientObj data member via the Server component's property setter:
procedure setClientClass(theClient: TClient);
begin
// Assign the "client" property. This works.
FClientObj := theClient;
// Assign the class of the "client" object to the Metaclass variable.
// THIS DOESN'T WORK: incompatible types error from the compiler.
FClientClass := theClient;
end;
I'm pretty sure the problem is that I am trying to assign an object of TClient to a Metaclass of TClient variable. I just don't know the correct syntax to make the assignment. I don't want to just do:
FClientClass := TClient;
because I want to allow for the assignation of concrete components in the future that may be descendants of TClient.
What is the syntax for doing the assignment correctly? I'm hoping it is simpler than doing something complex with the RTTI library like as Malte's reply in this thread indicates:
How can I create an Delphi object from a class reference and ensure constructor execution?
I believe you are looking for TObject.ClassType. Use it like this:
procedure setClientClass(theClient: TClient);
begin
FClientObj := theClient;
FClientClass := TClientClass(theClient.ClassType);
end;
Note 1: I'm assuming that you mean FClientClass: TClientClass rather than FClientClass: class of TClientClass.
Note 2: I've only compiled this in my head; I hope it works on a real compiler.

How to know what type is a var?

TypeInfo(Type) returns the info about the specified type, is there any way to know the typeinfo of a var?
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= TypeInfo(S);
Info:= TypeInfo(Instance);
Info:= TypeInfo(Obj);
end
This code returns:
[DCC Error] Unit1.pas(354): E2133 TYPEINFO standard function expects a type identifier
I know a non instantiated var is only a pointer address.
At compile time, the compiler parses and do the type safety check.
At run time, is there any way to know a little more about a var, only passing its address?
No.
First, there's no such thing as a "non-instantiated variable." You instantiate it by the mere act of typing its name and type into your source file.
Second, you already know all there is to know about a variable by looking at it in your source code. The variable ceases to exist once your program is compiled. After that, it's all just bits.
A pointer only has a type at compile time. At run time, everything that can be done to that address has already been determined. The compiler checks for that, as you already noted. Checking the type of a variable at run time is only useful in languages where a variable's type could change, as in dynamic languages. The closest Delphi comes to that is with its Variant type. The type of the variable is always Variant, but you can store many types of values in it. To find out what it holds, you can use the VarType function.
Any time you could want to use TypeInfo to get the type information of the type associated with a variable, you can also directly name the type you're interested in; if the variable is in scope, then you can go find its declaration and use the declared type in your call to TypeInfo.
If you want to pass an arbitrary address to a function and have that function discover the type information for itself, you're out of luck. You will instead need to pass the PTypeInfo value as an additional parameter. That's what all the built-in Delphi functions do. For example, when you call New on a pointer variable, the compiler inserts an additional parameter that holds the PTypeInfo value for the type you're allocating. When you call SetLength on a dynamic array, the compiler inserts a PTypeInfo value for the array type.
The answer that you gave suggests that you're looking for something other than what you asked for. Given your question, I thought you were looking for a hypothetical function that could satisfy this code:
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= GetVariableTypeInfo(#S);
Assert(Info = TypeInfo(string));
Info:= GetVariableTypeInfo(#Instance);
Assert(Info = TypeInfo(IObjectType));
Info:= GetVariableTypeInfo(#Obj);
Assert(Info = TypeInfo(TDBGrid));
end;
Let's use the IsClass and IsObject functions from the JCL to build that function:
function GetVariableTypeInfo(pvar: Pointer): PTypeInfo;
begin
if not Assigned(pvar) then
Result := nil
else if IsClass(PPointer(pvar)^) then
Result := PClass(pvar).ClassInfo
else if IsObject(PPointer(pvar)^) then
Result := PObject(pvar).ClassInfo
else
raise EUnknownResult.Create;
end;
It obviously won't work for S or Instance above, but let's see what happens with Obj:
Info := GetVariableTypeInfo(#Obj);
That should give an access violation. Obj has no value, so IsClass and IsObject both will be reading an unspecified memory address, probably not one that belongs to your process. You asked for a routine that would use a variable's address as its input, but the mere address isn't enough.
Now let's take a closer look at how IsClass and IsObject really behave. Those functions take an arbitrary value and check whether the value looks like it might be a value of the given kind, either object (instance) or class. Use it like this:
// This code will yield no assertion failures.
var
p: Pointer;
o: TObject;
a: array of Integer;
begin
p := TDBGrid;
Assert(IsClass(p));
p := TForm.Create(nil);
Assert(IsObject(p));
// So far, so good. Works just as expected.
// Now things get interesting:
Pointer(a) := p;
Assert(IsObject(a));
Pointer(a) := nil;
// A dynamic array is an object? Hmm.
o := nil;
try
IsObject(o);
Assert(False);
except
on e: TObject do
Assert(e is EAccessViolation);
end;
// The variable is clearly a TObject, but since it
// doesn't hold a reference to an object, IsObject
// can't check whether its class field looks like
// a valid class reference.
end;
Notice that the functions tell you nothing about the variables, only about the values they hold. I wouldn't really consider those functions, then, to answer the question of how to get type information about a variable.
Furthermore, you said that all you know about the variable is its address. The functions you found do not take the address of a variable. They take the value of a variable. Here's a demonstration:
var
c: TClass;
begin
c := TDBGrid;
Assert(IsClass(c));
Assert(not IsClass(#c)); // Address of variable
Assert(IsObject(#c)); // Address of variable is an object?
end;
You might object to how I'm abusing these functions by passing what's obviously garbage into them. But I think that's the only way it makes sense to talk about this topic. If you know you'll never have garbage values, then you don't need the function you're asking for anyway because you already know enough about your program to use real types for your variables.
Overall, you're asking the wrong question. Instead of asking how you determine the type of a variable or the type of a value in memory, you should be asking how you got yourself into the position where you don't already know the types of your variables and your data.
With generics, it is now possible to get the type info without specifying it.
Certain users indicated the following code doesn't compile without errors.
As of Delphi 10 Seattle, version 23.0.20618.2753, it compiles without errors, as seen below in the screenshot.
program TypeInfos;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.TypInfo;
type
TTypeInfo = class
class procedure ShowTypeInfo<T>(const X: T);
end;
{ TTypeInfo }
class procedure TTypeInfo.ShowTypeInfo<T>(const X: T);
var
LTypeInfo: PTypeInfo;
begin
LTypeInfo := TypeInfo(T);
WriteLn(LTypeInfo.Name);
end;
var
L: Exception;
B: Boolean;
begin
// Console output
TTypeInfo.ShowTypeInfo(L); // Exception
TTypeInfo.ShowTypeInfo(B); // Boolean
end.
Not that I know of. You can get RTTI (Run Time Type Information) on published properties of a class, but not for "normal" variables like strings and integers and so forth. The information is simply not there.
Besides, the only way you could pass a var without passing a type is to use either a generic TObject parameter, a generic type (D2008, as in ), or as an untyped parameter. I can't think of another way of passing it that would even compile.

Resources