Component is specific class - does not work in BPL structure - delphi

I am upgrading Delphi software from Delphi 6 (2001) to Delphi 11 Alexandria.
This software consists of many BPL's, but this code is not working properly. The is command is not returning True, when checking if the component from a BPL is an TIBQuery - although it really is.
procedure LoadDLLAndPassDatabaseConnection(const DLLName: string);
var
PackageHandle: HMODULE;
ServiceModule: TMyServiceModule;
I: Integer;
Component: TComponent;
begin
PackageHandle := LoadPackage(PChar(DLLName));
ServiceModule := TMyServiceModule(GetProcAddress(hInst,'GetServiceModule'));
if Assigned(ServiceModule) then
begin
for I:=0 to ServiceModule.ComponentCount - 1 do
begin
Component := ServiceModule.Components[I];
// This component is declared in another bpl.
// It really is an TIBQuery, but the following if never returns True...
// (Evaluating Component.ClassName results in 'TIBQuery')
if Component is TIBQuery then
begin
// this is never executed...
TIBQuery(Component).Database := GetDatabase;
end;
end;
end;
end;
I already considered to compare classnames, but this does not work for descendants. And we tried toggling project options such as "Emit runtime type information", but that's not making any difference.
How to get this working?
Thank you!

The is operator does not work across BPLs (DLLs) for the following reason:
The class you are inspecting is implemented inside its own unit file.
You build the BPL, link the unit, and a RTTI section is created inside the BPL file.
Then, you build the EXE, link the unit, and a new RTTI section is created inside the EXE file.
Now: the class name is the same for the two modules, but the RTTI, used by the is operator to check equality, are different, so the operator returns FALSE!
Solution: check equality againts class name.

I found this somewhere, but it seems to contradict Antionio's answer a bit.
When you use packages, there is only ever one copy of any unit in
memory. One copy of Forms, one copy of SysUtils, one copy of System
(well, most of it), one copy of StdCtrls, etc.
All class-related operations, such as the "is" and "as" operators, rely
on class references. Class references are actually just addresses. They
point to definitions for the layouts of the classes' internals. (They
point to what's called the virtual-method table, the VMT.) Two classes
are the same if they point to the same VMT -- if the addresses are equal.
When you have a class defined in the EXE's copy of StdCtrls and the same
class defined in a DLL's copy of StdCtrls, those classes will really
have different addresses. The "is" and "as" operators won't work with
cross-module clases. But when you use packages, there is only one copy
of the class, kept in vcl70.bpl, so all modules that reference that
package will share a single class definition.

As Antonio Petricca wrote (thank you!), it's not possible to use the is operator. I now have implemented this by comparing (ancestor) class names - I want to share the code:
function IsSameClassOrAncestor(const ClazzToCheck: TClass; const Clazz: TClass): Boolean;
begin
Result := SameText(ClazzToCheck.ClassName, Clazz.ClassName);
if not Result and not SameText(ClazzToCheck.ClassName, 'TObject') then
begin
Result := IsSameClassOrAncestor(ClazzToCheck.ClassParent, Clazz);
end;
end;
This way, I can check this as follows:
if IsSameClassOrAncestor(Component, TIBQuery)
begin
// The code is now executed correctly, also for descendants
TIBQuery(Component).Database := GetDatabase;
end;

Related

TDictionary cast fails in referenced package

A TDictionary is assigned to an TObject variable. I can cast the TOBject back to a TDictionary in the same project. However if the cast is done in a referenced bpl project the cast fails depending on how the project is referenced.
The test code:
procedure TestTDefault.Test1;
var
Obj: TObject;
begin
Obj := TDictionary<string, Integer>.Create;
Check(Obj is TDictionary<string, Integer>);
CheckNotNull(Obj as TDictionary<string, Integer>);
// this calls the function in the referenced bpl package.
// Returns True if Obj can be cast back to a TDictionary.
CheckTrue(TestIfDictionary(Obj)); // outcome depends if referenced packge is included in <DCC_UsePackage>
end;
The function in the dependent package:
function TestIfDictionary(aObject: TObject): Boolean;
begin
Result := aObject is TDictionary<string,Integer>;
end;
I have a simple project group with only two packages:
The DUnit Test Runner (Package1Tests.exe)
Package1.bpl
Both packages have the same compiler/linker options set.
What is very odd is that the test works only if Package1 is NOT listed as a runtime package:
However test fails if Package1 is listed a runtime package!!
I am using XE2. As to the purpose, this issue surfaced when dealing with RTTI. I was able to isolate the problem in this simple Test project. Problem has nothing to do with RTTI.
Just as a note, if instead of using a TDictionary I use a TStringList, then test always works. So problem seems to be somehow related to generics.
If needed I can make the simple test project available.
I have spent quite some time tracking down this problem. I am happy that I got to discover what triggers the problem. But unfortunately I can just not understand why this happens.
The problem you have here is related to generic instantiation. Each module is separately instantiating the generic type, and so they have different type identity. This is a basic limitation of the design of generics, and its interaction with runtime packages.
You could instantiate the generic type in a unit in the package, for instance like this:
type
TDictionaryStringInteger = class(TDictionary<string, Integer>);
And then use TDictionaryStringInteger in place of TDictionary<string, Integer>. However, this pretty much cripples your code as it stops you writing generic methods that use generic types.
The other way out of your bind is to stop using runtime packages. Frankly, that seems rather more attractive to me.
You should use type declaration for this generic class.
type
Tx = TDictionary<string, Integer>;
....
Obj := Tx.Create;
Check(Obj is Tx);
RTTI doesn't matter in your case.

Programmatically get all units used in a dpr in Delphi

I am new in Delphi and I am trying to make an application in which I will give as an input a .dpr file and the application will create a list with all the .pas files used by this .dpr... I still cannot find a function in Delphi or a way to read the uses of the .dpr in order to navigate through the file system to these pas files and read their uses, and so on... Does anyone has any idea on how to achieve this?
It's not exactly straightforward: You don't just need to read the .dpr file, but you also need to parse the .dproj and registry to get Search Paths. If you're trying to do this right, you also have to parse the .dpr and .pas files as code files so you can find the uses statements, handle {$I '...'} includes, {$IFDEF} blocks, interface vs implementation sections, and so on.
All that said, you might want to look to the open source CnPack and GExperts projects for inspiration. Both of them have solved this problem, and you may be able to leverage their work towards whatever problem you're trying to solve.
If you let Delphi create a .map file (linker option), it will contain a list of all source and dcu files used in that project. GExperts does that, using a simple parser for a map file which is taken from my dzlib https://sourceforge.net/p/dzlib/code/HEAD/tree/dzlib/trunk/src/u_dzMapFileReader.pas
I would like to update this question for possible answer for future reference.
Create a separate unit file (PhonyObject.pas)
unit PhonyObject;
interface
uses
System.Classes, FMX.Forms, FMX.Dialogs;
type
TPhonyObject = class(TObject)
end;
TPhonyClass = class of TPhonyObject;
procedure FindUnitName(anObject: TObject);
var
PhonyName: string;
PhonyClass: TPhonyClass;
PhonyInstance: TObject;
PhonyClassName: procedure(anObject: TObject) = FindUnitName; //function: String = FindUnitName;
implementation
uses System.TypInfo;
procedure FindUnitName(anObject: TObject);
begin
if anObject <> nil then PhonyName := anObject.UnitName
else if not (TObject.UnitName <> 'System') then
begin
if TypInfo.GetTypeData(anObject.ClassInfo) <> nil then PhonyName := String(GetTypeData(anObject.ClassInfo)^.UnitName);
end else PhonyName := TObject.UnitName;
//FreeAndNilProperties
end;
initialization
PhonyClass := TPhonyObject;
PhonyInstance := PhonyClass.Create;
ShowMessage('Unit Name =' + PhonyInstance.UnitName);
PhonyInstance.Free;
finalization
PhonyClass := nil; //PhonyClass.Free;
end.
And in order to use this inside another (multiple) units, this is the code I have used so far, but I hope to update it later on. I have this showing up inside a hand made "console" with black background and white text in a TMemo. If anyone wants the code for the TMemo (its not commonly known), or how to show all these inside basically a debug window, all you just have to do let me know. This is the best I have gotten it so far, but I need a better understanding of the child/parent object/classes
unit AnotherUnit;
interface
uses
System.Classes, PhonyObject;
type
TPhonyObj = class(TPhonyObject)
end;
//var
implementation
{$R *.fmx}
uses ...;
initialization
PhonyClass := TPhonyObj;
PhonyInstance := PhonyClass.Create;
ShowMessage('UnitName= ' + PhonyInstance.UnitName + ' (AnotherUnit)'); // PhonyClass.UnitName // PhonyClassName(PhonyInstance);
PhonyInstance.Free;
finalization
PhonyClass := nil;
end;
I used as unique of Unit Names and class names, as I could and I realize I don't actually use any objects till the end, none the less it should work with out any problems. Please comment if there are some better ideas, but I think this is a powerful feature for Delphi programming when you can predict when certain unit names are going to suddenly show up. And how to predict for them too.

Why does the is operator fail to return what I expect when passed an instance from a different module?

I work on Delphi project who interac with many other small libraries.
I use FastMM4 and I would like work with complex classes passed on dll parameter.
So for exemple I send my form to my dll. Into the dll I test the type of parameter with the operator "IS".
But into the Dll the operator "IS" return always "false"
Exemple
library Dll;
uses
FastMM4,
System.SysUtils,
System.Classes,
Vcl.Dialogs,
Vcl.Forms;
{$R *.res}
procedure Complex(L : TObject);stdcall;
begin
if L is TForm then
showmessage('Ok')
else
showmessage('Pas ok') ;
if L is TCustomFrame then
showmessage('Ok')
else
showmessage('Pas ok')
end;
exports
Complex;
begin
end.
And the call
procedure TffsIsOperator.Button2Click(Sender: TObject);
var
MaDLL : THandle;
Proc : procedure (l : TObject);
begin
try
MaDLL := LoadLibrary(PChar('Dll.dll'));
#Proc := GetProcAddress(MaDLL, 'Complex');
Proc(self);
finally
FreeLibrary(MaDLL);
end;
end;
Firstly, you have a calling convention mis-match. You must fix that by making the calling convention the same on both sides of the interop boundary.
Even when you fix that, the apparent misbehaviour of the is operator is to be expected. You have two instances of the VCL in your process. One in the host and one in the DLL. They each have distinct versions of the classes defined in the VCL. So, the DLL's TForm is a different class form the TForm in the host. And that is why is evaluates false.
The traditional way to handle this is to arrange that you only have one instance of the RTL/VCL in your process. And you achieve that through the use of runtime packages.
If runtime packages are not a viable option for you, and you must use a DLL, then you will have to give up passing any Delphi classes across the DLL boundary. I fully expect this to be unwelcome news, but that is just how it is. You cannot pass TObject instances across a DLL boundary and attempt to call methods, query type identity, etc. That is simply not supported for DLLs. Only for runtime packages.
So, if you have to use DLLs then you need to stick to simple types. Integers, floating point values, character types, arrays (but not dynamic arrays), records, pointers to such types, interfaces. As a simple rule of thumb, if you cannot find an example of your proposed interop in Win32, then it is probably invalid.

BPL File needs Run-Time Packages !

I have created a Package and i want to use the BPL File of my Package ...
My Package have VCL.dcp and RTL.dcp as Required libraries , i load this Package in my application without any errors but when i want to unload it , an Access Violation shown !
If i Build my Application with Run-Time Packages ( "vcl" and "rtl" ) , Access Violation not shown !
What is this mean ?! My Application need VCL and RTL Libraries to Load BPLs ?! I want to Load my Package like a DLL File , is there any solution ?
I`m using Delphi 2010
thanks a lot ...
Your BPL requires the RTL and VCL packages. If your Application doesn't require them, then that means the RTL and VCL units are compiled into your EXE file. When your EXE loads your BPL, you now have two copies of the RTL and VCL units — one set of copies comes from within the EXE, and the second copies come from the RTL and VCL packages that your package implicitly causes to be loaded.
Delphi isn't intended to accommodate that situation. It's possible that you have memory that was allocated by one RTL and attempted to get freed by the other RTL. Or there might be function pointers in the EXE that refer to functions that were in the VCL package.
I see three options for you:
Compile your EXE to use packages. Specifically, it should require the same RTL and VCL packages that your BPL requires.
Make your BPL not require any other packages. If it doesn't require RTL and VCL, then any RTL and VCL units that your package uses will get compiled into your BPL. You'll end up with two separate copies again, but it should work better since neither copy will think it's supposed to be shared.
Load your package like a real DLL instead of like a package. You said you wanted to use it like a DLL, so do that. Use LoadLibrary, and then use GetProcAddress to get whatever functions you want to call. If you go this route, it's probably better to not make your code be a package at all. Make it a DLL, and export functions that only use parameter types that you'd expect to find in other DLLs, like integers, character pointers, and record pointers, not strings or objects.
It should be clear that the first option is the easiest. The second could probably work, and it sounds like that's the way you'd prefer, but I expect it will generate more headaches before it finally works. The third option is best if you'll ever have to use other development environments during the lifetime of this project.
What have your package inside?
What work do you do with it?
How do you charge and discharge? What's in it?
What do you do with the package before unload it?
When you Unload it, all the objects/forms/components/... that yo've used is released?
ADDED: I Think that you are using anything of the package when you try to Onload. This is the reason of AV.
In an EXE compiled without runtime package, I load the package:
OutputDebugString(PChar('Loading the package'));
hand := LoadPackage('r:\rrrrrrr\Package1.bpl');
I Unload the package with this code:
OutputDebugString(PChar('Ready to Unload Package'));
UnloadPackage(hand);
OutputDebugString(PChar('Unloaded'));
The package has a unit with a form (form1) and a unit Init.pas, for initialization like this:
unit Init;
interface
// prototipos
procedure Start_P;
procedure Finish_P;
implementation
uses
Unit1, Windows;
procedure Finish_P();
begin
OutputDebugString(PChar('Finish_P form free'));
Form1.Free;
end;
procedure Start_P();
begin
OutputDebugString(PChar('Start_P Creating form'));
Form1 := TForm1.Create(nil);
Form1.Show;
end;
Initialization;
Start_P();
Finalization;
Finish_P();
end.
The package is loaded and the form visualized without problems, and the same with the operation of Close and Unload. The project is compiled with "Build with rutime packages" unchecked.
Can you post any code.
The result of OutputDebugString is this (no AV error):
[2644] Loading the package
[2644] Start_P Creating form
[2644] Ready to Unload Package
[2644] Finish_P form free
[2644] Unloaded
Regards.
Thanks for your helps ...
I put an example of my package and my Application here to Find what is the problem !
We have a package without requiring to Run-Time Packages like VCL and RTL , in other words i removed all libraries from the Requires section in my package :
my package contains a form with code below :
unit MyUnit;
interface
uses
Windows, Forms, StdCtrls, Buttons, Controls, Classes, Dialogs;
type
TMyForm = class(TForm)
MyLabel: TLabel;
MyEdit: TEdit;
PostBtn: TBitBtn;
procedure PostBtnClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MyForm: TMyForm;
implementation
{$R *.dfm}
function ShowForm(FCaption, LCaption : String) : String;
var
F : TMyForm;
begin
F := TMyForm.Create(nil);
try
F.Caption := FCaption;
F.MyLabel.Caption := LCaption;
F.ShowModal;
finally
Result := F.MyEdit.Text;
F.Free;
end;
end;
procedure TMyForm.PostBtnClick(Sender: TObject);
begin
if MyEdit.Text <> '' then
Close
else
ShowMessage('Please Enter Value !');
end;
exports
ShowForm;
end.
I Load this Package and Call ShowForm Function and then Unload package :
var
ShowF : function(FCaption, LCaption : String) : String;
MyPkg : HMODULE;
FC, LC : String;
begin
MyPkg := LoadPackage(ExtractFilePath(Application.ExeName)+'MyPackage.bpl');
FC := 'Enter Value ... ';
LC := 'Value : ';
if MyPkg <> 0 then
begin
try
#ShowF := GetProcAddress(MyPkg, 'ShowForm');
if Assigned(ShowF) then
Edit1.Text := ShowF(FC, LC)
else
ShowMessage('Function not found !');
finally
UnloadPackage(MyPkg);
end;
end;
end;
After the Procedure above done , the AV Shows !
#Neftalí : If I just do loading and unloading the Package , no AV Shows , but i think that is because i don`t call some routines or objects or ... that they need VCL or RTL Libraries , if i use objects and functions and ... of this package , after using them i will get an AV ...
is it true ?!
If I Build my application with Run-Time package ( VCL and RTL ) no AV will shown !
I`m confusing !! , I want to use an BPL package without any Run-Time package needed ...
thanks a lot ...
Yes, if you want to use runtime packages in your application you have to build it with runtime packages, and then it requires them (links statically with them).
The solution to your problem depends on what the problem actually is (which is unclear at the moment).
Ohhhhh, great oversight/neglect (mine).
With the code that you have posted, made a simple change a test it (use PChar).
function ShowForm(FCaption, LCaption : String) : PChar;
...
Result := PChar(F.MyEdit.Text);
...
The same when you define the sitaxis of the function:
ShowF : function(FCaption, LCaption : String):PChar;
Test it and say the result.
Regards.

RTTI on objects in Delphi

I'm trying to parse objects to XML in Delphi, so I read about calling the object's ClassInfo method to get its RTTI info.
The thing is, this apparently only works for TPersistent objects. Otherwise, I have to specifically add a compiler directive {$M+} to the source code for the compiler to generate RTTI info.
So I happily added the directive, only to find that, even if it did return something from the ClassInfo call (it used to return nil), now I cannot retrieve the class' properties, fields or methods from it. It's like it created the object empty.
Any idea what am I missing here? Thanks!
Did you put those properties and methods into the published section?
Besides that, 'classical' RTTI ($TYPEINFO ON) will only get you information on properties, not on methods. You need 'extended' RTTI ($METHODINFO ON) for those.
Good starting point for extended RTTI: David Glassborow on extended RTTI
(who would believe that just this minute I finished writing some code that uses extended RTTI and decided to browse the Stack Overflow a little:))
RTTI will only show you published properties,etc. - not just public ones.
Try your code with a TObject and see what happens - if that isn't working, post your code because not everyone is psychic.
Have you considered using the TXMLDocument component? It will look at your XML and then create a nice unit of Delphi classes that represents your XML file -- makes it really, really easy to read and write XML files.
As for the RttiType problem returning only nil, this probably occurs for one reason: in your test, you did not instantiate the class at any time. The compiler, because it never has a reference to this class (because it is not an instance at all), simply removes it from the information as a form of optimization. See the two examples below. The behavior is different when you have the class instantiated at some point in your code or not.
Suppose the following class:
type
TTest = class
public
procedure Test;
end;
and the following code below:
var
LContext: TRttiContext;
LType: TRttiType;
LTest: TTest;
begin
LContext := TRttiContext.Create;
for LType in LContext.GetTypes do
begin
if LType.IsInstance then
begin
WriteLn(LType.Name);
end;
end;
end;
so far, TTest class information is not available for use by RTTI. However, when we create at some point, within the application, then a reference is created for it within the compile, which makes this information available:
var
LContext: TRttiContext;
LType: TRttiType;
LTest: TTest;
begin
LTest := TTest.Create; //Here i´m using TTest.
//Could be in another part of the program
LContext := TRttiContext.Create;
for LType in LContext.GetTypes do
begin
if LType.IsInstance then
begin
WriteLn(LType.Name);
end;
end;
end;
At that point, if you use LContext.FindType ('TTest'), there will not be a nil return, because the compiler kept reference to the class. This explains the behavior you were having in your tests.

Resources