BPL File needs Run-Time Packages ! - delphi

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.

Related

Component is specific class - does not work in BPL structure

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;

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.

How to solve "Need imported data reference" while building with runtime packages

To help us modularize a monolithic application, we are in the process of setting up packages for use in debug builds, while still compiling to a single exe for release builds.
One of our packages (EAUtils) contains a unit that is now producing [DCC Error] E2201 Need imported data reference ($G) to access 'SMsgDlgWarning' from unit 'SystemUtils'.
This happens when building the EAUtils package itself. I am not into building packages that depend on EAUtils yet. EAUtils only depends on rtl/vcl packages and a package I created for the Jedi WinApi units.
This is a result of the lines:
// This is a TaskDialog override, with the same args as the old MessageDlg.
function TaskDialog(const aContent: string; const Icon: HICON = 0;
const Buttons: TTaskDialogCommonButtonFlags = TDCBF_OK_BUTTON): Integer;
const
Captions: array[TMsgDlgType] of Pointer = (#SMsgDlgWarning, #SMsgDlgError, #SMsgDlgInformation, #SMsgDlgConfirm, nil);
var
aMsgDlgType: TMsgDlgType;
aTitle: string;
begin
aMsgDlgType := TaskDialogIconToMsgDlgType(Icon);
if aMsgDlgType <> mtCustom then
aTitle := LoadResString(Captions[aMsgDlgType])
else
aTitle := Application.Title;
More specifically this is a result of referencing SMsgDlgWarning, SMsgDlgError, SMsgDlgInformation and SMsgDlgConfirm, which are all declared in Vcl.Const.
Please note that this code compiles without error when we are building a single executable.
As a means of optimization, our include file does contain {$IMPORTEDDATA OFF} as this allows for faster access to (global) variables and constants. See http://hallvards.blogspot.com/2006/09/hack13-access-globals-faster.html.
According to the documentation on the error ( http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/cm_package_varref_xml.html ) this is the cause and it says "To alleviate the problem, it is generally easiest to turn on the $IMPORTEDDATA switch and recompile the unit that produces the error."
So, I have set {$IMPORTEDDATA ON} in our include file and made doubly sure by setting the Use imported data references to true in the Delphi Compiler | Compiling | Debugging section of the project options.
Unfortunately, contrary to the documentation, this did not alleviate the problem. Even setting this compiler directive directly above the offending code and rebuilding the package did not remove the errors.
What else do I need to do to solve this E2201 error?
Not sure, but it may be significant that SMsgDlgWarning and its friends are resource strings?
The error message is, IMHO, misleading, it's Vcl.Consts which has been compiled with $G- and that's causing the problem.
As a workaround, you can use something like this:
function Captions(AType: TMsgDlgType): Pointer;
begin
Result := nil;
case AType of
TMsgDlgType.mtWarning:
Result := #SMsgDlgWarning;
TMsgDlgType.mtError:
Result := #SMsgDlgError;
TMsgDlgType.mtInformation:
Result := #SMsgDlgInformation;
TMsgDlgType.mtConfirmation:
Result := #SMsgDlgConfirm;
end;
end;
Using a const array of string compiles, too (although it breaks localization):
const
Captions: array[TMsgDlgType] of string = (SMsgDlgWarning, SMsgDlgError, SMsgDlgInformation, SMsgDlgConfirm, '');
or you could build your own package containing Vcl.* units, with {$G+} and use that instead of the standard vcl package. I prefer the first solution; the latter can potentially create more problems later with deployment (so-called "DLL hell").

delphi exe and dll without build with runtime packages

For my last project i was using many frames in my delphi application ,so i dicided to create dlls and put them inside the dlls(ALL created in Delphi)
i have gone through many websites and came up with the code that works but for that example i have to compile both apps and dlls with build with runtime packages which means i have to distribute the bpls also. and if dont check build with runtime packages error is coming
this is the code i found
in exe
procedure TForm1.Button1Click(Sender: TObject);
type
TGetTheFrame =Function( Owner: TComponent; TheParent: TWinControl ): TFrame; stdcall ;
var
GetTheFrame : TGetTheFrame;
begin
try
GetTheFrame(application,TabSheet1).Free ;
except
end;
frm := GetTheFrame(application,TabSheet1) ;
dllHandle := LoadLibrary('project1.dll') ;
if dllHandle <> 0 then
begin
GetTheFrame := GetProcAddress(dllHandle, 'GetTheFrame') ;
frm := GetTheFrame(application,TabSheet1) //call the function
{ ShowMessage('error function not found') ;
FreeLibrary(dllHandle) ; }
end
else
begin
ShowMessage('xxxx.dll not found / not loaded') ;
end
in dll
Function GetTheFrame( Owner: TComponent; TheParent: TWinControl ): TFrame; stdcall;
Begin
Result := TFrame2.Create( Owner );
Result.Parent := TheParent;
End;
thats all but i want this code to work without runtime packages
Too bad. That code won't work without run-time packages. (And with run-time packages, you should use LoadPackage instead of LoadLibrary.)
Without packages, each module of your program (the EXE and each DLL) has its own copy of the definition of all the standard classes, including TFrame, TWinControl, and even TObject. A TWinControl class from the EXE doesn't look like a TWinControl to the DLL.
Since you're sharing classes between modules, you need to make sure they all have the same definitions of those classes, and run-time packages is how you do that.
If you really won't use run-time packages, then you need to change the interface of your DLL so it doesn't share any Delphi object types. Instead of the TWinControl parent, pass the control's Handle property, or any other HWnd value to serve as the parent window. The DLL code will not be able to assume that there is a Delphi object for the parent anymore, and the EXE will not be able to assume that the control it receives is a Delphi object; they will be restricted to using the Windows API to manipulate window handles and send messages.

How to recognize the Registered classes in a Delphi Package

I am going through most of my applications and porting them to D2009 and I have one application that makes use of dynamic packages. For the life of me I cannot get my host application to recognize classes registered in a package. I traced through and the initialization section in the package being loaded was called and RegisterClasses was called but when I do a GetClass() call the classes are not available. Is there someone out there who can enlighten me as to what might be going on? I have researched and looked to see if there are any issues with the D2009 release and dynamic packages and so far I have found nothing. I'm beginning to wonder if I have a corrupted installation of Delphi or some other problem.
TIA
If you are using a 3rd party memory manager then make sure it is proven to work with D2009 (actually 2007 and up).
With FastMM (which is the default MM since 2007) you would have to set the UseRuntimePackages define in FastMM4Options.inc
make sure that the following steps are done:
Create a new package in Delphi;
Insert a form in this package;
Insert a "inicialization" section in the form and uses the RegisterClass method. (registerClass(TForm1)); Don't forget the "T".
Save and compile the package;
Close all;
Copy the .bpl file (c:\Users\Public\Documents\RAD Studio\5.0\Bpl) to the application folder;
Create a new aplication in Delphi;
Go in Project > Options > Packages, and check the box "Build with runtime packages";
Leave only "vcl;rtl" in the text field and click OK button;
Insert a button;
In the source of the button, insert the code:
procedure TForm1.Button1Click(Sender: TObject);
var
PackageModule: HModule;
AClass: TPersistentClass;
begin
PackageModule := LoadPackage('Package1.bpl');
if PackageModule <> 0 then
begin
AClass := GetClass('TForm2');
if AClass <> nil then
with TComponentClass(AClass).Create(Application)
as TCustomForm do
begin
ShowModal;
Free;
end;
UnloadPackage(PackageModule);
end;
end;
Compile the application. =)

Resources