I got a very serious problem when I'm trying to access TDictionary variable in host program from a dynamicly loaded dll. Here is the complete code, anyone can give some help? thanks!
===========main program project source code===================
program main;
uses
ShareMem,
Forms,
uMain in 'uMain.pas' {Form1},
uCommon in 'uCommon.pas';
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
==============unit uMain================
unit uMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, uCommon;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
Tfoo = function(ADic: TMyDic): string; stdcall;
procedure TForm1.Button1Click(Sender: TObject);
var
Dic: TMyDic;
HLib: THandle;
foo: Tfoo;
begin
Dic := TMyDic.Create;
try
Dic.Add(1, 'World!');
Dic.Add(2, 'Hello, ');
HLib := LoadLibrary('Mydll.dll');
try
#foo := GetProcAddress(HLib, 'foo');
ShowMessage(foo(Dic));
finally
FreeLibrary(HLib);
end;
finally
Dic.Free;
end;
end;
end.
=================dll project source code=====================
library MyDll;
uses
ShareMem,
SysUtils,
Classes,
uCommon in 'uCommon.pas';
function foo(ADic: TMyDic):string; stdcall;
var
I: Integer;
S: string;
begin
for I in ADic.Keys do
begin
S := S + ADic[I];
end;
Result := s;
end;
exports
foo;
end.
================unit uCommon==============
unit uCommon;
interface
uses
SysUtils, Generics.Collections;
type
TMyDic = TDictionary<Integer, string>;
implementation
end.
Are you getting exceptions? Maybe access violations or invalid pointer operations?
You can't share strings and objects between Delphi and a DLL if the DLL has its own memory manager. Since you're using Delphi 2010, you should have FastMM installed by default. Add "SimpleShareMem" as the first thing in the uses list for both the DLL and the EXE, and see if that doesn't fix the problem?
EDIT: In response to additional information from the poster:
You're calling dic.free after you unload the DLL. Even if you share memory managers, that's going to give you an access violation. Here's why.
Free calls TObject.Destroy, which is a virtual method. The compiler generates code to look it up in the object's Virtual Method Table. But the VMT is stored in static memory that's specific to the module, not in shared memory allocated by the memory manager. You unloaded the DLL and pulled the rug out from underneath the VMT pointer in the object, and so when it tries to call a virtual method you get an access violation.
You can fix this by making sure to call Free before unloading the DLL. Or you can use runtime packages instead of a DLL, which gets around this problem by putting the VMT for the object in an external package that won't be unloaded before you're done with it.
I would strongly discourage passing object instances between an executable and a regular DLL. Mainly for the exact reasons you are are encountering. What happens if the DLL is rebuilt and you've changed the object in some incompatible subtle way?
As Mason points out, packages are the preferred way to partition your application into modules.
I finally found what the real problem is! It seems like this: "For..in keys" loop will cause TDictionary create an instance for its data field FKeyCollection:
function TDictionary<TKey,TValue>.GetKeys: TKeyCollection;
begin
if FKeyCollection = nil then
FKeyCollection := TKeyCollection.Create(Self);
Result := FKeyCollection;
end;
So when the dll is unloaded, the memory that FKeyCollection pointed is also freed, thus left a "dangling pointer".
destructor TDictionary<TKey,TValue>.Destroy;
begin
Clear;
FKeyCollection.Free; //here will throw an exception
FValueCollection.Free;
inherited;
end;
Related
I have a bad behavior using GetProcAddress() to calling a simple method inside a Delphi package.
I have a singleton object that has some methods, and when I call any singleton method inside a Delphi package using GetProcAddress(), another instance of the singleton is being created. It is a big problem because there are a lot of methods that initialize services when the application is started.
Below is the simple example to share the problem:
Singleton Object
unit Unit2;
interface
uses System.Classes;
type
TMyClass = class(TPersistent)
strict private
class var FInstance : TMyClass;
private
class procedure ReleaseInstance();
public
constructor Create;
class function GetInstance(): TMyClass;
procedure TheMethod; -->>> Any method
end;
implementation
uses
Vcl.Dialogs;
{ TMyClass }
constructor TMyClass.Create;
begin
inherited Create;
end;
class function TMyClass.GetInstance: TMyClass;
begin
if not Assigned(Self.FInstance) then
Self.FInstance := TMyClass.Create;
Result := Self.FInstance;
end;
class procedure TMyClass.ReleaseInstance;
begin
if Assigned(Self.FInstance) then
Self.FInstance.Free;
end;
procedure TMyClass.TheMethod;
begin
ShowMessage('This is a method!');
end;
initialization
finalization
TMyClass.ReleaseInstance();
end.
Package Source Code
unit Unit3;
interface
uses Unit2;
procedure CustomMethod;
implementation
procedure CustomMethod;
begin
TMyClass.GetInstance.TheMethod; // ----->> callimg this method, another instance is initialized and lost the first settings
end;
exports
CustomMethod;
begin
end.
Main program code
procedure TForm1.Button1Click(Sender: TObject);
var
Hdl: HModule;
P: procedure;
begin
TMyClass.GetInstance.TheMethod; // -------->>> Initialize the singleton class normally
Hdl := LoadPackage('CustomPgk.bpl');
if Hdl <> 0 then
begin
#P := GetProcAddress(Hdl, 'CustomMethod'); //// ---->>> Call the custom method
if Assigned(P) then
P;
UnloadPackage(Hdl);
end;
end;
Can somebody help me, please?
The only way the Main program code can compile as shown is if it uses Unit2 to get the definition of TMyClass, which means the Main program must be compiled with its own copy of Unit2 since the package is not loaded yet.
In which case, LoadPackage() should fail, since you would have Unit2 being compiled into two separate modules that are loaded in the same process, which is a big no-no, and defeats the point of using packages.
But, even if LoadPackage() didn't fail, you would just end up with two separate FInstance variables in memory, one in the Main program's copy of Unit2, and one in the BPL's copy of Unit2. Which is why you end up with two objects created.
Both modules need to share a single copy of Unit2 in memory to avoid that. So, you need to either:
load the package statically at program load time, instead of dynamically.
move Unit2 to a 2nd package that both the Main program and the 1st package can load statically, and make sure "Runtime Packages" are enabled in all three modules.
I am modulating my application to work with separate modules (plugin).
I have already successfully made my EXE application read and load the plugins, including the forms.
Now I need to do the inverse, export functions from the executable to DLL.
Example:
Inside my executable, it has a TMemo component. I want to create a function like this
function GetMemo(): widestring;
In my idea, whoever wrote the DLL plugin, when calling the function GetMemo(), would already take the contents of the TMemo in DLL.
It is possible?
The simplest way to handle this is to define a record of function pointers, and then have the EXE pass an instance of that record to each plugin while initializing it. The EXE can then implement the functions as needed and pass them to the plugins, without actually exporting them from its PE exports table like a DLL would.
For example:
type
PPluginExeFunctions = ^PluginExeFunctions;
PluginExeFunctions = record
GetMemo: function: WideString; stdcall;
...
end;
function MyGetMemoFunc: WideString; stdcall;
begin
Result := Form1.Memo1.Text;
end;
...
var
ExeFuncs: PluginExeFunctions;
hPlugin: THandle;
InitFunc: procedure(ExeFuncs: PPluginExeFunctions); stdcall;
begin
ExeFuncs.GetMemo := #MyGetMemoFunc;
...
hPlugin := LoadLibrary('plugin.dll');
#InitFunc := GetProcAddress(hPlugin, 'InitializePlugin');
InitFunc(#ExeFuncs);
...
end;
var
ExeFuncs: PluginExeFunctions;
procedure InitializePlugin(pExeFuncs: PPluginExeFunctions); stdcall;
begin
ExeFuncs := pExeFuncs^;
end;
procedure DoSomething;
var
S: WideString;
begin
S := ExeFuncs.GetMemo();
...
end;
unit uDoingExport;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure testproc; stdcall;
implementation
{$R *.dfm}
procedure testproc;
begin
ShowMessage('testproc');
End;
exports
testproc;
end.
I simply added the method I want to publish from within my EXE in the unit's Interface, and on the Implementation I added exports (method name). I am using stdcall not cdecl.
In my child, I can loadlibrary the exe file... or you can go a little crazy like Apache does, and in the previous code, add a loadlibrary, which loads a DLL, which intern can loadlibrary the caller.
My point was to show, your EXE is simply like a DLL (just a different binary header) and vise versa. Just slap EXPORTS. Proof it works, I ran tdump against the EXE:
Exports from ProjDoingExport.exe
1 exported name(s), 1 export addresse(s). Ordinal base is 1.
Sorted by Name:
RVA Ord. Hint Name
-------- ---- ---- ----
0005294C 1 0000 testproc
I know, a late answer, but, a great question!
How can I save breakpoints using the Delphi IDE? I only know how to store the settings in a .dsk file.
I am using Delphi 2007.
I'm assuming from your mention of the .Dsk file that you are aware that the breakpoints are stored in there, but want to save them yourself for some reason. Of course, the easiest method of getting a list of saved breakpoints is simply to read them from the .Dsk file, but that assumes that it has been saved to disk, which usually
occurs when you close the project file.
You can write your own IDE plug-in to get a list of currently-set breakpoints
and save them in any way you want. The minimalist example below shows how to do this - see the GetBreakpoints method for details. To use this in the IDE, you would create a new package which requires
DesignIde.Dcp. Make sure that the output directory for the .Bpl file is either where
your 3rd-party .Bpls are stored on or is on your path. You can then install the
package in the IDE vie Install packages from the IDE's menu.
As you can see, it works by using the BorlandIDEServices interface in the ToolsAPI units to get an IOTADebuggerServices interface, and then uses that to iterate its SourceBkpts list and saves a number of properties of each IOTASourceBreakpoint in that list.
Note that
You can also retrieve a list of address breakpoints and save those in a similar fashion.
Both kinds of breakpoint interface in ToolsAPI have property setters as well as getters, so you could modify existing breakpoints in code and conceivably create new ones.
Code
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ToolsApi;
type
TBreakpointSaveForm = class(TForm)
Memo1: TMemo;
btnGetBreakpoints: TButton;
procedure btnGetBreakpointsClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
protected
public
procedure GetBreakpoints;
end;
var
BreakpointSaveForm: TBreakpointSaveForm;
procedure Register;
implementation
{$R *.DFM}
procedure TBreakpointSaveForm.GetBreakpoints;
var
DebugSvcs: IOTADebuggerServices;
procedure SaveBreakpoint(BreakPoint : IOTASourceBreakpoint);
begin
Memo1.Lines.Add('File: ' + Breakpoint.FileName);
Memo1.Lines.Add('LineNo: ' + IntToStr(Breakpoint.LineNumber));
Memo1.Lines.Add('Passcount: ' + IntToStr(Breakpoint.Passcount));
Memo1.Lines.Add('');
end;
procedure SaveBreakpoints;
var
i : Integer;
BreakPoint : IOTASourceBreakpoint;
begin
Memo1.Lines.Add('Source breakpoint count : '+ IntToStr(DebugSvcs.GetSourceBkptCount));
for i := 0 to DebugSvcs.GetSourceBkptCount - 1 do begin
Breakpoint := DebugSvcs.SourceBkpts[i];
SaveBreakpoint(Breakpoint);
end;
end;
begin
if not Supports(BorlandIDEServices, IOTADebuggerServices, DebugSvcs) then begin
ShowMessage('Failed to get IOTADebuggerServices interface');
exit;
end;
Memo1.Lines.Clear;
SaveBreakpoints;
end;
procedure Register;
begin
end;
initialization
BreakpointSaveForm := TBreakpointSaveForm.Create(Application);
BreakpointSaveForm.Show;
finalization
if Assigned(BreakpointSaveForm) then
BreakpointSaveForm.Free;
end.
procedure TBreakpointSaveForm.btnGetBreakpointsClick(Sender: TObject);
begin
GetBreakpoints;
end;
I'm trying to automate an application (Windows 8, Delphi XE.) For my testing I'm doing the following:
Created a small test application, consisting of a form and a memo (Form1)
Added a new ActiveX Object, CoClass name TestOLE, Threading mode Apartment, Instancing Multiple (as per this article.)
Added one method Method1 which only adds some text to the memo control in Form1
I then start the application and double click on a file named test.vbs which contains the following code:
dim obj
set obj = GetObject("", "Project1.TestOLE")
obj.AddSomeText "Hola mundo"
When the application is running, I see that a new form is created, the text is added and then it exits.
What I want to accomplish is that the opened application should have its memo text changed.
I've repeated creating new projects with both MultipleInstance and SingleInstance, and in an outburst of heuristic anger, I even changed the threading model to single, to no avail.
I see two flags in the type library editor: "Replaceable" and "Aggregatable." However, selecting "Replaceable" ends up in an error in the generated RIDL file.
I've been reading a lot about GetObject. It appears that its documentation is even wrong (it says you can omit the first parameter but I've found that doesn't work).
Is this the right way to write an automation server in Delphi that can be reused?
Well, I got it working (I hope.)
Reading more of the same article cited above, found the following:
Know how to implement servers that support GetActiveObject.
Adding a global object, and registering in the Running Object Table (ROT) accomplishes the desired task of having the COM call passed to the running application:
Project file:
program TestOLEProject3;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
TestOLEProject3_TLB in 'TestOLEProject3_TLB.pas',
Unit2 in 'Unit2.pas' {TestOLE: CoClass},
Unit3 in 'Unit3.pas';
{$R *.TLB}
{$R *.res}
begin
Application.Initialize;
RegisterGlobalTestOLE;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Unit2.pas:
unit Unit2;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, TestOLEProject3_TLB, StdVcl;
type
TTestOLE = class(TAutoObject, ITestOLE)
protected
procedure Method1; safecall;
procedure Quit; safecall;
end;
implementation
uses ComServ, Unit1, Unit3;
procedure TTestOLE.Method1;
begin
Form1.Memo1.Lines.Add('Wheeee');
end;
procedure TTestOLE.Quit;
begin
RevokeGlobalTestOLE;
end;
initialization
TAutoObjectFactory.Create(ComServer, TTestOLE, CLASS_TestOLE, ciMultiInstance,
tmApartment);
end.
Unit3.pas (functions to register and unregister the global object):
unit Unit3;
interface
procedure RegisterGlobalTestOLE;
procedure RevokeGlobalTestOLE;
implementation
uses TestOLEProject3_TLB, ComObj, ActiveX;
var
GlobalTestOLEHandle: longint = 0;
procedure RegisterGlobalTestOLE;
var
GlobalTestOLE: ITestOLE;
begin
GlobalTestOLE := CoTestOLE.Create;
OleCheck(RegisterActiveObject(GlobalTestOLE, CLASS_TestOLE,
ACTIVEOBJECT_STRONG, GlobalTestOLEHandle));
end;
procedure RevokeGlobalTestOLE;
begin
if (GlobalTestOLEHandle <> 0) then
begin
OleCheck(RevokeActiveObject(GlobalTestOLEHandle, nil));
GlobalTestOLEHandle := 0;
end;
end;
end.
I am having a strange problem of using interface in different versions of Delphi. The following minimized code compiles and runs as expected in Delphi XE and higher but not in Delphi 7. Specificaly, it seems when compiling in Delphi 7, the function TForm1.Load: IMoleculeSubject; does not returns the correct result, i.e., the correct reference to the newly created instance. Could you help to comment about the reason and possible workaround? Many thanks!
uInterface.pas
unit uInterface;
interface
type
IMoleculeSubject = interface
['{BEB4425A-186C-45DF-9DCE-C7175DB0CA90}']
end;
TMoleculeSubject = class(TInterfacedObject, IMoleculeSubject)
end;
implementation
end.
uBusiness.pas
unit uBusiness;
interface
uses
uInterface;
type
TMoleculeDecorator = class(TMoleculeSubject)
private
FID: Integer;
public
property ID: Integer read FID;
constructor Create;
end;
implementation
{ TMoleculeDecorator }
constructor TMoleculeDecorator.Create;
begin
inherited Create;
FID := Random(100);
end;
end.
Unit1.pas
unit Unit1;
interface
uses
uInterface, uBusiness,
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
function Load: IMoleculeSubject;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
MolSubject: IMoleculeSubject;
begin
MolSubject := Load;
// The down-cast is to show the returned result is wrong in Delphi 7!
Caption := IntToStr(TMoleculeDecorator(MolSubject).ID);
end;
function TForm1.Load: IMoleculeSubject;
var
MolSubject: IMoleculeSubject;
begin
MolSubject := TMoleculeDecorator.Create;
Result := MolSubject;
end;
end.
The Load function works perfectly well in all versions of Delphi. The problem is your cast, which is what is known as an unsafe typecast. An unsafe typecast from an interface reference to an object has ill-defined behaviour in older versions of Delphi. However, the behaviour is well-defined in modern Delphi. The documentation says more.
So, the basic problem is that your expectations for the behaviour are not compatible with the Delphi 7 version of the language.
If you get the interface to return the ID you will find that the interface you are creating is as expected.
program InterfaceDemo;
{$APPTYPE CONSOLE}
uses
Classes;
type
IMyIntf = interface
function GetID: Integer;
end;
TImplementingObject = class(TInterfacedObject, IMyIntf)
private
FID: Integer;
function GetID: Integer;
public
constructor Create;
end;
{ TImplementingObject }
constructor TImplementingObject.Create;
begin
FID := Random(100);
Writeln(FID);
end;
function TImplementingObject.GetID: Integer;
begin
Result := FID;
end;
var
MyIntf: IMyIntf;
begin
Randomize;
MyIntf := TImplementingObject.Create;
Writeln(MyIntf.GetID);
Readln;
end.
It's rather unusual to ask for the implementing object from an interface. To do so suggests that there is a problem with your design. Should you really need to do so there are a few options:
In modern Delphi you can use the type-safe case with the as operator.
In older Delphi versions there are hacks that retrieve the implementing object: Casting a delphi interface to its implementation class without modifying the interface
You could add a function to the interface that returns the implementing object.
The latter option works in all versions of Delphi and does so without resorting to subterfuge.
Casting interfaces to objects is available since Delphi 2010. Where are workarounds for older Delphi versions, see for example How to cast a Interface to a Object in Delphi