Delphi - Using TStringList in Class Definition (very new) - delphi

I'm doing a simple class definition in Delphi and I wanted to use a TStringList in the class & it's constructor (so everytime you create an object, you pass it a StringList and it does some magic stuff to the StringList data, copying the string list to it's own internal string list). The problem I get is that when I try to declare what it "uses" before the class definition (so it knows how to handle the TStringList), it fails on compile. But without that, it doesn't know what a TStringList is. So it seems to be a scoping issue.
Below is a (very simplified) class definition, similar to what I'm trying to do. Can someone suggest how I can make this work and get the scoping right?
I tried adding the uses statements at the project level as well, but it still fails. I wonder what I need to do to get this right.
unit Unit_ListManager;
interface
type
TListManager = Class
private
lmList : TStringList;
procedure SetList;
published
constructor Create(AList : TStringList);
end;
implementation
uses
SysUtils,
StrUtils,
Vcl.Dialogs;
constructor TBOMManager.Create(AList : TStringList);
begin
lmList := TStringList.Create;
lmList := AListList;
end;
procedure SetPartsList(AList : TStringList);
begin
lmList := AListList;
ShowMessage('Woo hoo, got here...');
end;
end.
Kind Regards

You didn't show where exactly you were adding the unit reference, but I'm betting it was the wrong place. Take note of the additional code between interface and type.
I've also corrected your definition of the constructor, which you had placed in published instead of public. Only property items belong in the published section.
unit Unit_ListManager;
interface
uses
Classes,
SysUtils,
StrUtils,
Vcl.Dialogs;
type
TListManager = Class
private
lmList : TStringList;
procedure SetList;
public
constructor Create(AList : TStringList);
end;
implementation
constructor TListManager.Create(AList : TStringList);
begin
inherited Create; // This way, if the parent class changes, we're covered!
// lmList := TStringList.Create; This would produce a memory leak!
lmList := AListList;
end;
procedure TListManager.SetList;
begin
// You never provided an implementation for this method
end;
end.

Related

Delphi interface type incompatibility

What I am trying to do is to add common methods to these two classes which have the same indirect ancestor.
IMyMethods = interface
procedure SomeMethod;
end;
TMyADODataset =class(TADODataset, IMyMethods) // ADO
public
procedure SomeMethod;
end;
TMyUniDataset =class(TUniTable, IMyMethods) // UniDAC
public
procedure SomeMethod;
end;
SomeMethod would be implemented differently for ADO and for UniDAC. So I thought an interface is perfect.
Then we have
TMyTable =class
private
FDataset: TDataset;
end;
Here I have choosen TDataset as that is the common ancestor of TADODataset and TUniTable.
FDataset could be instantiated as follows:
if FProvider = prADO then
FDataset := TMyADODataset.Create
else
FDataset := TMyUniDataset.Create;
Now the problem is how to call SomeMethod of FDataset, the following does not compile and gives a type incompatibility error:
IMyMethods(FDataset).SomeMethod;
This is because TDataset does not implement IMyMethods, which is correct. But is there any way I can trick the compiler into accepting this? Or is there a better solution? I thought of class helpers but the implentation of SomeMethod will be different for ADO and UniDAC.
Use the SysUtils.Supports() function to obtain the IMyMethods interface from the FDataset object, eg:
uses
..., SysUtils;
var
Intf: IMyMethods;
...
if Supports(FDataset, IMyMethods, Intf) then
Intf.SomeMethod;
Just note that, in order for this to work, IMyMethods needs to have a Guid assigned to it, eg:
type
IMyMethods = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
procedure SomeMethod;
end;
You can generate a new Guid directly in the Code Editor by pressing Ctrl+Shift+G.

Delphi Using unit procedure on form

I'm learning OOP and I have created a basic program so far. I have create my own clas:
Type
Zombie = class
private
fLife : Integer;
fAge : Integer;
fHeight: String;
public
Constructor Create(pLife, pAge : Integer; pHeight : String);
Procedure SetLife(pLife : Integer);
function GetLife : Integer;
Procedure ShowLife;
end;
The procedure ShowLife does exactly what it says:
procedure Zombie.ShowLife;
begin
ShowMessage(inttostr(fLife));
end;
I'm trying to call this procedure on a Form but it says undeclared identifier:
procedure Tform1.ShowLifebtnClick(Sender: TObject);
begin
Zombies_Unit.ShowLife;
end;
I have included the unit in the user of the Form. How can I use methods on another form
You need to create and free the object before/after you use it. The pattern is like this:
MyZombie := TZombie.Create(10, 20, 30);
try
MyZombie.ShowLife();
finally
MyZombie.Free();
end;
You have to create an instance of your class and call the method of that object like
MyZombie := Zombie.create(20,15);
MyZombie.ShowLife;
...
MyZombie.free;
Sending from mobile, cannot format code.
EDIT/SUPPLEMENT:
As my short answer seems to be suitable to tech bad habits (I am sorry for that) I want to add the following advices to the asker:
Please use Try/Finally constructs to avoid that objects are not removed in case of an error occuring between create() and free() like Zdravko Danev's answer points out. It also makes sense to use common naming conventions to make your code easier to understand (e.g. TZombie as class name).
You must pay attention in one thing: Your class is in the same file of your form? If the answer is no, you must declare the unit name on uses of your form file like:
unit MyUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TMyForm = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
MyForm: TMyForm;
implementation
uses unitzombie; //The name unit where is your class
{$R *.dfm}
end.
After solving this little problem, you must create your object before calling this methods:
procedure Tform1.ShowLifebtnClick(Sender: TObject);
var
Zombi: Zombie;
begin
Zombi := Zombie.Create(5,10,15);
try
Zombi.ShowLife;
finally
Zombi.Free;
end;
end;

How do I prevent duplication of generated code for a generic class?

Delphi has a nasty habit of duplicating code for generic classes. Even if that code is really the same, because the generic types are similar.
I want to prevent duplication for storing different classes.
In my generic container I only use Free to clean up if needed.
Suppose I have a generic container like so:
unit Unit1;
interface
uses Generics.Collections;
type
TMyContainer<T> = class(TObject)
strict private
FData: TList<T>;
public
constructor Create; virtual;
end;
I know T will often be an object. Because all objects are really TObject I don't want my container to create duplicate generic code for different types of objects.
Will the following trick work to prevent duplication?
A- Substitute the constructor with a class function:
unit Unit2;
uses Unit1;
type
TMyContainer<T> = class(Unit1.TMyContainer<T>)
public
class function Create: TMyContainer<T>; static;
end;
B: implement the class function Create like so:
class function TMyContainer<T>.Create: TMyContainer<T>;
var
X: TObject;
begin
if GetTypeKind(T) = tkClass then begin
X:= Unit1.TMyContainer<TObject>.Create;
end else begin
X:= Unit1.TMyContainer<T>.Create;
end;
TObject(Result):= X;
end;
Will this trick work to prevent the compiler from generating duplicate code for different types of objects, or will this fail because I'm using incorrect assumptions?
Note that I don't want to resort to using a non-generic store for my data.
Full sample code follows
unit Unit49;
interface
uses Generics.Collections;
type
TMyContainer<T> = class(TObject)
strict private
FData: TList<T>;
public
constructor Create; virtual;
end;
implementation
constructor TMyContainer<T>.Create;
begin
inherited Create;
FData:= TList<T>.Create;
end;
end.
Sample program
program Project85;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Unit49 in 'Unit49.pas';
type
TMyContainer<T> = class(Unit49.TMyContainer<T>)
public
class function Create: TMyContainer<T>; static;
end;
{ TMyContainer<T> }
class function TMyContainer<T>.Create: TMyContainer<T>;
var
Y: T;
X: TObject;
begin
if GetTypeKind(T) = tkClass then begin
X:= Unit49.TMyContainer<TObject>.Create;
end else begin
X:= Unit49.TMyContainer<T>.Create;
end;
TObject(Result):= X;
end;
var
A: TMyContainer<TObject>;
B: TMyContainer<TLanguages>;
begin
A:= TMyContainer<TObject>.Create;
B:= TMyContainer<TLanguages>.Create;
readln;
end.
Will this trick work to prevent the compiler from generating duplicate
code for different types of objects, or will this fail because I'm
using incorrect assumptions?
No, it will not work.
Basically, compiler follows your T through whole class hierarchy and replaces it with specific type.
For start, you will have separate TList<T> code generated for both TObject and TLanguages because your container is declared as FData: TList<T>, then
your trick collection also inherits from generic T TMyContainer<T> = class(Unit49.TMyContainer<T>) and whole code in your class function is basically useless.
Compiler will generate duplicate code for Unit49.TMyContainer<TLanguages> class as well as Unit49.TMyContainer<TObject> class.
From your example it is hard to say what code duplication are you trying to avoid. If container class is as simple as you have written in your example, then all code duplication will come from TList<T> class. If you are trying to avoid that one, there is no easy way out.
Part of your problem comes from fact that you have T that can be anything. It is hard to optimize it. The most optimization you could get is using array of T for storing data and then delegating manipulation functions where you can use TObject as base for all classes and plain T for others.
How much can you gain with above also depends on which Delphi version do you use, because in most recent versions TList<T> has been optimized a bit with similar techniques.
However, if you can have separate containers for class and other types then you can achieve code folding for TObject and descendant containers using TObjectList<TObject> (or even non generic TObjectList on Windows) for storing all specific classes and implementing thin wrapper functions with typecast for any type safe functions you need. Of course, each such function will have some code generated for each specific type, but since they are just typecast wrappers that will not be as much code as it would be if you would use full TList<T> for each class type.
TMyObjectContainer<T> = class(TObject)
strict private
FData: TObjectList<TObject>;
public
constructor Create; virtual;
destructor Destroy; override;
function Data(index: integer): T;
end;
constructor TMyObjectContainer<T>.Create;
begin
inherited;
FData := TObjectList<TObject>.Create;
end;
constructor TMyObjectContainer<T>.Create;
begin
FData.Free;
inherited;
end;
function TMyObjectContainer<T>.Data(index: integer): T;
begin
Result := T(FData.Items[index]);
end;

Delphi Interface Reference Counting

I ran into a strange situation while testing something today.
I have a number of interfaces and objects. The code looks like this:
IInterfaceZ = interface(IInterface)
['{DA003999-ADA2-47ED-A1E0-2572A00B6D75}']
procedure DoSomething;
end;
IInterfaceY = interface(IInterface)
['{55BF8A92-FCE4-447D-B58B-26CD9B344EA7}']
procedure DoNothing;
end;
TObjectB = class(TInterfacedObject, IInterfaceZ)
procedure DoSomething;
end;
TObjectC = class(TInterfacedObject, IInterfaceY)
public
FTest: string;
procedure DoNothing;
end;
TObjectA = class(TInterfacedObject, IInterfaceZ, IInterfaceY)
private
FInterfaceB: IInterfaceZ;
FObjectC: TObjectC;
function GetBB: IInterfaceZ;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
property BB: IInterfaceZ read GetBB implements IInterfaceZ;
property CC: TObjectC read FObjectC implements IInterfaceY;
end;
procedure TObjectB.DoSomething;
begin
Sleep(1000);
end;
procedure TObjectA.AfterConstruction;
begin
inherited;
FInterfaceB := TObjectB.Create;
FObjectC := TObjectC.Create;
FObjectC.FTest := 'Testing';
end;
procedure TObjectA.BeforeDestruction;
begin
FreeAndNil(FObjectC);
FInterfaceB := nil;
inherited;
end;
function TObjectA.GetBB: IInterfaceZ;
begin
Result := FInterfaceB;
end;
procedure TObjectC.DoNothing;
begin
ShowMessage(FTest);
end;
Now if I access the various implementations like this I get the following results:
procedure TestInterfaces;
var
AA: TObjectA;
YY: IInterfaceY;
ZZ: IInterfaceZ;
NewYY: IInterfaceY;
begin
AA := TObjectA.Create;
// Make sure that the Supports doesn't kill the object.
// This line of code is necessary in XE2 but not in XE4
AA._AddRef;
// This will add one to the refcount for AA despite the fact
// that AA has delegated the implementation of IInterfaceY to
// to FObjectC.
Supports(AA, IInterfaceY, YY);
YY.DoNothing;
// This will add one to the refcount for FInterfaceB.
// This is also allowing a supports from a delegated interface
// to another delegated interface.
Supports(YY, IInterfaceZ, ZZ);
ZZ.DoSomething;
// This will fail because the underlying object is actually
// the object referenced by FInterfaceB.
Supports(ZZ, IInterfaceY, NewYY);
NewYY.DoNothing;
end;
The first Supports call, which uses the variable in the implements, returns YY which is actually a reference to TObjectA. My AA variable is reference counted. Because the underlying reference counted object is a TObjectA, the second supports, which uses the interface in the supports call, works and returns me an interface. The underlying object is actually now a TObjectB. The internal object behind FInterfaceB is the object being reference counted. This part makes sense because GetBB is actually FInterfaceB. As expected here, the last call to Supports returns a null for NewYY and the call at the end fails.
My question is this, is the reference counting on TObjectA with the first supports call by design? In other words, when the property that implements the interface is returning an object and not an interface does this mean that the owner object will be the one doing the reference counting? I was always under the impression that implements would also result in the internal delegated object being reference counted instead of the main object.
The declarations are as follows:
property BB: IInterfaceZ read GetBB implements IInterfaceZ;
With this option above, the internal object behind FInterfaceB is the one that is reference counted.
property CC: TObjectC read FObjectC implements IInterfaceY;
With this second option above, TObjectA is the one that is being reference counted and not the delegated object FObjectC.
Is this by design?
Edit
I just tested this in XE2 and the behavior is different. The second Supports statement returns nil for ZZ. The debugger in XE4 tells me that the YY is referring to (TObjectA as IInterfaceY). In XE2 it tells me that its a (Pointer as IInterfaceY). Also, in XE2, the AA is not ref counted on the first support statement but the internal FObjectC is reference counted.
Additional Information after the question answered
There is one caveat to this. You can chain the Interface version but not the object version. That means that something like this will work:
TObjectBase = class(TInterfacedObject, IMyInterface)
…
end;
TObjectA = class(TInterfacedObject, IMyInterface)
FMyInterfaceBase: IMyInterface;
property MyDelegate: IMyInterface read GetMyInterface implements IMyInterface;
end;
function TObjectA.GetMyInterface: IMyInterface;
begin
result := FMyInterfaceBase;
end;
TObjectB = class(TInterfacedObject, IMyInterface)
FMyInterfaceA: IMyInterface;
function GetMyInterface2: IMyInterface;
property MyDelegate2: IMyInterface read GetMyInterface2 implements IMyInterface;
end;
function TObjectB.GetMyInterface2: IMyInterface;
begin
result := FMyInterfaceA;
end;
But the object version gives a compiler error with this saying that TObjectB doesn't implement the methods for the interface.
TObjectBase = class(TInterfacedObject, IMyInterface)
…
end;
TObjectA = class(TInterfacedObject, IMyInterface)
FMyObjectBase: TMyObjectBase;
property MyDelegate: TMyObjectBase read FMyObjectBase implements IMyInterface;
end;
TObjectB = class(TInterfacedObject, IMyInterface)
FMyObjectA: TObjectA;
property MyDelegate2: TObjectA read FMyObjectA implements IMyInterface;
end;
So if you want to start chaining the delegation then you need to stick to interfaces or work around it another way.
tl;dr This is all by design – it's just that the design changes between XE2 and XE3.
XE3 and later
There is quite a difference between delegation to an interface type property and delegation to a class type property. Indeed the documentation calls out this difference explicitly with different sections for the two delegation variants.
The difference from your perspective is as follows:
When TObjectA implements IInterfaceY by delegating to class type property CC, the implementing object is the instance of TObjectA.
When TObjectA implements IInterfaceZ by delegating to interface type property BB, the implementing object is the object that implements FInterfaceB.
One key thing to realise in all this is that when you delegate to a class type property, the class that is delegated to need not implement any interfaces. So it need not implement IInterface and so need not have _AddRef and _Release methods.
To see this, modify your code's definition of TObjectC to be like so:
TObjectC = class
public
procedure DoNothing;
end;
You will see that this code compiles, runs, and behaves exactly the same way as does your version.
In fact this is ideally how you would declare a class to which an interface is delegated as a class type property. Doing it this way avoids the lifetime issues with mixing interface and class type variables.
So, let's look at your three calls to Supports:
Supports(AA, IInterfaceY, YY);
Here the implementing object is AA and so the reference count of AA is incremented.
Supports(YY, IInterfaceZ, ZZ);
Here the implementing object is the instance of TObjectB so its reference count is incremented.
Supports(ZZ, IInterfaceY, NewYY);
Here, ZZ is an interface implemented by the instance of TObjectB which does not implement IInterfaceY. Hence Supports returns False and NewYY is nil.
XE2 and earlier
The design changes between XE2 and XE3 coincide with the introduction of the mobile ARM compiler and there were many low-level changes to support ARC. Clearly some of these changes apply to the desktop compilers too.
The behavioural difference that I can find concerns delegation of interface implementation to class type properties. And specifically when the class type in question supports IInterface. In that scenario, in XE2, the reference counting is performed by the inner object. That differs from XE3 which has the reference counting performed by the outer object.
Note that for a class type that does not support IInterface, the reference counting is performed by the outer object in all versions. That makes sense since there's no way for the inner object to do it.
Here's my example code to demonstrate the difference:
{$APPTYPE CONSOLE}
uses
SysUtils;
type
Intf1 = interface
['{56FF4B9A-6296-4366-AF82-9901A5287BDC}']
procedure Foo;
end;
Intf2 = interface
['{71B0431C-DB83-49F0-B084-0095C535AFC3}']
procedure Bar;
end;
TInnerClass1 = class(TObject, Intf1)
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
procedure Foo;
end;
TInnerClass2 = class
procedure Bar;
end;
TOuterClass = class(TObject, Intf1, Intf2)
private
FInnerObj1: TInnerClass1;
FInnerObj2: TInnerClass2;
public
constructor Create;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
property InnerObj1: TInnerClass1 read FInnerObj1 implements Intf1;
property InnerObj2: TInnerClass2 read FInnerObj2 implements Intf2;
end;
function TInnerClass1.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TInnerClass1._AddRef: Integer;
begin
Writeln('TInnerClass1._AddRef');
Result := -1;
end;
function TInnerClass1._Release: Integer;
begin
Writeln('TInnerClass1._Release');
Result := -1;
end;
procedure TInnerClass1.Foo;
begin
Writeln('Foo');
end;
procedure TInnerClass2.Bar;
begin
Writeln('Bar');
end;
constructor TOuterClass.Create;
begin
inherited;
FInnerObj1 := TInnerClass1.Create;
end;
function TOuterClass.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TOuterClass._AddRef: Integer;
begin
Writeln('TOuterClass._AddRef');
Result := -1;
end;
function TOuterClass._Release: Integer;
begin
Writeln('TOuterClass._Release');
Result := -1;
end;
var
OuterObj: TOuterClass;
I1: Intf1;
I2: Intf2;
begin
OuterObj := TOuterClass.Create;
Supports(OuterObj, Intf1, I1);
Supports(OuterObj, Intf2, I2);
I1.Foo;
I2.Bar;
I1 := nil;
I2 := nil;
Readln;
end.
The output on XE2 is:
TInnerClass1._AddRef
TOuterClass._AddRef
Foo
Bar
TInnerClass1._Release
TOuterClass._Release
The output on XE3 is:
TOuterClass._AddRef
TOuterClass._AddRef
Foo
Bar
TOuterClass._Release
TOuterClass._Release
Discussion
Why did the design change? I cannot answer that definitively, not being privy to the decision making. However, the behaviour in XE3 feels better to me. If you declare a class type variable you would expect its lifetime to be managed as any other class type variable would be. That is, by explicit calls to destructor on the desktop compilers, and by ARC on the mobile compilers.
The behaviour of XE2 on the other hand feels inconsistent. Why should the fact that a property is used for interface implementation delegation change the way its lifetime is managed?
So, my instincts tell me that this was a design flaw, at best, in the original implementation of interface implementation delegation. The design flaw has led to confusion and lifetime management troubles over the years. The introduction to ARC forced Embarcadero to review this issue and they changed the design. My belief is that the introduction of ARC required a design change because Embarcadero have a track record of not changing behaviour unless absolutely necessary.
The paragraphs above are clearly speculation on my part, but that's the best I have to offer!
You are mixing object pointers and interface pointers, which is always a recipe for disaster. TObjectA is not incrementing the reference count of its inner objects to ensure they stay alive for its entire lifetime, and TestInterfaces() is not incrementing the reference count of AA to ensure it survives through the entire set of tests. Object pointers DO NOT participate in reference counting! You have to manage it manually, eg:
procedure TObjectA.AfterConstruction;
begin
inherited;
FObjectB := TObjectB.Create;
FObjectB._AddRef;
FObjectC := TObjectC.Create;
FObjectC._AddRef;
FObjectC.FTest := 'Testing';
end;
procedure TObjectA.BeforeDestruction;
begin
FObjectC._Release;
FObjectB._Release;
inherited;
end;
AA := TObjectA.Create;
AA._AddRef;
Needless to say, manual reference counting undermines the use of interfaces.
When dealing with interfaces, you need to either:
Disable reference counting completely to avoid premature destructions. TComponent, for instance, does exactly that.
Do EVERYTHING using interface pointers, NEVER with object pointers. This ensures proper reference counting across the board. This is generally the preferred solution.

How can I make sure RTTI is available for a class without instantiating it?

I've recently posted a question in this forum asking for any advice regarding missing RTTI information in a DXE2 executable.
That post was a stripped down version of my actual case. RRUZ came to the rescue, and so the stripped down version was quickly resolved. The original problem, though, is still standing, and so I'm posting it in full now. "Main":
program MissingRTTI;
{$APPTYPE CONSOLE}
uses
System.SysUtils, RTTI, MyUnit in 'MyUnit.pas', RTTIUtil in 'RTTIUtil.pas';
var
RHelp: TRttiHelper;
begin
RHelp := TRttiHelper.Create();
if (RHelp.IsTypeFound('MyUnit.TMyClass')) then WriteLn('TMyClass was found.')
else WriteLn('TMyClass was not found.');
ReadLn;
RHelp.Free();
end.
RTTIUtil.pas:
unit RTTIUtil;
interface
uses
MyUnit;
type
TRttiHelper = class(TObject)
public
function IsTypeFound(TypeName: string) : boolean;
end;
implementation
uses
RTTI;
function TRttiHelper.IsTypeFound(TypeName: string): boolean;
var
rCtx: TRttiContext;
rType: TRttiType;
begin
Result := false;
rCtx := TRttiContext.Create();
rType := rCtx.FindType(TypeName);
if (rType <> nil) then
Result := true;
rCtx.Free();
end;
end.
and finally MyUnit.pas:
unit MyUnit;
interface
type
TMyClass = class(TObject)
end;
implementation
end.
The desired type is not found. However, if I change TRttiHelper.IsTypeFound so that it instantiates (and immediately frees) an instance of TMyClass, the type is found. Like so:
function TRttiHelper.IsTypeFound(TypeName: string): boolean;
var
rCtx: TRttiContext;
rType: TRttiType;
MyObj: TMyClass;
begin
Result := false;
MyObj:= TMyClass.Create();
MyObj.Free();
rCtx := TRttiContext.Create();
...
So I'm wondering, is there any way I can force RTTI to be emitted for TMyClass without actually instantiating it?
Update:
On a side not, I might mention that if I try to fetch the TRttiType using TRttiContext.GetType, the desired type is found. So there is some RTTI emitted. Checking the TRttiType.IsPublic property as retrieved by TRttiContext.GetType yields a true value, i.e. the retrieved type is public (and hence should be possible to locate using TRttiContext.FindType).
Add a reference to the class and make sure that the compiler/linker cannot strip it from the executable.
unit MyUnit;
interface
type
TMyClass = class(TObject)
end;
implementation
procedure ForceReferenceToClass(C: TClass);
begin
end;
initialization
ForceReferenceToClass(TMyClass);
end.
In production code you would want to place ForceReferenceToClass in a base unit so that it could be shared. The initialization section of the unit that declares the class is the most natural place for the calls to ForceReferenceToClass since the unit is then self-contained.
Regarding your observation that GetType can locate the type, the very act of calling GetType(TMyClass) adds a reference to the type to the program. It's not that the RTTI is present and FindType cannot find it. Rather, the inclusion of GetType(TMyClass) adds the RTTI to the resulting program.
I used {$STRONGLINKTYPES ON} and worked very well. Put it on main unit.

Resources