I'm reading the Nick Hodges' book "Coding in Delphi" and I'm trying to understand the interface usage.
In a unit I've put asimple interface:
unit INameInterface;
interface
type
IName = interface
['{CE5E1B61-6F44-472B-AE9E-54FF1CAE0D70}']
function FirstName: string;
function LastName: string;
end;
implementation
end.
and in another unit I've put the implementation of this interface, according with the book sample:
unit INameImplementation;
interface
uses
INameInterface;
type
TPerson = class(TInterfacedObject, IName)
protected
function FirstName: string;
function LastName: string;
end;
implementation
{ TPerson }
function TPerson.FirstName: string;
begin
Result := 'Fred';
end;
function TPerson.LastName: string;
begin
Result := 'Flinstone';
end;
end.
At this point I've created a simple VCL form application in order to use the object I've created. The form code is this:
unit main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, INameImplementation;
type
TfrmMain = class(TForm)
lblFirtName: TLabel;
lblLastName: TLabel;
txtFirstName: TStaticText;
txtLastName: TStaticText;
btnGetName: TButton;
procedure btnGetNameClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
Person: TPerson;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.FormCreate(Sender: TObject);
begin
txtFirstName.Caption := '';
txtLastName.Caption := '';
end;
procedure TfrmMain.btnGetNameClick(Sender: TObject);
begin
txtFirstName.Caption := ...
end;
end.
My question is this: how can I use the interface? The two funcions are declared as protected so how can I access them from the form? I've to define them as public, or should I use the INameInterface interface unit?
I'm terribly confused about interfaces!!!
Eros
Essentially there are three things for you to know, beyond what you have already demonstrated understanding.
1. How to call methods of an interface
If you have a reference to an interface, then you can call methods just as you would on a class reference:
var
Name: IName;
....
Writeln(Name.FirstName);
Writeln(Name.LastName);
2. How to obtain interface references
Typically you do this by instantiating a class that implements the interface you wish to use:
var
Name: IName;
....
Name := TPerson.Create;
// now you can use Name as before
There are other ways to obtain interface references, but let's leave those to one side for now.
3. How to pass around interfaces
You might not wish to create a new object every time you need to use an interface. So you can get other parties to pass you the interface to use. For instance interfaces can be passed as method parameters:
procedure Foo(Name: IName);
begin
// use Name as before
end;
You can obtain interface references via function calls and properties, etc.
The two functions are declared as protected so how can I access them from the form?
Well, they are declared protected in the implementing object. But you are not going to access them via the implementing object. You will access them via the interface. Which means that the visibility in the implementing object is not relevant from the perspective of the interface.
Your form unit references INameImplementation which is needed to create the object that implements the interface. You'll also need to use INameInterface so that your code can see the interface itself.
This example isn't very powerful yet because you can still see the implementing object's type. But imagine if that was hidden from you and all you could see was a function that returned an IName. It's when you reach this point that interfaces can achieve their potential.
Related
I have met a really weird behaviour using Delphi and DLL.
I want to learn how to create and use DLL's. To do so, I created two files: one containing the DLL, and the other one calling it.
The DLL retrieves informations from a *.ini file (the GetInfos function). When calling it in a form, I get the following error: "Access violation at address XXXXXXXX in "dlltest.dll" module. Read of address 00000000.
I wanted to try some other things, so I created a simple procedure, that just display a message.
The interesting thing is that when I call this procedure (which only purpose is to display a message), I no longer get the Access violation error.
I really do not understand how it can make my code work. I'd really like if someone could give me some explanation. I am using Delphi 10.3 Rio Version 26.0.36039.7899
Here is the code I produced :
dlltest:
library dlltest;
uses
System.SysUtils, System.Classes, System.IniFiles, Winapi.Windows, vcl.dialogs;
var
// Ini file var
iniConfig: TInifile;
procedure SayHello;
var
hello: PChar;
begin
hello := 'Hello world!';
ShowMessage(hello);
end;
// -----------------------------------------------------------------------------
// Retrieving .ini file
function GetIni: TInifile;
begin
result := TInifile.Create('.\monitoring.ini');
end;
// -----------------------------------------------------------------------------
function GetInfos: ShortString;
begin
iniConfig := GetIni;
try
result := iniConfig.ReadString('filename', 'start', 'start');
finally
iniConfig.Free;
end; { try }
end; { function GetInfos }
exports
SayHello, GetInfos;
begin
end.
Using dll:
unit testUtilisationDll;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils,
System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms,
Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
Form1: TForm1;
procedure SayHello; StdCall; external 'dlltest.dll';
function GetInfos: ShortString; StdCall;
external 'dlltest.dll';
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
SayHello;
ShowMessage(GetInfos(self));
end;
end.
When you export the functions, they are using the default calling convention (Register), whereas when you import them, you are telling the import that they're using StdCall calling convention.
The normal case for DLL exported functions is StdCall convention, so you should declare your functions to use this calling convention in your .DLL source:
function GetInfos: ShortString; stdcall;
and
procedure SayHello; stdcall;
Another problem is that when you call GetInfos:
ShowMessage(GetInfos(self));
you are passing it a parameter (self), but your declaration of the import:
function GetInfos: ShortString; StdCall; external 'dlltest.dll';
doesn't list any parameters. You won't be able to compile the program as it is shown in your question...
I am creating a game using delphi and want to move some of my code to a separate unit, however this code uses attributes from a form. Is this possible?
I am creating a game using a VCL form application and currently have all my code for the game algorithm in form unit. There is nothing wrong with this, as in my program runs well, except it looks messy and I have been advised to put the algorithm code in a separate unit. I have tried moving the code into a new unit, however whatever I try syntax errors appear.
This is code in my main unit where Grid is TStringGrid from the form and GridSize is a procedure from my attempted second unit:
procedure TGame.NewGame;
begin
Grid.Width:=GridSize(Grid.ColCount);
Grid.Height:=GridSize(Grid.RowCount);
end;
This is the second unit code:
unit UGameGenerator;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.Menus,
Vcl.StdCtrls;
implementation
function GridSize(size: integer): integer;
begin
result:=291+36*(size-8);
end;
end.
EDIT:
This is code from the second unit:
procedure ClearGrid;
var
i,j: integer;
begin
for i := 0 to Grid.ColCount-1 do
begin
for j := 0 to Grid.RowCount-1 do
begin
Grid.Cells[i,j]:='';
end;
end;
end;
The compiler needs to find the declaration of GridSize somehow. To do that, follow this guide:
In the main form, add UGameGenerator to the uses list:
unit MainForm;
interface
uses
...,UGameGenerator; // Add here or in the implementation section
...
implementation
...
end.
In your UGameGenerator unit, expose all types/functions/procedures that is used in other program parts in the interface:
unit UGameGenerator;
interface
uses
...,...;
function GridSize(size: integer): integer;
implementation
function GridSize(size: integer): integer;
begin
result:=291+36*(size-8);
end;
end.
A tip when designing the separate unit, avoid using variables directly from other units. Instead pass them as parameters in procedure/function calls.
Otherwise you risk to have much trouble with circular references.
In your updated question, declare procedure ClearGrid( aGrid : TStringGrid); and pass the grid as a parameter.
Is it real?
For example, I have form1 and button1. I can assign to button1.onClick in design time onlyButton26Click.
TForm1 = class(TForm)
procedure Button26Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
I also have unit 2 like :
unit ProcAndFunc;
interface
uses sysutils,forms;
procedure createArrTable(Sender: TObject);
Can I assign createArrTable to button1.onClick in design time?
Thanks.
Ok. Unit1
TForm1 = class(TForm)
myButton1 : TButton;
procedure Button26Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Unit2
TForm2 = class(TForm)
myButton2 : TButton;
procedure ButtonClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Just to know how it works. What should I change in unit2 that I can assign ButtonClick to myButton1.OnClick
How to make a separate unit for event methods, which IDE allows me to assign to component events at design time? It seems that I duplicate question. But that answer does not suit me. Is there the other way?
EDIT3 (08.08.2016):
I created this TDataModule;
unit Unit3;
interface
uses
System.SysUtils, System.Classes,dialogs;
type
TDataModule3 = class(TDataModule)
procedure Click2(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
DataModule3: TDataModule3;
implementation
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TDataModule3.Click2(Sender: TObject);
begin
ShowMessage('hello world');
end;
end.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, unit3;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage('hello world');
end;
end.
And I still cant assign DataModule3.Click2 to button1.onclick in design-time. (screenshot). What do I also need to do?
Can I assign createArrTable to button1.onClick in design time?
Not at design-time, no. createArrTable() is not a member of a class, and thus is not accessible via an object pointer at run-time. So it does not satisfy the signature that is normally required for an OnClick event handler. The IDE will not allow you to assign it at design-time.
However, you can assign createArrTable() at run-time, with some minor tweaking.
The OnClick event (and most RTL/VCL events in general) is implemented using a closure, which consists of 2 pointers - a pointer to the procedure code itself, and a pointer to an object.
When the event is fired at runtime, the compiled code extracts the object pointer from the closure and calls the procedure with that object passed to the procedure's Self parameter. It is the lack of a Self parameter that prevents your current createArrTable() implementation from being used as an OnClick event handler. If you were to somehow assign it as-is, the parameter list will get corrupted at runtime.
However, if you add an explicit Self parameter to createArrTable(), you can avoid that issue, and skip the class object requirement:
procedure createArrTable(Self: Pointer; Sender: TObject);
The button will still try to pass an object pointer from the closure to Self when calling the procedue, but now you have made space for an object pointer to be passed correctly and not corrupt other parameters. The object pointer itself becomes irrelevant and can be set to anything you want it to be, even nil.
However, since createArrTable() is still not a member of a class, you cannot assign it directly to the OnClick event, but you can use a TMethod variable to facilitate the assignment, eg:
var
M: TMethod;
begin
M.Code := #createArrTable;
M.Data := WhateverYouWantSelfToBe;
Button1.OnClick := TNotifyEvent(M);
end;
Now createArrTable() will work as expected when OnClick is fired at run-time.
What should I change in unit2 that I can assign ButtonClick to myButton1.OnClick
Nothing. It is already a suitable event handler as-is. All you would have to do is add unit2 to the uses clause of the interface section in unit1 so the IDE can see ButtonClick() at design-time, then you would be able to assign Form2.ButtonClick to myButton1.OnClick. Just know that you will have to setup your project to create Form2 before Form1 at runtime, otherwise you will likely crash the code when the DFM streaming system tries to access ButtonClick via an invalid Form2 object when assigning the value of Form1.myButton.OnClick.
To avoid having to change the creation order of the Forms, you can place the event handlers into a separate TDataModule instead, and then link its methods to events on your Form. Just make sure the TDataModule is created at runtime before the Form is created:
Access DataModule's event from another Form (delphi design-time)
You cannot ever assign that procedure as an event handler since it is not a method of a class. So you could make the procedure be a method of another class and make sure that there was a global instance of that class visible to the designer.
However that would be a very poor solution in my view. It is best practice to decouple UI logic from application logic where possible. The clean solution is to simply call the procedure from an event handler. That means that your procedure is still available to other parties in its current form and does not need to be shoe-horned into looking like an event handler. You could also likely remove the Sender parameter which I suspect you are not using in any case.
I am thinking about the New and Dispose commands from Delphi and was wondering if I can use these commands in other procedures/functions/threads, etc. within my process.
I would like to store the address to a TList but I am a little insecure since it uses the var reference which could be used to 'Save' the vars actual address. I don't want any access violations or anything...
Here is my code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
MyTList : TList;
public
{ Public declarations }
end;
var
Form1 : TForm1;
type
TMyStruct = record
Int1 : Integer;
Int2 : Integer;
Str1 : String;
Str2 : String;
end;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
P_TMyStruct : ^TMyStruct;
I : Integer;
begin
for I := 1 to 3 do begin
New (P_TMyStruct);
P_TMyStruct^.Int1 := I;
P_TMyStruct^.Int2 := 1337;
P_TMyStruct^.Str1 := inttostr(I);
P_TMyStruct^.Str2 := '1337';
MyTList.Add(P_TMyStruct);
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
I : Integer;
begin
for I := 0 to MyTList.Count - 1 do begin
ShowMessage(inttostr(TMyStruct(MyTList.Items[i]^).Int1));
ShowMessage(inttostr(TMyStruct(MyTList.Items[i]^).Int1));
ShowMessage(TMyStruct(MyTList.Items[i]^).Str1);
ShowMessage(TMyStruct(MyTList.Items[i]^).Str2);
Dispose(MyTList.Items[i]);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyTList := TList.Create;
end;
end.
So would that be safe since I'm not disposing it in the stack?
Dynamic memory allocations in Delphi will return memory on the heap and not the stack. (See Marco Cantu's explanation of these terms.) As such, the memory will be "globally accessible" in your application provided a reference to that memory is available. The New() procedure is one way to dynamically allocate memory. (Some others are: GetMem(), string assignments, object creation.)
So what you're proposing is safe (in terms of not causing access violations).
However, it will leak memory because the way you are using it, Dispose() will not deallocate all memory.
When you assign a string value to the struct, more memory is allocated on the heap. When you later Dispose your struct, the exact memory allocated in New() is deallocated, but at the low level, (simple Pointer reference to the memory), Delphi has no knowledge that there may be other internal structures that need deallocation; so the strings are leaked.
Fixing the memory leak
What you need to do is cast the pointer returned by MyTList.Items[i] to its correct type. But first you need to explicitly declare a type for the pointer to your struct. As a standard convention, most Delphi programmers will declare the pointer type with the same name, replacing the T prefix with P. I.e.
PMyStruct = ^TMyStruct;
TMyStruct = record
Int1 : Integer;
Int2 : Integer;
Str1 : String;
Str2 : String;
end;
Then when you do the following: Dispose(PMyStruct(MyTList.Items[i])), the compiler "recongnises" the type the pointer is referring to and will use that information to perform additional actions for its managed types. The point is the compiler can automatically handle the managed types correctly - but only if you give it the correct information about the struct that contains them.
The types affected by this are:
strings
dynamic arrays
interface references (which will need a ref released)
Also any indirect references to the above managed types will be managed. E.g.
If a Variant or OleVariant references and interface, it will be managed.
If a child record within a record (struct) references managed types, they will be managed.
arrays of strings, arrays of interfaces... each string/interface entry will also be managed.
Given that there are many more possible permutations to the above, it is safer to always ensure that Dispose() knows the type that was used in the initial New() allocation.
Non Managed Types
Perhaps given the above discussion, I should make a specific qualification about types that are not managed types. One can infer (and it would be accurate) that non managed types are not automatically deallocated when the containing structure is Dispose()d.
E.g. If your record structure contains an object reference, then because an object reference is not a managed type, you would still have to explicitly control the lifetime of that object instance. (Fortunately there are many techniques to do so.)
Let's say I have two forms in a delphi project, I want to be able to access form1's variables from form2. Is there anyone to declare, say a 'public' variable in form1 which can be read from all forms?
I have tried putting a variable in the public declaration
{ private declarations }
public
{ public declarations }
test: integer;
end;
and in form 2 i have
unit Unit2;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, unit1;
type
{ TForm2 }
TForm2 = class(TForm)
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.lfm}
{ TForm2 }
procedure TForm2.FormCreate(Sender: TObject);
begin
form1 //<---------- DOES NOT GET RECOGNIZED
end;
end.
I then put 'Unit1' into the uses section on Form2, but it seems I can't do that due to a circular reference. I'd like to refrain from using pointers if possible.
First, it's better to pretend that globals don't exist at all. If you start out programming with the crutch of global variables, you'll just avoid learning the very simple techniques that allow you to do without them. You're worrying about using pointers (which actually wouldn't help your problem at all), but not concerned about using globals which are actually more dangerous.
Globals are dangerous because:
They provide a link between the units that share them (a concept called tight coupling)
This link is hidden in the sense that it's not obvious the exact nature of the link without examining the detail of the code.
Your code will become littered with side effects, which is to say that when you do something in a method, other unexpected things happen as well. This increases the possibility and complexity of subtle bugs.
The cumulative effect of the above points is that even if you break a project down into 20 units, your brain still has to think about all 20 units at the same time - as if they were only1 unit. This defeats the purpose of breaking your project down into smaller manageable units in the first place.
You'll quickly find that even medium sized projects start becoming very difficult to maintain.
Circular References
Please take a look at my answer to a previous question for thoughts on dealing with circular references more generally.
In your case, you simply need to move Unit1 from the interface uses to the implementation uses.
var
Form2: TForm2;
implementation
uses
Unit1;
{$R *.lfm}
{ TForm2 }
procedure TForm2.FormCreate(Sender: TObject);
begin
Form1.test; //Is now accessible, but remember: it will cause a runtime error if Form1 hasn't been created or has already been destroyed.
end;
Sharing data without globals
You'll notice that technically you're still using globals; albeit ones created by Delphi and not your own. Here's a technique you can use to share data in a more controlled fashion.
unit Unit3;
interface
type
TSharedData = class(TObject)
public
Test1: Integer;
Test2: Integer;
end;
Then in Form1 you add the following:
implementation
uses
...
Unit3;
type
TForm1 = class(TForm)
...
public
SharedData: TSharedData;
end;
//Inside either the constructor or OnCreate event add the following line:
SharedData := TSharedData.Create;
//Inside either the destructor or OnDestroyevent add the following line:
SharedData.Free;
Then in Form2 you do something slightly different because you want to use Form1's shared data not its own "shared data".
implementation
uses
...
Unit3;
type
TForm2 = class(TForm)
...
public
Form1SharedData: TSharedData;
end;
//Nothing added to constructor/destructor or OnCreate/OnDestroy events
Finally, after creating both forms, you give Form2 a refernce to Form1's shared data:
procedure RunApplicationWithoutFormGlobals;
var
LForm1: TForm1;
LForm2: TForm2;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, LForm1);
Application.CreateForm(TForm2, LForm2);
LForm2.Form1SharedData := LForm1.SharedData;
Application.Run;
end;
The above illustrates how easily you can do away with even Delphi's global variables.
Disclaimer: Some of the code goes agaisnt generally accepted encapsulation principles, but is for illustrative purposes only.
First, if you must use globals (it's probably better not to use globals, as Craig has wisely pointed out) then you should put the globals you want to share in SharedGlobals.pas:
unit SharedGlobals;
interface
var
{variables here}
Something:Integer;
implementation
{ nothing here?}
Now use that unit, from the two units you want to share access to that variable in. Alternatively, have both reference another object, which is named something sensible, and have that object be designed, as the holder of state (variable values) that those two instances (forms or classes, or whatever) need to share.
Second idea, since your two units that you have already have dependencies on each other, you could also get around your circular dependency by using the unit that would create a circular dependency, from the implementation section instead of the interface:
unit Unit2;
interface
/// stuff
implementation
uses Unit1;
...
unit Unit1;
interface
/// stuff
implementation
uses Unit2;
the example shows Form1(main) and Form2(other) which has better use for organization;
FORM1 interface declaration only;
can be read from all forms;
interface
procedure oncreate(Sender: TObject);
implementation
uses Form2;
procedure TForm1.oncreate(Sender: TObject);
begin
Form2.test1;
//{Form2.test2} are not visible to Form1;
end;
FORM2 interface and implementation declarations;
implementation declaration are local to the unit; cannot be read from all forms;
interface
procedure test1;
implementation
procedure test2; //declared under implementation;
procedure TForm2.test1;
begin
ShowMessage('form2 test1 message');
end;
procedure TForm2.test2;
begin
ShowMessage('form2 test2 message');
end;
any procedure, function, type, variable can be local to the unit under implementation;
it also makes intellisense(Ctrl+Space) clean from declarations used only to the unit;