I have an application that loads a BPL that as inside a simple form.
This form is an optional option of the main application.
The BPL loads correctly, the form is shown correctly, but I don’t know how to access the public methods and properties of the form inside the bpl.
Can anyone provide a simple example?
my code:
// Load the BPL on aplication Load
LoadPackage( 'About.bpl' );
// CAll for TForm1 inside the About.BPL
var
AClass: TClass;
AForm: TForm;
begin
AClass := GetClass('TForm1');
if AClass <> nil then
begin
Application.CreateForm(TComponentClass(AClass), AForm);
AForm.Show;
end;
// The unit TForm1 inside the BPL package
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
PublicMthd;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
Procedure TForm1.PublicMthd;
Begin
ShowMessage('Inside call');
End;
initialization
RegisterClass(TForm1);
finalization
UnRegisterClass(TForm1);
end.
How can i access "PublicMthd" in Tform1 ?
One of the interest of having TOptionalForm in a dynamically loaded bpl (assuming this from the "optional" bit)) is to avoid for your application to hold the definition of the TOptionalForm class specifically (it's in the unit contained in the package and only there).
That means that your application cannot know anything about it unless you use either:
- a shared base Class
- an Interface declaring the properties and methods you want to access
- some basic RTTI to access published properties and methods
- some extended RTTI to access public properties and methods (if you have D2010 or later)
- some external routines from the bpl accepting a base class parameter (or TObject/pointer) typecasting it as TOptionalForm internally.
This is very vague and general and more precision about your code would be needed to refine...
If you need to load the BPL dynamically, you should use - as already metioned by François:
an abstract class (which is more Delphi-like or)
an interface (which I consider cleaner and have better experience with)
placed into an interface-only unit used by both the main application and the form BPL.
I use an intermediate "contract/interface" BPL, statically used by both the main application and the dynamically loaded ones.
In the case of interface usage, may also look at the $WEAKPACKAGEUNIT directive to further decouple the BPL fom the application.
To comment on the comments - by using DLL exports or RTTI, you would basically bypass the whole point of BPLs, which is type and namespace sharing.
Related
This question already has answers here:
published property not shown in Object Inspector
(1 answer)
How do I use or resolve issues with visual form inheritance in Delphi?
(2 answers)
Register custom form so I can inherit from it from multiple projects, without copying the form to the Object Repository folder
(3 answers)
Closed 28 days ago.
I want all forms in project inherited from one base form. I now how it's doing. But I would like that in the Object Inspector for forms it was possible to change their own additional properties.
I tried to create a component:
unit FjForm;
interface
uses Forms, Classes;
type
TFjForm = class(TForm)
private
FSelected: boolean;
published
property Selected: Boolean read FSelected write FSelected;
end;
implementation
procedure Register;
begin
RegisterComponents('Fj', [TFjForm]);
end;
end.
Он компилируется, но в Tool Palette не появляется. Потом я создаю форму и делаю вот так
unit FramworkFjForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, FjForm,
Dialogs;
type
TForm3 = class(TFjForm)
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.
В Object Inspector ничего не меняется.
Fixing dfm
instead of object I write inherited
After that, the form stops opening, writes Error creating form: Ancestor for 'TFjForm' not found.
Of course, you can set properties in the form and initialize them in FormCreate, but it's more convenient to do this in the Object Inspector.
How to make it possible to set custom properties in the Object Inspector for the form?
There was a question similar to this however the user was using something way more advanced so I was quite confused.
This is the procedure the exception flares up. Specifically on the ADOCon.connected line. I am using the dbgo stuff and Microsoft access for my database.
The exception I am getting is: EAcessViolation. I'm wondering what mistake I've made to cause it and how to solve it. I have run the procedure on both with a pre-existing database and a new one. When there is a pre-existing database the exception is one the 19th line and without it is on the 14th line. As a user has mentioned, I have read the documentation however I am still confused on how to solve the error. The error is definitely here as this is the first piece of access I call.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,DB, ADODB,ComObj;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
ADOCom:TADOcommand;
ADOCon:TADOConnection;
ADOQ:TADOQuery;
nameDB:string;
db:OLEVariant;
begin
namedb:='Brill.accdb';
if not fileexists(namedb) then
begin
db:=createOLEObject('ADOX.Catalog');
db.create('Provider=Microsoft.ACE.OLEDB.12.0;Data Source='+nameDB+';');
db:=null;
ADOCon.connectionstring:='Provider=Microsoft.ACE.OLEDB.12.0;Data Source='+
nameDB+';';
ADOCon.connected:=True;
ADOCon.loginprompt:=False;
end
else
ADOCon.connectionstring:='Provider=Microsoft.ACE.OLEDB.12.0;Data Source='+
nameDB+';';
end.
Your code is riddled with errors.
When you declare variables of a particular class type in your code, you're responsible for creating an instance of that class and assigning it to the variable before using it, and cleaning up when you're finished with it.
var
ADOCon: TAdoConnection;
ADOCom:TADOcommand;
ADOQ:TADOQuery;
begin
ADOCon := TADOConnection.Create(nil);
try
// Set up connection properties here
ADOQ := TADOQuery.Create(nil);
try
// Set up ADOQ properties and use query here
finally
ADOQ.Free;
end;
finally
ADOCon.Free;
end;
end;
Also, your use of the db: OleVariant (and all code related to it) is doing absolutely nothing. You get an instance, assign properties to that instance, and then throw it away, which means you can just delete that variable and the three lines of code related to it entirely; they serve zero purpose.
Of course, the better solution than any of the above is to add a TDataModule to your form, drop a TADOConnection and TADOQuery on it, set the properties in the Object Inspector or the OnCreate of the datamodule. You can then move that datamodule into the list of available forms, move it up to be created before your main form, and have access from anywhere in your app that uses that datamodule, and the datamodule will free everything properly when you exit your application. It also separates all of the database code from your user interface, cleaning up your code considerably.
Delphi Berlin 10.1 adds [weak] references. Marco Cantu's Blog has some basics on it.
For my test I created two COM libraries holding two automation object types. The container object holds a list of the content objects while the content objects holds a weak reference to their container.
The following two scenarios were tested and worked correctly (weak references are set null and memory is released) :
A single COM library with both interfaces and CoClasses.
Two COM libraries one with the interfaces and another with the CoClasses
However, when I place the coclasses in two separate libraries the code produces "invalid class typecast", the error goes away when removing the [weak] attribute. Please excuse the odd sample, its purpose is simply to make the problem minimal and should not be taken as standard coding practice
Here is the first library .ridl file that defines both interfaces and the CoClass for the
container:
[
uuid(E1EE3651-A400-49BF-B5C5-006D9943B9C0),
version(1.0)
]
library DelphiIntfComLib
{
importlib("stdole2.tlb");
interface IMyContainer;
interface IMyContent;
coclass MyContainer;
[
uuid(A7EF86F7-40CD-41EE-9DA1-4D9B7B24F06B),
helpstring("Dispatch interface for MyContainer Object"),
dual,
oleautomation
]
interface IMyContainer: IDispatch
{
[id(0x000000C9)]
HRESULT _stdcall Add([in] IMyContent* AMyContent);
};
[
uuid(BFD6D976-8CEF-4264-B95A-B5DA7817F6B3),
helpstring("Dispatch interface for MyContent Object"),
dual,
oleautomation
]
interface IMyContent: IDispatch
{
[id(0x000000C9)]
HRESULT _stdcall SetWeakReferenceToContainer([in] IMyContainer* AContainer);
};
[
uuid(1F56198B-B1BE-4E11-BC78-0E6FF8E55214)
]
coclass MyContainer
{
[default] interface IMyContainer;
};
};
Here is my container implementation
unit Unit1;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, DelphiIntfComLib_TLB, StdVcl, Generics.Collections;
type
TMyContainer = class(TAutoObject, IMyContainer)
private
FList: TList<IMyContent>;
protected
procedure Add(const AMyContent: IMyContent); safecall;
public
Destructor Destroy; override;
procedure Initialize; override;
end;
implementation
uses ComServ;
procedure TMyContainer.Add(const AMyContent: IMyContent);
begin
FList.Add(AMyContent);
AMyContent.SetWeakReferenceToContainer(self);
end;
destructor TMyContainer.Destroy;
begin
FList.Free;
inherited;
end;
procedure TMyContainer.Initialize;
begin
inherited;
FList := TList<IMyContent>.create;
end;
initialization
TAutoObjectFactory.Create(ComServer, TMyContainer, Class_MyContainer,
ciMultiInstance, tmApartment);
end.
My second library reference the first and only contains my content interface's CoClass
[
uuid(65659EE4-1949-4112-88CA-F2D5B5D8DA2C),
version(1.0)
]
library DelphiImplComLib
{
importlib("stdole2.tlb");
importlib("DelphiIntfComLib.dll");
coclass MyContent;
[
uuid(79D1669A-8EB6-4AE6-8F4B-91137E6E6DC1)
]
coclass MyContent
{
[default] interface IMyContent;
};
and its implementation with the weak reference
unit Unit2;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, DelphiImplComLib_TLB, StdVcl, DelphiIntfComLib_TLB;
type
TMyContent = class(TAutoObject, IMyContent)
private
[Weak] //If included will cause "invalid class typecast" error
FContainer : IMyContainer;
protected
procedure SetWeakReferenceToContainer(const AContainer: IMyContainer); safecall;
end;
implementation
uses ComServ;
procedure TMyContent.SetWeakReferenceToContainer(const AContainer: IMyContainer);
begin
FContainer := AContainer;
end;
initialization
TAutoObjectFactory.Create(ComServer, TMyContent, Class_MyContent,
ciMultiInstance, tmApartment);
end.
I tested as follows
program Project13;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
DelphiImplComLib_TLB in 'impl\DelphiImplComLib_TLB.pas',
DelphiIntfComLib_TLB in 'Intf\DelphiIntfComLib_TLB.pas';
var
GMyContainer : IMyContainer;
GMyContent : IMyContent;
begin
GMyContainer := CoMyContainer.Create;
GMyContent := CoMyContent.Create;
GMyContainer.Add(GMyContent);
end.
Why do I get an error when I split my implementations? How can I alleviate this problem?
Do not use [Weak] for COM-interfaces. It is not intended for use with COM. [Weak] should only be used for internal non-exported-COM interfaces.
The reason is that there is no way for a COM-interface implementation, which may not even be implemented by a Delphi class, to properly handle the [weak] references. Additionally, the COM libraries you have aren't sharing the same implementation of the base TObject. You may get away with building everything using the shared rtl package, but even then... you're dancing on a land-mine.
As Allen Bauer explained in his answer, [weak] does not work with COM interfaces, as they are not guaranteed to be backed by Delphi TObject-derived classes, which is necessary for [weak] references to be auto-nil'ed when objects are freed. The RTL keeps track of weak references at runtime, but cannot track weak references across libraries unless a single instance of the RTL library is shared between them (ie if you compile the libraries with Runtime Packages enabled, and then deploy the RTL BPL with your executables).
However, as long as you don't need to use the auto-nil functionality of [weak], you can use an untyped Pointer instead:
type
TMyContent = class(TAutoObject, IMyContent)
private
FContainer : Pointer{IMyContainer};
...
end;
You would just have to typecast FContainer to IMyContainer whenever you need to use its methods/properties, eg:
IMyContainer(FContainer).Add(...);
In 10.1 Berlin and later, you can use the [unsafe] attribute instead:
type
TMyContent = class(TAutoObject, IMyContent)
private
[Unsafe] FContainer : IMyContainer;
...
end;
As mentioned on Marco's blog:
Weak and Unsafe Interface References in Delphi 10.1 Berlin
What if the object has a standard reference count implementation and you want to create an interface reference that is kept out of the total count of references? You can now achieve this by adding the [unsafe] attribute to the interface variable declaration, changing the code above to:
procedure TForm3.Button2Click(Sender: TObject);
var
[unsafe]
one: ISimpleInterface;
begin
one := TObjectOne.Create;
one.DoSomething;
end;
Not that this is a good idea, as the code above would cause a memory leak. By disabling the reference counting, when the variable goes out of scope nothing happens. There are some scenarios in which this is beneficial, as you can still use interfaces and not trigger the extra reference. In other words, an unsafe reference is treated just like... a pointer, with no extra compiler support.
Now before you consider using the unsafe attribute for having a reference without increasing the count, consider that in most cases there is another better option, that is the use of weak references. Weak references also avoid increasing the reference count, but they are managed. This means that the system keeps track of weak references, and in case the actual object gets deleted, it will set the weak reference to nil. With an unsafe reference, instead, you have no way to know the status of the target object (a scenario called dangling reference).
I am wondering if it is possible to define property of type function/procedure pointer in Delphi 2010 RIDL editor, for an interface definition, such that when I create an instance of its implementor using its CoCreator, I may then assign a function/procedure in my source that uses that interface definition, to that property. Actually I want to know how to fill "???" on the following.
TLB file:
IComIntf = interface(IDispatch)
...
function Get_OnDoSomething : ??? safecall;
procedure Set_OnDoSomething(const New_Event : ???); safecall;
...
property OnDoSomething : ???;
...
implementation
uses ComObj;
class function CoComIntf.Create: IComInt;
...
begin
Result := CreateComObject(CLASS_ComIntf) as IComIntf;
end;
implementation file, ComIntfUnit.pas:
type
TOnDoSomething = function (Info: OleVariant): HResult of object;
TComIntf = class(TAutoObject, IComIntf)
private
fOnDoSomething : TDoSomething;
...
public
property OnDoSomething: TOnDoSomething read fOnDoSomething write fOnDoSomething;
...
Client form:
uses ComIntfUnit;
type
TForm1 = class(TForm)
private
{ Private declarations }
fCom : IComIntf;
function DoSomething(Info: OleVariant): HResult;
public
{ Public declarations }
...
end;
...
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
fCom := CoComIntf.Create;
fCom.OnDoSomething := DoSomething;
...
Thank you in advance.
Raw function pointers are not COM compatible types. You could certainly use a void pointer if you wished but that wouldn't really be in the spirit of COM.
What you are expected to do here is to pass another interface. Then the callee can call a function on that interface and have the caller's code be executed.
You are going about this the wrong way. The correct way to implement this is to define a second interface that the client implements and passes to your COM object. The COM object can then hold on to that interface and call methods on it when needed. This is most commonly implemented using Connection Points for event interfaces.
An easy way to implement this, especially since you are using IDispatch, is to create a new Automation Object using the IDE wizard, and be sure to click on the "Generate Event support code" checkbox in the wizard dialog. The wizard will then generate 2 interfaces, one for the object itself, and one for the object's events, and implement the connection point logic for you. You can then add methods to the 2 interfaces as needed, where the object methods call the event methods when needed.
If you then compile, register, and import the final object into the IDE and generate a VCL wrapper for it, the resulting component will have VCL-style events on it that are internally hooked up to the object's connection point events.
I'm trying to use a Form from another Unit, but the code isn't recognizing the other Unit.
Example:
unit uImpressao;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, uniGUITypes, uniGUIAbstractClasses,
uniGUIClasses, uniGUIForm, uniGUIBaseClasses, uniPanel, uniURLFrame;
type
TfImpressao = class(TUniForm)
ufRelatorio: TUniURLFrame;
UniImage1: TUniImage;
procedure UniImage1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
uses
MainModule, Main, uBancoHoras;
procedure TfImpressao.UniImage1Click(Sender: TObject);
begin
fBh.iTeste.Visible := false;
end;
end.
unit uBancoHoras;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, uniGUITypes, uniGUIAbstractClasses,
uniGUIClasses, uniGUIForm, uniLabel, pngimage, uniImage, uniGUIBaseClasses,
uniPanel, uniPageControl, uniButton, uniBitBtn, uniSpeedButton, uniCanvas,
uniDateTimePicker, uniMemo, uniMultiItem, uniComboBox, uniEdit, uniBasicGrid,
uniDBGrid, uniDBMemo, uniRadioButton, uniDBText, uniRadioGroup, frxClass,
frxDBSet;
type
TfBH = class(TUniForm)
iTeste : TUniImage;
private
{ Private declarations }
public
{ Public declarations }
end;
var
url: string;
function fBH: TfBH;
implementation
{$R *.dfm}
uses
MainModule, Main, uImpressao;
function fBH: TfBH;
begin
Result := TfBH(MM.GetFormInstance(TfBH));
end;
procedure TfBH.iTesteClick(Sender: TObject);
begin
fImpressao.ShowModal;
end;
When I try using the uImpressao unit in uBancohoras unit, returns the error "Undeclared identifier 'fImpressao'".
Using uBancoHoras unit in the uImpressao unit, works fine.
I don't understand why this error is happening with one unit, but not with the other.
I hope you can help me!
Note: Please note that this answer was based on the original code posted in the question, which was subsequently replaced in its entirety with new and vastly different code.
fBH is not declared in unit A,so fBH.iTeste.Visible := True; can't possibly work; there is no such variable. You deleted the global variable fBH that the IDE creates for you (although, interestingly you left the var statement above it right above the implementation keyword).
Either add back in the declaration (by adding var fBH: TfBH; between the end of the class declaration and the implementation keyword), or create an instance of the form in unit B when you need to use it and access it through the local variable from there.
(Whichever direction you go, you never address a form using the variable from within that form's methods; use Self instead. Don't use fBH.ShowModal; use either Self.ShowModal or ShowModal instead.)
In uBancoHoras you have defined
function fBH: TfBH;
...
implementation
...
function fBH: TfBH;
begin
Result := TfBH(MM.GetFormInstance(TfBH));
end;
So you have defined a global function called fBH that returns an instance of the TfBH form class, seemingly through some sort of factory method (probably defined in MainModule?).
There is no corresponding method or variable in uImpressao with the name fImpressao, however - the the compiler error that fImpressao is an undeclared identifier.
Assuming that MM.GetFormInstance is suited to the task, and also assuming you wish to keep this design pattern, you would have to define (in uImpressao)something like :
function fImpressao: TfImpressao;
...
implementation
...
function fImpressao : TfImpressao;
begin
Result := TfImpressao(MM.GetFormInstance(TfImpressao));
end;
We can't see the implementation details of MM.GetFormInstance, however, so there is no guarantee that this will work - it only follows the pattern that fBH has set. Agreed with Ken that you should perhaps consider a better way to manage your forms. Resorting to global variables or global methods that reach across units to dig up a class instance feels like a headache waiting to happen...