I have class
TFolder = class
NODE_INDEX: Integer;
FIRST_INDEX : Integer;
CODE_NAME: AnsiString;
NAME: AnsiString;
constructor Create(NewNODE_INDEX, NewFIRST_INDEX: Integer; NewCODE_NAME, NewNAME: AnsiString);
destructor Destroy; override;
end;
And i have Type
type
TFolderList = class (TObjectList<TFolder>)
end;
Then i try to use this type
TAccount = class
...
FOLDERS: TFolderList;
public
constructor Create(...);
destructor Destroy; override;
procedure LoadFoldersFromDisk(var _objectList: TFolderList);
end;
When i try to send my TObject list like parameter
procedure TForm1.FormCreate(Sender: TObject);
begin
olLOCALFolders := TObjectList<TFolder>.Create();
Account.LoadFoldersFromDisk(olLOCALFolders);
end;
I get error "Types of actual and formal var parameters must be identical". What I'm doing wrong?
The error is because your sub-class is a new type, distinct from TObjectList<TFolder> and hence the error.
It is probably a mistake to derive a sub-class like this:
type
TFolderList = class (TObjectList<TFolder>)
end;
Doing so forces all parties to use that type and denies you the opportunity to take advantage of generic type compatibility. If you have a generic method operating on TObjectList<T> then your new type would be incompatible.
Instead declare an alias:
type
TFolderList = TObjectList<TFolder>;
The point about an alias is that it is a different name for the same type as opposed to a new type which your code declares.
Or simply use TObjectList<TFolder> everywhere without declaring TFolderList.
More broadly your TFolder type seems more suited to be a value type. I think it may be better as a record rather than a class.
Further, a var parameter appears incorrect. You would use a var parameter if the function was going to modify the reference. But it is going to populate the list that is passed in by the caller. You should remove the var from the argument list.
Just replace the TObjectList<TFolder> wtih the TFolderList you defined eariler:
procedure TForm1.FormCreate(Sender: TObject);
begin
olLOCALFolders := TFolderList.Create();
Account.LoadFoldersFromDisk(olLOCALFolders);
end;
However, you probably do not need to use var parameter here - the method name LoadFoldersFromDisk suggest that the method will populate the list sent as parameter with items, and for that you can send the list by value. You only need to use var parameter if the method would alert the list object's memory location (as opposed to it's content), ie when the LoadFoldersFromDisk could free the original list and create new one.
Related
I have an object that is derived from the TStringList object that I call a "TAutoString." It allows you to specify an object type when the list is created. Then each time a new entry is added to the string list, it also creates a copy of the object associated with that string entry. This makes it easy to store all kinds of additional information along with each string. For example:
type TMyObject = class(TObject)
public
Cats: integer;
Dogs: integer;
Mice: integer;
end;
MO := TAutoString.Create(TMyObject);
Inside the object, the class information is stored in a class variable:
private
ObjectClass: TClass;
constructor TAutoString.Create(ObjClass: TClass);
begin
inherited Create;
ObjectClass:=ObjClass;
end;
Now, every time a new item is added, it creates a new object of the specified type:
function TAutoString.Add(const S: string): Integer;
begin
Result:=inherited Add(S);
Objects[Result]:=ObjectClass.Create;
end;
I can now add or read information associated with each string entry.
TMyObject(MO.Objects[25]).Cats := 17;
D:=TMyObject(MO.Objects[25]).Dogs;
This works great as along as the object doesn't have a constructor. If the object has a constructor, its constructor won't get called when the object is created because the constructor for TObject is not virtual.
Can anyone think of a way around this problem. I've seen solutions that use the RTTI libraries, but this is in Delphi-7, which doesn't have an RTTI library.
As an aside, it seems a bit strange that TObject's constructor is not virtual. If it were, it would enable all sorts of useful features like the one I'm trying to implement.
EDIT: Remy's suggestion below was just the nudge I needed. I had originally tried a similar strategy, but I couldn't make it work. When it didn't seem to work the way I thought it should, I assumed there must be something that I didn't understand about virtual methods. His post pushed me to look at it again. It turned out that I had left off the "Override" directive for the constructor of the object I wanted to attach. Now it works just the way it should.
The other issue I was concerned about was that I had already used the Auto Strings in a bunch of other applications where the object was based on "TObject" and I didn't want to go back and change all that code. I solved that issue by overloading the constructors and having one for TObject-based objects and another my TAutoClass objects:
constructor Create(ObjClass: TAutoClass); overload; virtual;
constructor Create(ObjClass: TClass); overload; virtual;
Depending on which constructor is called, the object class stored in a different in a different variable.
private
AutoClass: TAutoClass;
ObjectClass: TClass;
Then when the object is constructed I check to see which has been assigned and use that one:
procedure TAutoString.CreateClassInstance(Index: integer);
begin
if AutoClass<>nil then Objects[Index]:=AutoClass.Create
else Objects[Index]:=ObjectClass.Create
end;
The new version works perfectly with either type of object.
To do what you want, you will have to define a base class for your list objects to derive from, and then you can add a virtual constructor to that class. Your ObjectClass member will have to use that class type instead of using TClass.
For example:
type
TAutoStringObject = class(TObject)
public
constructor Create; virtual;
end;
TAutoStringObjectClass = class of TAutoStringObject;
TAutoString = class(TStringList)
private
ObjectClass: TAutoStringObjectClass;
public
constructor Create(ObjClass: TAutoStringObjectClass);
function Add(const S: string): Integer; override;
...
end;
...
constructor TAutoStringObject.Create;
begin
inherited Create;
end;
constructor TAutoString.Create(ObjClass: TAutoStringObjectClass);
begin
inherited Create;
ObjectClass := ObjClass;
end;
function TAutoString.Add(const S: string): Integer;
var
Obj: TAutoStringObject;
begin
Obj := ObjectClass.Create;
try
Result := inherited AddObject(S, Obj);
except
Obj.Free;
raise;
end;
end;
...
Then, you simply adjust your derived object classes to use TAutoStringObject instead of TObject, eg:
type
TMyObject = class(TAutoStringObject)
public
...
constructor Create; override;
end;
MO := TAutoString.Create(TMyObject);
...
And their constructor will be called, as expected.
Here is a hint for a cleaner solution:
It is possible to add a virtual constructor to Tobject.
To do so you will need to use what is called a "class helper".
Here is an example:
type
TobjectHelper = class helper for Tobject
public
constructor Create; virtual; // adds a virtual constructor to Tobject.
end;
(My use case was a TryCreateObject function to detect out of memory situation during object creation, wraps try except end and simply returns true/false to prevent try except blocks in code and instead use more logic controleable if statements)
Class helpers were apperently(?) introduced in Delphi 8 and later. Your requirements is for Delphi 7 so this may not work.
Unless you coding for Windows 95/Windows 98/Windows XP it may be time for you to upgrade to a more recent version of Delphi, especially the Delphi XE versions for unicode support, otherwise you coding against an aging platform that is about to become obsolete etc.
However for Windows 95/Windows 98 and Windows XP I do believe Delphi 2007 may be of some use, I believe it can compile code that can run on those older windows platforms, I could be wrong though.
Later versions of Delphi require certain windows system DLLs to be present otherwise the build/compiled executable will not run, w95/w98/wxp lack these dlls.
I have a logging class, which links to many modules. The main method of this class is a class method:
type
TSeverity = (seInfo, seWarning, seError);
TLogger = class
class procedure Log(AMessage: String; ASeverity: TSeverity);
end;
Somewhere else I have a function DoSomething() which does some things that I would like to log. However, I do not want to link all the modules of the logger to the module in which 'DoSomething()' is declared to use the logger. Instead I would like to pass an arbitrary logging method as a DoSomething's parameter and call it from its body.
The problem is that TLogger.Log requires parameter of TSeverity type which is defined in logger class. So I can't define a type:
type
TLogProcedure = procedure(AMessage: String; ASverity: TSeverity) of Object;
because I would have to include an unit in which TSeverity is declared.
I was trying to come up with some solution based on generic procedure but I am stuck.
uses
System.SysUtils;
type
TTest = class
public
class function DoSomething<T1, T2>(const ALogProcedure: TProc<T1,T2>): Boolean; overload;
end;
implementation
class function TTest.DoSomething<T1, T2>(const ALogProcedure: TProc<T1, T2>): Boolean;
var
LMessage: String;
LSeverity: Integer;
begin
//Pseudocode here I would like to invoke logging procedure here.
ALogProcedure(T1(LMessage), T2(LSeverity));
end;
Somewehere else in the code I would like to use DoSomething
begin
TTest.DoSomething<String, TSeverity>(Log);
end;
Thanks for help.
Update
Maybe I didn't make myself clear.
unit uDoer;
interface
type
TLogProcedure = procedure(AMessage: String; AErrorLevel: Integer) of Object;
// TDoer knows nothing about logging mechanisms that are used but it allows to pass ALogProcedure as a parameter.
// I thoight that I can somehow generalize this procedure using generics.
type
TDoer = class
public
class function DoSomething(const ALogProcedure: TLogProcedure): Boolean;
end;
implementation
class function TDoer.DoSomething(const ALogProcedure: TLogProcedure): Boolean;
begin
ALogProcedure('test', 1);
Result := True;
end;
end.
Separate unit with one of the logging mechanisms.
unit uLogger;
interface
type
TSeverity = (seInfo, seWarning, seError);
// I know that I could solve my problem by introducing an overloaded method but I don't want to
// do it like this. I thought I can use generics somehow.
TLogger = class
class procedure Log(AMessage: String; ASeverity: TSeverity); {overload;}
{class procedure Log(AMessage: String; ASeverity: Integer); overload;}
end;
implementation
class procedure TLogger.Log(AMessage: String; ASeverity: TSeverity);
begin
//...logging here
end;
{class procedure TLogger.Log(AMessage: String; ASeverity: Integer);
begin
Log(AMessage, TSeverity(ASeverity));
end;}
end.
Sample usage of both units.
implementation
uses
uDoer, uLogger;
procedure TForm10.FormCreate(Sender: TObject);
begin
TDoer.DoSomething(TLogger.Log); //Incompatible types: Integer and TSeverity
end;
Introducing generics here does not help. The actual parameters that you have are not generic. They have fixed type, string and Integer. The function you are passing them to is not generic and receives parameters of type string and TSeverity. These types are mis-matched.
Generics won't help you here because your types are all known ahead of time. There is nothing generic here. What you need to do, somehow, is convert between Integer and TSeverity. Once you can do that then you can call your function.
In your case you should pass a procedure that accepts an Integer, since you don't have TSeverity available at the point where you call the procedure. Then in the implementation of that procedure, where you call the function that does accept a TSeverity, that's where you convert.
In scenarios involving generic procedural types, what you have encountered is quite common. You have a generic procedural type like this:
type
TMyGenericProcedure<T> = procedure(const Arg: T);
In order to call such a procedure you need an instance of T. If you are calling the procedure from a function that is generic on T, then your argument must also be generic. In your case that argument is not generic, it is fixed as Integer. At that point your attempt to use generics unravels.
Having said all of that, what you describe doesn't really hang together at all. How can you possibly come up with the severity argument if you don't know what TSeverity is at that point? That doesn't make any sense to me. How can you just conjure up an integer value and hope that it matches this enumerated type? Some mild re-design would enable you to do this quite simply without any type conversions.
As David Heffernan says, you cannot use generics in this way. Instead you should use a function to map the error level to a severity type, and use that to glue together the two. Based on your updated example, one could modify it like this:
unit uDoer;
interface
type
TLogProcedure = reference to procedure(const AMessage: String; AErrorLevel: Integer);
// TDoer knows nothing about logging mechanisms that are used but it allows to pass ALogProcedure as a parameter.
type
TDoer = class
public
class function DoSomething(const ALogProcedure: TLogProcedure): Boolean;
end;
implementation
class function TDoer.DoSomething(const ALogProcedure: TLogProcedure): Boolean;
begin
ALogProcedure('test', 1);
Result := True;
end;
end.
You can then provide the glue procedure which converts the error level to a severity:
implementation
uses
uDoer, uLogger;
function SeverityFromErrorLevel(const AErrorLevel: Integer): TSeverity;
begin
if (AErrorLevel <= 0) then
result := seInfo
else if (AErrorLevel = 1) then
result := seWarning
else
result := seError;
end;
procedure LogProc(const AMessage: String; AErrorLevel: Integer);
var
severity: TSeverity;
begin
severity := SeverityFromErrorLevel(AErrorLevel);
TLogger.Log(AMessage, severity);
end;
procedure TForm10.FormCreate(Sender: TObject);
begin
TDoer.DoSomething(LogProc);
end;
Note I didn't compile this, but the essence is there. I used a procedure reference (reference to procedure) as they're a lot more flexible, which may come in handy later.
I have the following construct:
program Project26;
{$APPTYPE CONSOLE}
{$R *.res}
type
TPrint_address_func = function(offset: integer; info: disassembler_info): boolean;
disassembler_info = record
data: string;
print_address_func: TPrint_address_func;
end;
begin
end.
Obvious either the record of the function-type needs to be declared in a forward declaration.
I know that I cannot declare the record as forward, but...
Is there a way to declare the procedural-variable as forward?
Or can I replace the record with an old-school object and declare that as forward?
You cannot forward declare procedural types, or records. So, the conclusion is that you have to put the type definition inside the record:
type
disassembler_info = record
type
TPrint_address_func = function(info: disassembler_info): boolean;
var
data: string;
print_address_func: TPrint_address_func;
end;
FWIW, once I start defining types inside records, I tend to start breaking the declaration up with visibility specifiers. I'd declare this type like this:
type
disassembler_info = record
public
type
TPrint_address_func = function(info: disassembler_info): boolean;
public
data: string;
print_address_func: TPrint_address_func;
end;
If you pass a record pointer, then this problem is easy to solve, even in Delphi versions that don't support nested record types. Forward-declare a record pointer type, and then declare the function type using the record pointer. Finally, declare the record:
type
PDisassembler_info = ^TDisassembler_info;
TPrint_address_func = function(offset: Integer;
info: PDisassembler_info): Boolean;
TDisassembler_info = record
data: string;
print_address_func: TPrint_address_func;
end;
You're probably going to have more than just one function pointer, and you're probably going to have more than one instance of your record, too. As you extend this pattern, you're ultimately going to re-invent classes. Consider this:
type
TDisassembler_info = class
data: string;
function print_address(offset: Integer): Boolean; virtual; abstract;
end;
Now, instead of defining a free function, you declare a descendant of your class and override the abstract method. This has a few advantages as the number of function pointers and record instances grows:
The compiler automatically fills in the function pointers with all the right values. It stores them in the class's VMT. There's no chance you'll have a null function pointer by accidentally forgetting to assign print_address_func. The compiler will warn if you attempt to instantiate a class without overriding the abstract methods.
It's impossible to accidentally pass the wrong record pointer when you call the function. In your design, calling the function will look like this:
info.print_address_func(offset, info);
It would surely be an error if the record parameter you passed differed from the record whose function you called. With an object, the redundancy and opportunity for error go away:
info.print_address(offset);
No matter how many functions you have, the size of a single instance of the class remains constant because all instances share a single VMT. In your current model, if you have 100 instances of your record, you'll have 100 copies of the same function pointer.
It is possible to solve this with a record helper.
Type
disassembler_info = record
private
FP: Pointer;
public
data: string;
end;
TPrint_address_func = function(info: disassembler_info): boolean;
disassembler_info_helper = record helper for disassembler_info
private
procedure SetAFunc(aF: TPrint_Address_Func);
function GetAFunc: TPrint_Address_Func;
public
property print_address_func: TPrint_address_func read GetAFunc write SetAFunc;
end;
function disassembler_info_helper.GetAFunc: TPrint_Address_Func;
begin
Result := TPrint_address_func(FP);
end;
procedure disassembler_info_helper.SetAFunc(aF: TPrint_Address_Func);
begin
TPrint_address_func(FP) := TPrint_address_func(aF);
end;
function MyFunc(aRec: disassembler_info): boolean;
begin
Result := true;
WriteLn('Hello from MyFunc');
end;
var
aFunc: TPrint_address_func;
aRec:disassembler_info;
begin
aRec.print_address_func := MyFunc;
aFunc := arec.print_address_func;
if aFunc(aRec) then begin
WriteLn('Voila!');
end;
ReadLn;
end.
The helper injects a property of TPrint_address_func with read and write methods that operates on a private variable declared in disassembler_info.
I am new to delphi development. I have to create an event and pass some properties as parameters. Could someone share some demo program that shows how to do this from scratch. I googled nearly every site, they all gave a piece of code, but what I need is a full fledged program that is simple and understandable.
Here's a short-but-complete console application that shows how to create your own event in Delphi. Includes everything from type declaration to calling the event. Read the comments in the code to understand what's going on.
program Project23;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
// Declare an event type. It looks allot like a normal method declaration except
// it suffixed by "of object". That "of object" tells Delphi the variable of this
// type needs to be assigned a method of an object, not just any global function
// with the correct signature.
TMyEventTakingAStringParameter = procedure(const aStrParam:string) of object;
// A class that uses the actual event
TMyDummyLoggingClass = class
public
OnLogMsg: TMyEventTakingAStringParameter; // This will hold the "closure", a pointer to
// the method function itself + a pointer to the
// object instance it's supposed to work on.
procedure LogMsg(const msg:string);
end;
// A class that provides the required string method to be used as a parameter
TMyClassImplementingTheStringMethod = class
public
procedure WriteLine(const Something:string); // Intentionally using different names for
// method and params; Names don't matter, only the
// signature matters.
end;
procedure TMyDummyLoggingClass.LogMsg(const msg: string);
begin
if Assigned(OnLogMsg) then // tests if the event is assigned
OnLogMsg(msg); // calls the event.
end;
procedure TMyClassImplementingTheStringMethod.WriteLine(const Something: string);
begin
// Simple implementation, writing the string to console
Writeln(Something);
end;
var Logging: TMyDummyLoggingClass; // This has the OnLogMsg variable
LoggingProvider: TMyClassImplementingTheStringMethod; // This provides the method we'll assign to OnLogMsg
begin
try
Logging := TMyDummyLoggingClass.Create;
try
// This does nothing, because there's no OnLogMsg assigned.
Logging.LogMsg('Test 1');
LoggingProvider := TMyClassImplementingTheStringMethod.Create;
try
Logging.OnLogMsg := LoggingProvider.WriteLine; // Assign the event
try
// This will indirectly call LoggingProvider.WriteLine, because that's what's
// assigned to Logging.OnLogMsg
Logging.LogMsg('Test 2');
finally Logging.OnLogMsg := nil; // Since the assigned event includes a pointer to both
// the method itself and to the instance of LoggingProvider,
// need to make sure the event doesn't out-live the LoggingProvider
end;
finally LoggingProvider.Free;
end;
finally Logging.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
The complete project answer is good. But this is an alternate answer showing how to do what you want, in a form you already have.
Go into your form, and go to the interface section, in the types area, outside your form's class definition and add a type:
interface
type
TMyEvent = procedure(Sender:TObject;Param1,Param2,Param3:Integer) of object;
TMyForm = class(TForm)
....
It is traditional, but not required, that the first item in your event be the object sending it, but to use base class TObject instead of your form's actual class type.
The other parameters above are not required at all, but are showing you how you would declare your own additional data. if you don't need them, then just use Sender:TObject.
And in that case, you don't have to define TMyEvent at all, just use the TNotifyEvent type.
Now declare a field that uses that type, in your form:
TMyForm = class(TForm)
private
FMyEvent : TMyEvent;
...
Now declare a property that accesses that field, in your form's properties section:
// this goes inside the class definition just before the final closing end
property MyEvent:TMyEvent read FMyEvent write FMyEvent
Now go to where you want that event to 'fire' (get called if it is set) and write this:
// this goes inside a procedure or function, where you need to "fire" the event.
procedure TMyForm.DoSomething;
begin
...
if Assigned(FMyEvent) then FMyEvent(Self,Param1,Param2,Param3);
end;
You use an event handler to react when something else happens (for example AfterCreation and before closing).
In order to use events for your own class, you need to define the event type. Change the type and number of parameters needed.
type
TMyProcEvent = procedure(const AIdent: string; const AValue: Integer) of object;
TMyFuncEvent = function(const ANumber: Integer): Integer of object;
In the class, you can add a DoEvent (rename for the proper event). SO you can call the DoEvent internally. The DoEvent handles the possibility that an event is not assigned.
type
TMyClass = class
private
FMyProcEvent : TMyProcEvent;
FMyFuncEvent : TMyFuncEvent;
protected
procedure DoMyProcEvent(const AIdent: string; const AValue: Integer);
function DoMyFuncEvent(const ANumber: Integer): Integer;
public
property MyProcEvent: TMyProcEvent read FMyProcEvent write FMyProcEvent;
property MyFuncEvent: TMyFuncEvent read FMyFuncEvent write FMyFuncEvent;
end;
procedure TMyClass.DoMyProcEvent(const AIdent: string; const AValue: Integer);
begin
if Assigned(FMyProcEvent) then
FMyProcEvent(AIdent, AValue);
// Possibly add more general or default code.
end;
function TMyClass.DoMyFuncEvent(const ANumber: Integer): Integer;
begin
if Assigned(FMyFuncEvent) then
Result := FMyFuncEvent(ANumber)
else
Result := cNotAssignedValue;
end;
in the context of placing "events" into a DLL I described a concept using interfaces, step by step... maybe this helps in a different way: Using event listeners in a non-gui environment (DLL) (Delphi)
I'm serializing and deserializing an object (TComponent descendant) using the example in the ComponentToString section in the Delphi help file. This is so I can store the object in a VARCHAR field in the database.
When I need to instantiate a new instance of my class from a string stored in the database, can I do that using a constructor of the form CreateFromString(AOwner: TComponent; AData: String)? Or do I have to use a non-class method that returns an instance of my component class?
If I can use the constructor version, how to I "map" the return value of ReadComponent to the "self" that is being created by the constructor?
Here's the deserialization example from the help file:
function StringToComponentProc(Value: string): TComponent;
var
StrStream:TStringStream;
BinStream: TMemoryStream;
begin
StrStream := TStringStream.Create(Value);
try
BinStream := TMemoryStream.Create;
try
ObjectTextToBinary(StrStream, BinStream);
BinStream.Seek(0, soFromBeginning);
Result:= BinStream.ReadComponent(nil);
finally
BinStream.Free;
end;
finally
StrStream.Free;
end;
end;
In general, yes, you can make a constructor deserialize a string and use that information to initialize the new instance. A trivial example of that would be a class with a single Integer field. Pass a string to the constructor and have the constructor call StrToInt and initialize the field with the result.
But if the only function you have for deserialization is one that also creates the instance, then you cannot use that from the constructor because then you'll end up with two instances when you only wanted one. There's no way for a constructor to say, "Never mind; don't construct an instance after all. I already got one somewhere else."
However, that's not the situation you're in. As you should know, TStream.ReadComponent allows you to create the instance yourself. It only instantiates the class if you haven't already given it an instance to use. You should be able to write your constructor like this:
constructor TLarryComponent.CreateFromString(const AData: string);
var
StrStream, BinStream: TStream;
begin
Create(nil);
StrStream := TStringStream.Create(AData);
try
BinStream := TMemoryStream.Create;
try
ObjectTextToBinary(StrStream, BinStream);
BinStream.Position := 0;
BinStream.ReadComponent(Self);
finally
BinStream.Free;
end;
finally
StrStream.Free;
end;
end;
There we're passing the current object, designated by Self, to ReadComponent. The stream will ignore the class name stored in the stream and assume that the current object is of the correct class.
You can do this by a class (static) method, but not via a constructor.
Delphis' constructors are called by compiler intrinsic on the just-created instance, which is already partially initialized (it's of the desired class and instance/field storage is zeroed-out).
If you see the source of TStream.ReadComponent, you'll find that the components' real class is read from the source stream at first, then an empty instance is constructed and filled by RTTI from the stream and returned as the result. Which means:
To use TStream.ReadComponent, you'll need to register your class to Delphis' streaming system via RegisterClass.
Use a static class function instead of a constructor:
type
TYourClass = class(TComponent)
public
class function CreateFromString(AOwner: TComponent; AData: String): TYourClass; static;
end;
implementation
class function TYourClass.CreateFromString(AOwner: TComponent; AData: String): TYourClass;
begin
Result := (StringToComponentProc(AData) as TYourClass);
if AOwner <> nil then
AOwner.InsertComponent(Result);
end;
The AOwner part could be a problem though, since TStream.ReadComponent has no parameter for the owner.
There is another so question about that problem:
How can I specify the Owner of component read from a Delphi TStream?
Edit: I've updated the code sample to include the owner, too.
Note that inserting into the component list of the owner requires a unique or empty Name for the component that is being inserted.