I read with interest Nick Hodges blog on Why You Should Be Using Interfaces
and since I'm already in love with interfaces at a higher level in my coding I decided to look at how I could extend this to quite low levels and to investigate what support for this existed in the VCL classes.
A common construct that I need is to do something simple with a TStringList, for example this code to load a small text file list into a comma text string:
var
MyList : TStrings;
sCommaText : string;
begin
MyList := TStringList.Create;
try
MyList.LoadFromFile( 'c:\temp\somefile.txt' );
sCommaText := MyList.CommaText;
// ... do something with sCommaText.....
finally
MyList.Free;
end;
end;
It would seem a nice simplification if I could write with using MyList as an interface - it would get rid of the try-finally and improve readability:
var
MyList : IStrings;
//^^^^^^^
sCommaText : string;
begin
MyList := TStringList.Create;
MyList.LoadFromFile( 'c:\temp\somefile.txt' );
sCommaText := MyList.CommaText;
// ... do something with sCommaText.....
end;
I can't see an IStrings defined though - certainly not in Classes.pas, although there are references to it in connection with OLE programming online. Does it exist? Is this a valid simplification? I'm using Delphi XE2.
There is no interface in the RTL/VCL that does what you want (expose the same interface as TStrings). If you wanted to use such a thing you would need to invent it yourself.
You would implement it with a wrapper like this:
type
IStrings = interface
function Add(const S: string): Integer;
end;
TIStrings = class(TInterfacedObject, IStrings)
private
FStrings: TStrings;
public
constructor Create(Strings: TStrings);
destructor Destroy; override;
function Add(const S: string): Integer;
end;
constructor TIStrings.Create(Strings: TStrings);
begin
inherited Create;
FStrings := Strings;
end;
destructor TIStrings.Destroy;
begin
FStrings.Free; // don't use FreeAndNil because Nick might see this code ;-)
inherited;
end;
function TIStrings.Add(const S: string): Integer;
begin
Result := FStrings.Add(S);
end;
Naturally you would wrap up the rest of the TStrings interface in a real class. Do it with a wrapper class like this so that you can wrap any type of TStrings just by having access to an instance of it.
Use it like this:
var
MyList : IStrings;
....
MyList := TIStrings.Create(TStringList.Create);
You may prefer to add a helper function to actually do the dirty work of calling TIStrings.Create.
Note also that lifetime could be an issue. You may want a variant of this wrapper that does not take over management of the lifetime of the underlying TStrings instance. That could be arranged with a TIStrings constructor parameter.
Myself, I think this to be an interesting thought experiment but not really a sensible approach to take. The TStrings class is an abstract class which has pretty much all the benefits that interfaces offer. I see no real downsides to using it as is.
Since TStrings is an abstract class, an interface version of it wouldn't provide much. Any implementer of that interface would surely be a TStrings descendant anyway, because nobody would want to re-implement all the things TStrings does. I see two reasons for wanting a TStrings interface:
Automatic resource cleanup. You don't need a TStrings-specific interface for that. Instead, use the ISafeGuard interface from the JCL. Here's an example:
var
G: ISafeGuard;
MyList: TStrings;
sCommaText: string;
begin
MyList := TStrings(Guard(TStringList.Create, G));
MyList.LoadFromFile('c:\temp\somefile.txt');
sCommaText := MyList.CommaText;
// ... do something with sCommaText.....
end;
To protect multiple objects that should have the same lifetime, use IMultiSafeGuard.
Interoperation with external modules. This is what IStrings is for. Delphi implements it with the TStringsAdapter class, which is returned when you call GetOleStrings on an existing TStrings descendant. Use that when you have a string list and you need to grant access to another module that expects IStrings or IEnumString interfaces. Those interfaces are clunky to use otherwise — neither provides all the things TStrings does — so don't use them unless you have to.
If the external module you're working with is something that you can guarantee will always be compiled with the same Delphi version that your module is compiled with, then you should use run-time packages and pass TStrings descendants directly. The shared package allows both modules to use the same definition of the class, and memory management is greatly simplified.
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 am implementing an object TTextFile that is a framework for using the low level pascal file function with the OO paradigm. I want to add to developers the option to use it as a TStringList when needed in the same object, like this:
TTextFile = class(TObject)
constructor Create(FileName: String);
procedure OpenForRead;
procedure OpenForWrite;
{...}
property Content: TStringList;
end;
But my problem is that I want the Content property to use user LoadFromFile only at the first time the application uses it. Not in the Create construction, because the file might be too big, and the programmer would prefer to use the other functions in this case. The Content would be use when he knows the file he is using will not be very big.
An example of a big file is a list with all the client names and citizen ID. An example of a very tiny file is that same list, but only with the clients that are waiting to be attended in the current day.
Is it possible to be done in OO pascal? If it is not possible, I will have to make a kind of activation procedure or an overload Create and make the programmer always check if the Content is loaded before use it.
Use the concept of lazy initialization. The first time the Content property is read, load the file contents, but then keep the contents available so that subsequent accesses of the property don't re-read the file.
private
FContent: TStrings;
function GetContent: TStrings;
public
property Content: TStrings read GetContent;
function TTextFile.GetContent: TStrings;
begin
if not Assigned(FContent) then begin
FContent := TStringList.Create;
try
FContent.LoadFromFile(FFileName);
except
FContent.Free;
FContent := nil;
raise;
end;
end;
Result := FContent;
end;
Certainly this is possible.
Change your class declaration:
TTextFile = class(TObject)
constructor Create(FileName: String);
procedure OpenForRead;
procedure OpenForWrite;
function GetContent: TStringList;
{...}
property Content: TStringList read GetContent;
end;
and implement it:
function TTextFile.GetContent: TStringList;
begin
Result := TStringList.Create;
Result.LoadFromFile(FFileName); // Presumes FileName is stored in FFileName in constructor
end;
for a framework I wrote a wrapper which takes any object, interface or record type to explore its properties or fields. The class declaration is as follows:
TWrapper<T> = class
private
FType : TRttiType;
FInstance : Pointer;
{...}
public
constructor Create (var Data : T);
end;
In the constructor I try to get the type information for further processing steps.
constructor TWrapper<T>.Create (var Data : T);
begin
FType := RttiCtx.GetType (TypeInfo (T));
if FType.TypeKind = tkClass then
FInstance := TObject (Data)
else if FType.TypeKind = tkRecord then
FInstance := #Data
else if FType.TypeKind = tkInterface then
begin
FType := RttiCtx.GetType (TObject (Data).ClassInfo); //<---access violation
FInstance := TObject (Data);
end
else
raise Exception.Create ('Unsupported type');
end;
I wonder if this access violation is a bug in delphi compiler (I'm using XE).
After further investigation I wrote a simple test function, which shows, that asking for the class name produces this exception as well:
procedure TestForm.FormShow (Sender : TObject);
var
TestIntf : IInterface;
begin
TestIntf := TInterfacedObject.Create;
OutputDebugString(PChar (TObject (TestIntf).ClassName)); //Output: TInterfacedObject
Test <IInterface> (TestIntf);
end;
procedure TestForm.Test <T> (var Data : T);
begin
OutputDebugString(PChar (TObject (Data).ClassName)); //access violation
end;
Can someone explain me, what is wrong? I also tried the procedure without a var parameter which did not work either. When using a non generic procedure everything works fine, but to simplify the use of the wrapper the generic solution would be nice, because it works for objects and records the same way.
Kind regards,
Christian
Your code contains two wrong assumptions:
That you can obtain meaningful RTTI from Interfaces. Oops, you can get RTTI from interface types.
That a Interface is always implemented by a Delphi object (hence your attempt to extract the RTTI from the backing Delphi object).
Both assumptions are wrong. Interfaces are very simple VIRTUAL METHOD tables, very little magic to them. Since an interface is so narrowly defined, it can't possibly have RTTI. Unless of course you implement your own variant of RTTI, and you shouldn't. LE: The interface itself can't carry type information the way an TObject does, but the TypeOf() operator can get TypeInfo if provided with a IInterface
Your second assumption is also wrong, but less so. In the Delphi world most interfaces will be implemented by Delphi objects, unless of course you obtain the interface from a DLL written in an other programming language: Delphi's interfaces are COM-compatible, so it's implementations can be consumed from any other COM-compatible language and vice versa. But since we're talking Delphi XE here, you can use this syntax to cast an interface to it's implementing object in an intuitive and readable way:
TObject := IInterface as TObject;
that is, use the as operator. Delphi XE will at times automagically convert a hard cast of this type:
TObject := TObject(IInterface);
to the mentioned "as" syntax, but I don't like this magic because it looks very counter-intuitive and behaves differently in older versions of Delphi.
Casting the Interface back to it's implementing object is also wrong from an other perspective: It would show all the properties of the implementing object, not only those related to the interface, and that's very wrong, because you're using Interfaces to hide those implementation details in the first place!
Example: Interface implementation not backed by Delphi object
Just for fun, here's a quick demo of an interface that's not backed by an Delphi object. Since an Interface is nothing but an pointer to a virtual method table, I'll construct the virtual method table, create a pointer to it and cast the the pointer to the desired Interface type. All method pointers in my fake Virtual Method table are implemented using global functions and procedures. Just imagine trying to extract RTTI from my i2 interface!
program Project26;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
// This is the interface I will implement without using TObject
ITestInterface = interface
['{CFC4942D-D8A3-4C81-BB5C-6127B569433A}']
procedure WriteYourName;
end;
// This is a sample, sane implementation of the interface using an
// TInterfacedObject method
TSaneImplementation = class(TInterfacedObject, ITestInterface)
public
procedure WriteYourName;
end;
// I'll use this record to construct the Virtual Method Table. I could use a simple
// array, but selected to use the record to make it easier to see. In other words,
// the record is only used for grouping.
TAbnormalImplementation_VMT = record
QueryInterface: Pointer;
AddRef: Pointer;
ReleaseRef: Pointer;
WriteYourName: Pointer;
end;
// This is the object-based implementation of WriteYourName
procedure TSaneImplementation.WriteYourName;
begin
Writeln('I am the sane interface implementation');
end;
// This will implement QueryInterfce for my fake IInterface implementation. All the code does
// is say the requested interface is not supported!
function FakeQueryInterface(const Self:Pointer; const IID: TGUID; out Obj): HResult; stdcall;
begin
Result := S_FALSE;
end;
// This will handle reference counting for my interface. I am not using true reference counting
// since there is no memory to be freed, si I am simply returning -1
function DummyRefCounting(const Self:Pointer): Integer; stdcall;
begin
Result := -1;
end;
// This is the implementation of WriteYourName for my fake interface.
procedure FakeWriteYourName(const Self:Pointer);
begin
WriteLn('I am the very FAKE interface implementation');
end;
var i1, i2: ITestInterface;
R: TAbnormalImplementation_VMT;
PR: Pointer;
begin
// Instantiate the sane implementation
i1 := TSaneImplementation.Create;
// Instantiate the very wrong implementation
R.QueryInterface := #FakeQueryInterface;
R.AddRef := #DummyRefCounting;
R.ReleaseRef := #DummyRefCounting;
R.WriteYourName := #FakeWriteYourName;
PR := #R;
i2 := ITestInterface(#PR);
// As far as all the code using ITestInterface is concerned, there is no difference
// between "i1" and "i2": they are just two interface implementations.
i1.WriteYourName; // Calls the sane implementation
i2.WriteYourName; // Calls my special implementation of the interface
WriteLn('Press ENTER to EXIT');
ReadLn;
end.
Two possible answers.
If this always happens, even when T is an object, then it's a compiler error and you ought to file a QC report about it. (With Interfaces, the cast-an-interface-to-an-object thing requires some black magic from the compiler, and it's possible that the generics subsystem doesn't implement it properly.)
If you're taking a T that's not an object, though, such as a record type, and getting this error, then everything's working as designed; you're just using typecasts improperly.
Either way, there's a way to get RTTI information out of any arbitrary type. You know how TRttiContext.GetType has two overloads? Use the other one. Instead of calling GetType (TObject (Data).ClassInfo), try calling GetType(TypeInfo(Data)).
Oh, and declare FInstance as a T instead of a pointer. It'll save you a lot of hassle.
Is there a way to explore a interface's properties with Rtti?
The following code does not work:
procedure ExploreProps;
var
Ctx: TRttiContext;
RttiType: TRttiType;
RttiProp: TRttiProp;
begin
RttiType := Ctx.GetType(TypeInfo(IMyInterface));
for RttiProp in RttiType.GetProperties do
Writeln(RttiProp.ToString);
end;
Has anyone a solution how to do this correctly?
Interfaces are collections of functions. They don't really have properties the way objects do; that's just a bit of syntactic sugar that the compiler adds for you to make it easier to write code for them. The difference is that on objects, properties allow controlled access to private and protected members, whereas on interfaces, all members are public so there's no need for the properties.
As I known, there is no support for normal interfaces. You could add {$M+} and then try again.
Add this function in your interface
function GetObject: TObject;
and implement it in the class.
the use the GetObject function with RTTI routines
var
obj: IPerson;
begin
obj := TPerson.Create;
Count := GetPropList(obj.GetObject.ClassInfo, tkAny, #List);
end;
Please note that your class should be inherited from TInterfacedPersistent not TInterfacedObject
TPerson = class(TInterfacedPersistent, IPerson)
late answer, but you could typecast your interfae to TObject, e.g.
RttiType := Ctx.GetType(TObject(IMyInterface).ClassInfo);
I tend to use Delphi's TStringList for text manipulation, so I write a lot of procedures/functions like:
var
TempList: TStringList;
begin
TempList:= TStringList.Create;
try
// blah blah blah do stuff with TempList
finally
TempList.Free;
end;
end;
It would be nice to cut out the creation and freeing for such a common utility class.
Since we now have records with methods, is it possible to wrap a Class like TStringList in a
Record so I could just have:
var
TempList: TRecordStringList;
begin
// blah blah blah do stuff with TempList
end;
It's possible. Create an interface that exposes the methods / objects you want:
type
IStringList = interface
procedure Add(const s: string); // etc.
property StringList: TStringList read GetStringList; // etc.
end;
Implement the interface, and have it wrap a real TStringList:
type
TStringListImpl = class(TInterfacedObject, IStringList)
private
FStringList: TStringList; // create in constructor, destroy in destructor
// implementation etc.
end;
Then implement the record:
type
TStringListRecord = record
private
FImpl: IStringList;
function GetImpl: IStringList; // creates TStringListImpl if FImpl is nil
// returns value of FImpl otherwise
public
procedure Add(const s: string); // forward to GetImpl.Add
property StringList: TStringList read GetStringList; // forward to
// GetImpl.StringList
// etc.
end;
The fact that there's an interface inside the record means the compiler will handle reference counting automatically, calling _AddRef and _Release as copies get created and destroyed, so lifetime management is automatic. This works for objects that will never contain a reference to themselves (creating a cycle) - reference counting needs various tricks to get over cycles in the reference graph.
If you are lucky enough to have upgraded to Delphi 2009 then check out Barry's work with smart pointers.
TSmartPointer<T: class> = record
strict private
FValue: T;
FLifetime: IInterface;
public
constructor Create(const AValue: T); overload;
class operator Implicit(const AValue: T): TSmartPointer<T>;
property Value: T read FValue;
end;
They are really cool, but require Generics and Anonymous methods. If you haven't upgraded to Delphi 2009, then do it now! Especially while they are offering their BOGO special. You also get Marco's Delphi Developer Handbook free just for downloading the trial. I already purchased a copy of it too.
There is another example already implemented in CC.
StringList is the same as TStringList except that it is a value
type. It does not have to be created, destroyed or put within
try/finally. This is done by the compiler for you. There are
virtually no special performance penalties for these to work:
var
strings: StringList;
astr: string;
begin
strings.Add('test1');
strings.Add('test2');
aStr := string(strings);
RichEdit.Lines.AddStrings(strings);
end;
The code can be used as a template to wrap any TObject as a value class type.
It already has everything for a TStringList exposed for you.