Delphi iOS - Detect KeyDown of Done button - ios

How can I detect the press of the "Done" button on the virtual keyboard in Delphi when using iOS? Button in the upper right corner that closed virtual keyboard.

As per my comment in your question, you'd need to modify the FMX.VirtualKeyboard.iOS unit. These steps should have you most of the way to your solution:
Create a unit to define a TMessage descendant, e.g:
unit VirtualKeyboardMessages;
interface
uses
System.Messaging;
type
TVirtualKeyboardDoneClickedMessage = class(TMessage);
implementation
end.
Make a copy of FMX.VirtualKeyboard.iOS and save it in your project folder. Modify the unit to include the unit above, e.g.:
implementation
uses
System.Classes, System.SysUtils, System.TypInfo, System.Generics.Collections, System.UITypes, System.Types,
System.Messaging, System.Math, Macapi.ObjectiveC, Macapi.ObjCRuntime, Macapi.Helpers,
iOSapi.CocoaTypes, iOSapi.Foundation, iOSapi.UIKit, iOSapi.CoreGraphics,
FMX.Types, FMX.VirtualKeyboard, FMX.Platform, FMX.Forms, FMX.Platform.iOS, FMX.Consts, FMX.Helpers.iOS,
// Add this to the uses clause
VirtualKeyboardMessages;
Add a DoneButtonClicked method to the IKeyboardEvents interface and TKeyboardEventHandler class:
IKeyboardEvents = interface(NSObject)
['{72D3A7FD-DDE3-473D-9750-46C072E7B3B7}']
// code snipped for brevity, and to avoid copyright issues
// Add this method
procedure DoneButtonClicked; cdecl;
end;
TKeyboardEventHandler = class(TOCLocal)
strict private type
TKeyboardState = (Shown, Hidden);
private
FKeepFocus: Boolean;
// code snipped for brevity, and to avoid copyright issues
// Add this method
procedure DoneButtonClicked; cdecl;
end;
procedure TKeyboardEventHandler.DoneButtonClicked;
begin
HideVirtualKeyboard;
TMessageManager.DefaultManager.SendMessage(Self, TVirtualKeyboardDoneClickedMessage.Create);
end;
Modify the RefreshToolbarButtons method:
procedure TCocoaVirtualKeyboardService.RefreshToolbarButtons;
var
I: Integer;
B: UIBarButtonItem;
AutoReleasePool: NSAutoReleasePool;
begin
// code snipped for brevity, and to avoid copyright issues
//Hide button
if FHideButton = nil then
begin
FHideButton := TUIBarButtonItem.Create;
FHideButton.setTitle(StrToNSStr(SEditorDone));
FHideButton.setStyle(UIBarButtonItemStyleDone);
FHideButton.setTarget(FKeyboardHandler.GetObjectID);
// Following line commented out from original code:
// FHideButton.setAction(sel_getUid('HideVirtualKeyboard'));
// Following line added:
FHideButton.setAction(sel_getUid('DoneButtonClicked'));
end;
// code snipped for brevity, and to avoid copyright issues
end;
Then you will need to subscribe to the TVirtualKeyboardDoneClickedMessage in your form, which will need to use the VirtualKeyboardMessages unit, above. I'll leave that as an exercise for you.

uses VirtualKeyboardMessages;
type
TMainForm = class(TForm)
{...}
private
{ Private declarations }
MessageManager: TMessageManager;
SubscriptionId: Integer;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
MessageManager := TMessageManager.DefaultManager;
SubscriptionId := MessageManager.SubscribeToMessage( TVirtualKeyboardDoneClickedMessage, procedure(const Sender: TObject; const M: TMessage)
begin
ShowMessage('Done');
end);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
TMessageManager.DefaultManager.Unsubscribe(TVirtualKeyboardDoneClickedMessage, SubscriptionId);
end;
// Currently is good solution if you have only one TEdit on the form. But if you have multiple then is problem . And if you using visual dialog in this case "Showmessage" then virtual keyboard not hiding.

Related

How do I get the MessageDlgPos dimensions?

I want to position a MessageBox in a particular position with respect to the active cell in a string grid and this is no problem using MessageDlgPos() except that I want to prevent the box running off the right or bottom of the screen when the active cell is close to the right or bottom. What I need for this is a way of getting the dimensions of the box but I cannot see a simple way of getting these. Anyone know how without creating my own box?
The MessageDlg...() functions do not support what you are asking for. The dimensions of the dialog are not known until the dialog is being displayed, and you have no way to access the dialog window directly to query/re-position it, except maybe with a WH_CBT hook from SetWindowsHookEx().
That being said...
On Windows Vista+ with Vcl.Dialogs.UseLatestCommonDialogs=true and Visual Styles enabled, the MessageDlg...() functions call the Win32 TaskDialogIndirect() API to display a message box. You have no control over that dialog's dimensions, so you would have to wait for that dialog to issue a TDN_DIALOG_CONSTRUCTED notification to then query its actual dimensions before it is displayed, so you can then adjust its position as needed. However, the MessageDlg...() functions do not provide access to any of TaskDialogIndirect()'s notifications (TCustomTaskDialog, which is used internally, does have an OnDialogConstructed event, amongst other events). So, if you wanted to reposition this dialog, you would have to call TaskDialogIndirect() yourself with a custom callback function (or, use the VCL's TTaskDialog wrapper).
On pre-Vista, or with UseLatestCommonDialogs=false or Visual Styles disabled, the MessageDlg...() functions display a custom VCL TForm via Vcl.Dialogs.CreateMessageDialog() instead, which you can call directly, and then pretty much query, manipulate, and show the returned TForm however you want. Just be sure to Free() it when you are done using it.
You could use an actual TTaskDialog. You can create you own version of it, add a TaskDialogConstructed procedure and get the dimension in the TaskDialogConstructed procedure. Something along the lines of the following.
type
TTaskDialog = class(Vcl.Dialogs.TTaskDialog)
protected
procedure TaskDialogConstructed(Sender: TObject);
end;
procedure TTaskDialog.TaskDialogConstructed(Sender: TObject);
var
TaskDialog: TTaskDialog;
R: TRect;
begin
TaskDialog := Sender as TTaskDialog;
Win32Check(GetWindowRect(TaskDialog.Handle, R));
{... Do whatever with R ...}
end;
function ExecuteTaskDialog(AOwner: TComponent; ATitle, AText: string; ACommonButtons: TTaskDialogCommonButtons = [tcbOK]): integer;
var
TaskDialog: TTaskDialog;
begin
TaskDialog := TTaskDialog.Create(AOwner);
with TaskDialog do
begin
Caption := Application.Title;
Title := ATitle;
Text := AText;
MainIcon := tdiNone;
Flags := Flags + [tfUseHiconMain];
CommonButtons := ACommonButtons;
CustomMainIcon.LoadFromResourceName(HInstance, 'MAINICON');
OnDialogConstructed := TaskDialogConstructed;
Execute;
Result := ModalResult;
Free;
end;
end;
Create the MessageDlg yourself. Add an OnActivate or OnShow event. In this method, ask / change the properties of the dialog.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.StdCtrls;
type
Tfrm = class(TForm)
btn: TButton;
procedure btnClick(Sender: TObject);
private
procedure OnDlgActivate(Sender: TObject);
public
{ Public-Deklarationen }
end;
var
frm: Tfrm;
implementation
uses
Vcl.Dialogs, System.TypInfo;
{$R *.dfm}
procedure Tfrm.btnClick(Sender: TObject);
var
Ldlg : TForm;
LiRet : integer;
begin
Ldlg := CreateMessageDialog('Hallo World!', mtInformation,mbYesNo, mbYes);
try
Ldlg.OnActivate := OnDlgActivate;
LiRet := Ldlg.ShowModal;
finally
Ldlg.free;
end;
end;
procedure Tfrm.OnDlgActivate(Sender: TObject);
var
Lfrm: TForm;
LcTxt: string;
begin
Lfrm := Sender as TForm;
LcTxt := Format('%s %sLeft: %d / Top: %d', [Lfrm.ClassName, sLineBreak, Lfrm.Left, Lfrm.Top]);
ShowMessage(LcTxt);
end;
end.

Delphi: Method resolution clauses not being applied when getting/setting properties through RTTI

I am trying to add functionality to existing code, by using method resolution clause for various interface properties.
While this works fine, when getting / setting properties in code, this fails when trying to set or get the properties through RTTI.
When using RTTI, the 1st implementing class method is being called.
Here is code which shows the problem:
program TestIntfMethodResolutions;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Variants,
System.Classes,
TypInfo,
RTTI{,
uRTTIHelper};
type
ITestInterface = interface
['{61553B5F-574A-4B0F-AB6F-0560E324B463}']
function GetA:integer;
procedure SetA(const AVal:integer);
property A:integer read GetA write SetA;
end;
{$RTTI EXPLICIT METHODS([vcPrivate,vcPublic])}
TTintimpl1 = class(TInterfacedObject,ITestInterface)
private
FA:integer;
function GetA: integer;
procedure SetA(const Value: integer);virtual; // Does not need to be virtual
public
property A:integer read GetA write SetA;
end;
{$RTTI EXPLICIT METHODS([vcPrivate,vcPublic])}
//Explicit RTTI settings causes the Private methods to show up in the method list
TIntimpl2 = class(TTIntimpl1,ITestInterface)
private
procedure MySetA(const Value:integer);virtual;
public
procedure ITestInterface.SetA = MySetA;
end;
TMain = class
private
{ Private declarations }
public
{ Public declarations }
procedure FormCreate;
end;
var
Form5: TMain;
procedure TMain.FormCreate;
var
ctx:TRttiContext;
avalue,bvalue:tvalue;
atype,bastyp:TRttiType;
aproplist:Tarray<TRttiProperty>;
amethlist:Tarray<TRttiMethod>;
isinst:boolean;
aninst:TRttiInstanceType;
anintf:TRttiInterfaceType;
intflist:Tarray<TRttiInterfaceType>;
inst:pointer;
anint:ITestInterface;
aprop:TRttiProperty;
codeptr:pointer;
asetmeth:TRTTIMethod;
begin
ctx:=TRttiContext.Create;
//Faxisloopthr:=TIntimpl2.Create;
anint:=TIntimpl2.Create;
avalue:=anint as TObject;
atype:=ctx.GetType(avalue.TypeInfo);
if atype.IsInstance then
begin
aninst:=atype.AsInstance;
aproplist:=aninst.GetProperties;
amethlist:=aninst.GetMethods;
bvalue:=TValue.FromOrdinal(aproplist[0].PropertyType.Handle,1);
inst:=avalue.AsObject;
aprop:=aproplist[0]; //I could have called aproplist[0].SetValue(...
aprop.SetValue(inst,bvalue);
end;
writeln('RTTI result '+anint.A.ToString); //Should give me 20 but I get 10 everytime
//asetmeth:=aprop.SetterMethod(inst); // returns SetA and not MySetA - need uRTTIhelper unit. https://github.com/RRUZ/blog/tree/master/RTTI
// setpropvalue(inst,aprop.PropInfo,bvalue.AsVariant); // calls SetA and not MySetA
//Manually setting the value calls the correct method
anint.A:=1;
writeln('Direct setting '+anint.A.ToString);
end;
{ T2ndIntf }
{ TTintimpl1 }
function TTintimpl1.GetA: integer;
begin
Result:=FA;
end;
procedure TTintimpl1.SetA(const Value: integer);
var
a:integer;
begin
FA:=Value*10;
writeln('In SetA ',FA);
end;
{ TIntimpl2 }
//Should get called - but the 1st implementing parent gets called
procedure TIntimpl2.MySetA(const Value: integer);
begin
FA:=Value*20;
writeln('In MySetA ',FA);
end;
begin
Form5:=TMain.Create;
try
Form5.FormCreate;
finally
Form5.Free;
readln;
end;
end.
What am I doing wrong ?
Thanks,
IB.
Delphi 10.2 Tokyo Win64

How to avoid that a component is displayed as possible value of its own linkage property? [duplicate]

I'm trying to create a custom property editor for some custom component. The custom property editor is intended to edit some set property, like
type
TButtonOption = (boOption1, boOption2, boOption3);
TButtonOptions = set of TButtonOption;
my property editor descends from TSetProperty class. The problem is: my custom property editor doesn't get registered and Delphi IDE seems to use its own default set property editor, because ShowMessage() calls inside property editor methods never executes! I've created a sample package/component from scratch, as simple as possible, showing this issue. Here is the code:
unit Button1;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.StdCtrls, DesignIntf, DesignEditors;
type
TButtonOption = (boOption1, boOption2, boOption3);
TButtonOptions = set of TButtonOption;
TButtonEx = class(TButton)
private
FOptions: TButtonOptions;
function GetOptions: TButtonOptions;
procedure SetOptions(Value: TButtonOptions);
published
property Options: TButtonOptions read GetOptions write SetOptions default [];
end;
TMySetProperty = class(TSetProperty)
public
function GetAttributes: TPropertyAttributes; override;
procedure GetProperties(Proc: TGetPropProc); override;
function GetValue: string; override;
end;
procedure Register;
implementation
uses
Dialogs;
// TButtonEx - sample component
function TButtonEx.GetOptions: TButtonOptions;
begin
Result := FOptions;
end;
procedure TButtonEx.SetOptions(Value: TButtonOptions);
begin
if FOptions <> Value then
begin
FOptions := Value;
end;
end;
// register stuff
procedure Register;
begin
RegisterComponents('Samples', [TButtonEx]);
RegisterPropertyEditor(TypeInfo(TButtonOptions), nil, '', TMySetProperty);
end;
function TMySetProperty.GetAttributes: TPropertyAttributes;
begin
ShowMessage('GetAttributes');
Result := inherited GetAttributes;
end;
procedure TMySetProperty.GetProperties(Proc: TGetPropProc);
begin
ShowMessage('GetProperties');
inherited;
end;
function TMySetProperty.GetValue: string;
begin
ShowMessage('GetValue');
Result := inherited GetValue;
end;
end.
Please note that:
I'm registering the new property editor (TMySetProperty) for ALL components having a TButtonOptions property. I also tried to do it for TButtonEx only, but the result is the same.
I've added ShowMessage() calls inside all overriden methods of my custom property editor and those methods NEVER get called.
I've already debugged the package and RegisterPropertyEditor() executes. Nevertheless, my custom code in overridden methods never execute.
I've seen other 3rd party components using such property editor (TSetProperty descendants) running in older Delphi IDEs and I could not find any relevant difference in code. Maybe Delphi XE2+ requires something else?
So the question is:
Why my custom property editor does not register/work?
Note: This issue happens in Delphi XE2, XE3, XE4 and also XE5 at least. Other IDEs were not tested but probably have the same behavior.
Finally I got a solution... After testing everything I could imagine - without success - I started searching for something "new" in DesignEditors.pas and DesignIntf.pas units. Reading GetEditorClass() function, I discovered that it first checks for a PropertyMapper. A property mapper can be registered using RegisterPropertyMapper() function. Using it instead of RegisterPropertyEditor() works just as expected. Here is my modified, working code, also showing some interesting application for this: show or hide some options of my set-based property, based on some criteria:
unit Button1;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.StdCtrls,
DesignIntf, DesignEditors;
type
TButtonOption = (boOptionA, boOptionB, boOptionC);
TButtonOptions = set of TButtonOption;
type
TButtonEx = class(TButton)
private
FOptions: TButtonOptions;
function GetOptions: TButtonOptions;
procedure SetOptions(Value: TButtonOptions);
published
property Options: TButtonOptions read GetOptions write SetOptions default [];
end;
TMySetProperty = class(TSetProperty)
private
FProc: TGetPropProc;
procedure InternalGetProperty(const Prop: IProperty);
public
procedure GetProperties(Proc: TGetPropProc); override;
end;
procedure Register;
implementation
uses
TypInfo;
// TButtonEx - sample component
function TButtonEx.GetOptions: TButtonOptions;
begin
Result := FOptions;
end;
procedure TButtonEx.SetOptions(Value: TButtonOptions);
begin
if FOptions <> Value then
begin
FOptions := Value;
end;
end;
// Returns TMySetProperty as the property editor used for Options in TButtonEx class
function MyCustomPropMapper(Obj: TPersistent; PropInfo: PPropInfo): TPropertyEditorClass;
begin
Result := nil;
if Assigned(Obj) and (Obj is TButtonEx) and SameText(String(PropInfo.Name), 'Options') then begin
Result := TMySetProperty;
end;
end;
procedure Register;
begin
RegisterComponents('Samples', [TButtonEx]);
// RegisterPropertyEditor does not work for set-based properties.
// We use RegisterPropertyMapper instead
RegisterPropertyMapper(MyCustomPropMapper);
end;
procedure TMySetProperty.GetProperties(Proc: TGetPropProc);
begin
// Save the original method received
FProc := Proc;
// Call inherited, but passing our internal method as parameter
inherited GetProperties(InternalGetProperty);
end;
procedure TMySetProperty.InternalGetProperty(const Prop: IProperty);
var
i: Integer;
begin
if not Assigned(FProc) then begin // just in case
Exit;
end;
// Now the interesting stuff. I just want to show boOptionA and boOptionB in Object inspector
// So I call the original Proc in those cases only
// boOptionC still exists, but won't be visible in object inspector
for i := 0 to PropCount - 1 do begin
if SameText(Prop.GetName, 'boOptionA') or SameText(Prop.GetName, 'boOptionB') then begin
FProc(Prop); // call original method
end;
end;
end;
end.

Delphi 7 object undefinedat

I have a web service that I have created using Delphi and I want to connect to sql server with it so I have added to the project an ADO Connection and ADOQuery had both of them configured and ready to use, there was only a small problem, there are two units on my project and those objects were added to Unit1 and I am working with my ImplUnit whitch is another unit, and can`t find a way to reference or include one unit inside the other unit.
unit1
{ SOAP WebModule}
unit Unit1;
interface
uses
SysUtils, Classes, HTTPApp, InvokeRegistry, WSDLIntf, TypInfo,
WebServExp, WSDLBind, XMLSchema, WSDLPub, SOAPPasInv, SOAPHTTPPasInv,
SOAPHTTPDisp, WebBrokerSOAP, DB, ADODB;
type
TWebModule1 = class(TWebModule)
HTTPSoapDispatcher1: THTTPSoapDispatcher;
HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker;
WSDLHTMLPublish1: TWSDLHTMLPublish;
ADOConnection1: TADOConnection;
ADODataSet1: TADODataSet;
ADOQuery1: TADOQuery;
procedure WebModule1DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;
var
WebModule1: TWebModule1;
implementation
{$R *.dfm}
procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
WSDLHTMLPublish1.ServiceInfo(Sender, Request, Response, Handled);
end;
end.
My unit
unit UTImplementacao;
interface
uses
InvokeRegistry,DB, ADODB;
type
IInterface = interface(IInvokable)
['{EFF30FFA-DA0C-433A-832A-0BA057B55103}']
function ReceiveUser(username : String; password : String) :
Boolean; stdcall;
end;
TImplementacao = class(TInvokableClass, IInterface)
public
function ReceiveUser(username : String; password : String) :
Boolean; stdcall;
end;
implementation
{ TImplementacao }
function TImplementacao.ReceiveUser(username, password: String): Boolean;
var
ADOConnection1: TADOConnection;
ADOQuery1: TADOQuery;
begin
try
ADOConnection1 := TADOConnection.Create(nil);
ADOConnection1.LoginPrompt := False;
ADOConnection1.ConnectionString:= 'Provider=SQLOLEDB.1;Integrated Security=SSPI;' +
'Persist Security Info=False;' +
'User ID=Diego;'+
'Catalog=OnlineShopping;' +
'Data Source=DIEGO-PC\SQLEXPRESS'+
';Use Procedure for Prepare=1;' +
'Auto Translate=True;Packet Size=4096;'+
'Workstation ID=DIEGO-PC;'+
'Use Encryption for Data=False;'+
'Tag with column collation when possible=False;';
ADOConnection1.Connected := True;
ADOQuery1.Connection := ADOConnection1;
ADOQuery1.SQL.Add('select username,upassword from Users '+
'where username = :usernamep and upassword = '+
':upasswordp');
ADOQuery1.Parameters.ParamByName('upasswordp').Value := password;
ADOQuery1.Parameters.ParamByName('usernamep').Value := username;
ADOQuery1.ExecSQL;
Result := True;
finally
ADOQuery1.Free;
if ADOConnection1.Connected then
ADOConnection1.Close;
ADOConnection1.Free;
end;
Result := False;
end;
initialization
InvRegistry.RegisterInvokableClass(TImplementacao);
InvRegistry.RegisterInterface(TypeInfo(IInterface));
end.
please disregard the ADOConnection and ADOQuery that I have added to my unit i got a little desperate ad duplicade the code... Yeah, I know yachs!!!!
#SilverWarrior
If declare Unit1 inside the uses of UTImplementacao will I have access to the componemts below:
type
ADOConnection1: TADOConnection;
ADODataSet1: TADODataSet;
ADOQuery1: TADOQuery;
or should I declare for each one of the types variable inside var clause ?
If you want to access objects declared in Unit1 from other units in your project you need to add Unit1 into interface uses section (the one at top) of those units.
unit ImplUnit;
interface
uses
SysUtils, Classes, ... , Unit1;
...
That is the same way as Delphi automatically adds other units like Sysutils, Classes, etc.
Also I would strongly recomend you change the name of your unit to somethng more meaningfull so that when you will be looking at your code after some time you will quickly know what code does that unit contains and what it is used for.
EDIT: Based on your edit of the question I suspect you want to acces the components from your Unit1 directly by calling:
Unit1.AdoConnection1
That won't work. Why? Becouse the components are declared within the scope of the TWebModule1 class.
So you need to access them like this:
Unit1.WebModule1.AdoConnection1;
NOTE: If Unit1 is added into interface uses section of your UTImplementacao unit you can also directly call:
WebModule1.AdoConnection1
You don't have to prefix every command with Unit1. I have written this in such way to be hopefully more understandable which unit mebers are you accessing. Especially for other people which might be reading this thread and not knowing the structure of your program.

Errors in Delphi while trying to load procedures from dll

I have a problem while loading procedures from a dll, either when loading it dynamically or statically. When I put procedures from dll to my unit, everything works fine. When I try to do it with dll it gives me
First chance exception at $00526399. Exception class $C0000005 with message 'access violation at 0x00526399: read of address 0x00000390'. Process Project1.exe (21988)
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,Unit2;
type
TForm1 = class(TForm)
ListView1: TListView;
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Edit5: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Refresh;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
type
plist = ^element;
element = record
artist,title,genre: string[20];
year,grade: integer;
wsk: plist;
end;
database = file of element;
var
base: database;
first: plist;
handler: HModule;
{$R *.dfm}
procedure TForm1.Refresh();
var
current: plist;
begin
ListView1.Clear;
current:= first;
while current<>nil do
begin
with ListView1.Items.Add do
begin
Caption:=current^.artist;
SubItems.Add(current^.title);
SubItems.Add(current^.genre);
SubItems.Add(IntToStr(current^.year));
SubItems.Add(IntToStr(current^.grade));
end;
current:=current^.wsk;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var Save: procedure;
begin
handler:=LoadLibrary('lib.dll');
try
#Save:=GetProcAddress(handler, PChar(2));
if #Save = nil then raise Exception.Create('Load nie dziala');
Save();
finally
FreeLibrary(handler);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Load: procedure;
begin
handler:=LoadLibrary('lib.dll');
try
#Load:=GetProcAddress(handler, PChar(1));
if #Load = nil then raise Exception.Create('Load nie dziala');
Load();
finally
FreeLibrary(handler);
end;
Refresh();
end;
procedure TForm1.Button1Click(Sender: TObject);
var
el: element;
Add: procedure(el:element);
begin
el.artist:=Edit1.Text;
el.title:=Edit2.Text;
el.genre:=Edit3.Text;
el.year:=StrToInt(Edit4.Text);
el.grade:=StrToInt(Edit5.Text);
handler:=LoadLibrary('lib.dll');
try
#Add:=GetProcAddress(handler, PChar(3));
if #Add = nil then raise Exception.Create('Load nie dziala');
Add(el);
finally
FreeLibrary(handler);
Refresh();
{Form2:=TForm2.Create(Form1);
Form2.ShowModal;
Form2.Free;}
end;
end;
end.
The dll file looks like this:
library lib;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
System.SysUtils,
System.Classes;
{$R *.res}
type plist = ^element;
element = record
artist,title,genre:string[20];
year,grade:integer;
wsk: plist;
end;
database = file of element;
var
first: plist;
base: database;
procedure add(el: element); stdcall;
var current,tmp: plist;
begin
New(current);
current^ := el;
current^.wsk := nil;
if first = nil then
begin
first:=current;
end else
begin
tmp:=first;
while tmp^.wsk<>nil do
begin
tmp:=tmp^.wsk;
end;
tmp^.wsk:=current;
end;
end;
procedure load();stdcall;
var
el: element;
i: integer;
begin
AssignFile(base, 'baza.dat');
if not FileExists('baza.dat') then
begin
Rewrite(base);
end else
begin
Reset(base);
for i := 0 to FileSize(base)-1 do
begin
read(base, el);
add(el);
end;
end;
CloseFile(base);
end;
procedure save();stdcall;
var
current: plist;
el: element;
begin
AssignFile(base, 'baza.dat');
Rewrite(base);
current:=first;
while current<>nil do
begin
el:=current^;
el.wsk:=nil;
write(base, el);
current:= current^.wsk;
end;
end;
exports
add index 1,
load index 2,
save index 3;
begin
end.
It also shows me an error:
Expected ';' but received and identifier 'index' at line 91
But exports are done like I red on web.
The obvious errors are:
You don't perform much error checking. You assume that the calls to LoadLibrary always succeed.
The calling conventions don't match. You use stdcall in the DLL and register in the executable.
The ordinals don't match. In the DLL it is add (1), load (2) and save (3). In the executable you have add (3), load (1) and save (2).
You load and unload the DLL every time you call functions from the DLL. That means that the global variables in the DLL that hold your state are lost each time the DLL is unloaded.
Frankly this code is a real mess. I suggest that you do the following:
Switch to load time linking using the function names rather than ordinals. This means to use the external keyword in the executable. This will greatly simplify your code by removing all those calls to LoadLibrary, GetProcAddress etc. If runtime linking is needed, you can add it later using the delayed keyword.
Stop using global state in the DLL and instead pass information back and forth between modules. Remove all global variables. But make sure you don't pass Delphi objects back and forth.
Use PChar rather than short strings across the module boundary.
Stop using linked lists and dynamic allocation. That's hard to get right. Use TList<T> in the DLL to store the list of elements.

Resources