Conversion of a delphi win32 application to a library and FreeLibrary freeze - delphi

I have an application which Should be converted to a library. I' ve only copied the project dpr and changed the source file:
library aLibrary;
uses
FastMM4,
Forms,
SysUtils,
Windows,
Mainfrm in 'Mainfrm.pas' {Mainform};
{$R *.res}
Procedure DllMain(Reason: Integer);
Begin
If Reason = DLL_PROCESS_ATTACH Then
Begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
ExitCode := 0;
End;
If Reason = DLL_PROCESS_DETACH Then
Begin
Application.Terminate;
MainForm.Free;
End;
End;
Begin
DllProc := #DllMain;
DllProc(DLL_PROCESS_ATTACH);
End.
As you can see, I've simply removed the usually auto-generated code lines related to the app initialization and put them in the DllMain procedure, changed the 'program' keyword to 'library'.
The dll loads well, the embedded program runs well too, but I can't manage to free it (FreeLibrary) in the host process. The dll freezes, whatever the DLL_PROCESS_DETACH code is (even when nothing is put for this case).
What would be the proper way to free all the application stuffs ?

You're doing way too much in your DLL procedure.
While loading a DLL, the OS acquires the loader lock. That prevents any other libraries from being loaded at the same time. If you call a function that's in a library that hasn't already been loaded, then that will trigger an attempt to load that library. Since you're still inside the loader lock, that other library blocks while attempting to acquire the lock itself, and you get deadlock. Similar rules apply for unloading.
You should do the absolute minimum in the DllMain function to get your library loaded or unloaded. Leave everything else to a separate function that the DLL host can call after loading is complete or just before unloading begins. In your case, the minimum is probably nothing at all.

Ok, I've well understood your summary about dll locks and I've put the embedded application constructor/destructor in some standard exported dll routines, so there 's no more DllMain. However calling freelibrary still reveals a deadlock. My dll is instantiated by another dll, which is itself instantiated in an executable. Maybe the lock is introduced at a lower level in the processes hierarchy.

Related

Is there a way to execute only code from a unit from a big exe placed on shared drive?

I admit i am in the dirty tricks area in this question.
PROBLEM: exe is deployed on shared folder(I call it ShaExe from now on), without changing deployment rules somehow I want to cache the file locally (I call it LocExe fromnow on) and while launching ShaExe i finally end up running LocExe. LocExe must be updated (copied from ShaExe) everytime ShaExe is modified (=updated).
Somehow what I am researching is setting up something like this:
Prerequisite: LocExe path is an user\AppData\Local\MyApp harcoded path so ShaExe knows where LocExe is.
ShaExe is launched
ShaExe compares its size with the size of LocExe, if different it copies ShaExe over LocExe
ShaExe executes LocExe with the -skiplauncher command line parameter
ShaExe terminates itself
when -skiplauncher is used points 2 3 4 of the above list are not executed (this is to avoid an endless loop of executing LocExe and terminating the applcation)
Now this approach works, but the bottleneck is (1) because ShaExe size is 110MB so somehow launching an exe from shared path takes time (Even if I do not use the flag {$SetPEFlags IMAGE_FILE_NET_RUN_FROM_SWAP}).
I tried to prepare a very small version of ShaExe (basically built without units but only with the launcher logic in it ending up in 6MB of exe).
WIth this "light but useless ShaExe" the whole process is faster.
I would like that (2) (3) and (4) are executed immediately, is this possible or not with Windows?
I am perfectly aware the best practice would be either to copy manually the file locally and run it or create a simple launcher exe that does the job of running LocExe doing the cache when necessary, but I have a very big customer that requires me to do this without changing the deployment procedure.
THis is why I ask for expert advice here.
This is my code (of course simplified, instead of Unit1 imagine 100 units):
//this is the dpr
program MYProgram;
uses
Vcl.Forms,
execache in 'execache.pas',
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
RunCachedExe;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
//this is the execache unit
unit execache;
interface
uses
Vcl.Forms,
Windows,
ShellAPi,
sysutils,
dialogs,
system.IOUtils;
procedure RunCachedExe;
implementation
procedure RunCachedExe;
function GetFileSize(const aFilename: String): Int64;
var
info: TWin32FileAttributeData;
begin
result := -1;
if NOT GetFileAttributesEx(PWideChar(aFileName), GetFileExInfoStandard, #info) then
EXIT;
result := Int64(info.nFileSizeLow) or Int64(info.nFileSizeHigh shl 32);
end;
var
CurrentInstanceSize, CachedFileSize, i: integer;
CachedFilePath, Outs: string;
SkipLauncher: Boolean;
begin
SkipLauncher := False;
for i := 0 to paramcount -1 do
begin
if Paramstr(i) = '-SkipLauncher' then
SkipLauncher := True;
end;
if not SkipLauncher then
begin
// in this code path is hardcoded, in real life I use a function that calls:
// SHGetSpecialFolderPath with CSIDL_LOCAL_APPDATA
CachedFilePath := 'C:\Users\myuser\AppData\Local\MyProject\bincache\MyProject.exe';
CurrentInstanceSize := GetFileSize(Application.ExeName);
// this will return -1 if file not found
CachedFileSize := GetFileSize (CachedFilePath);
if CachedFileSize <> CurrentInstanceSize then
begin
ForceDirectories('C:\Users\myuser\AppData\Local\MyProject\bincache\');
CopyFile(PChar(Application.ExeName), PCHar(CachedFilePath), False);
end;
// launch local exe and close this one
ShellExecute (0, 'open', PWideChar( CachedFilePath ), PWideChar('-SkipLauncher'), nil, SW_SHOWNORMAL);
Application.Terminate;
end;
end;
This code uses some functions from other units but I could move all in the execache unit, if this could help to speed up the execution of the exe.
Somehow I would like to run only an initialization part, containing the logic described here and then terminate the app (when ShaExe is in place, that basically means when -skipLauncher is not used).
So, is it possible to run only an initialization logic of a big exe file?
I hope I expressed myself, thanks.
Regardless of the code you have, what you are essentially asking is if partially loading an executable is possible, to have it conditionally execute only a portion of the code it contains. Or a streaming executable perhaps? These are not possible, the OS will have to fetch the entire runtime from the remote location, together with any static dependencies, to the local computer. Even if the executable is to exit immediately.
You can have the behavior you want by separating the conditional part to a different executable though: a launcher. Without any VCL dependency you can have it really small. Even then, the better alternative is to run the executable locally to have it check if a remote location contains an updated version, since networked access is more prone to performance and security issues.

Program hangs when unloading a DLL making use of GdiPlus

I have an application which load a DLL that makes use of Delphi GDI+ Library. This application hangs when it unload the DLL (Calling FreeLibrary).
I tracked the issue down to GdiPlus.pas unit finalization section which calls GdiPlusShutdown which never returns.
How to avoid this deadlock?
The documentation for the GdiplusStartup function says this:
Do not call GdiplusStartup or GdiplusShutdown in DllMain or in any
function that is called by DllMain. If you want to create a DLL that
uses GDI+, you should use one of the following techniques to
initialize GDI+:
Require your clients to call GdiplusStartup before they call the functions in your DLL and to call GdiplusShutdown when they have
finished using your DLL.
Export your own startup function that calls GdiplusStartup and your own shutdown function that calls GdiplusShutdown. Require your clients
to call your startup function before they call other functions in your
DLL and to call your shutdown function when they have finished using
your DLL.
Call GdiplusStartup and GdiplusShutdown in each of your functions that make GDI+ calls.
By compiling this Delphi GdiPlus library into a DLL you are breaking this rule for both GdiplusStartup and GdiplusShutdown. These functions are called in unit initialization and finalization sections, respectively. And for library projects, code in the initialization and finalization sections of a unit is executed from DllMain.
It seems that the GdiPlus library that you use was never intended to be used from a library. But as a general rule, when writing library code, you should be aware of the restrictions around DllMain and make sure that code that you place in initialization and finalization sections respects that. I think that this GdiPlus library fails in that regard.
By way of contrast, have a look at the code in the Delphi RTL's WinApi.GDIPOBJ unit:
initialization
if not IsLibrary then
begin
// Initialize StartupInput structure
StartupInput.DebugEventCallback := nil;
StartupInput.SuppressBackgroundThread := False;
StartupInput.SuppressExternalCodecs := False;
StartupInput.GdiplusVersion := 1;
GdiplusStartup(gdiplusToken, #StartupInput, nil);
end;
finalization
if not IsLibrary then
begin
if Assigned(GenericSansSerifFontFamily) then
GenericSansSerifFontFamily.Free;
if Assigned(GenericSerifFontFamily) then
GenericSerifFontFamily.Free;
if Assigned(GenericMonospaceFontFamily) then
GenericMonospaceFontFamily.Free;
if Assigned(GenericTypographicStringFormatBuffer) then
GenericTypographicStringFormatBuffer.free;
if Assigned(GenericDefaultStringFormatBuffer) then
GenericDefaultStringFormatBuffer.Free;
GdiplusShutdown(gdiplusToken);
end;
This code respects the rules by making sure that it does not call GdiplusStartup and GdiplusShutdown from DllMain. Instead it leaves the onus on the author of any library that uses WinApi.GDIPOBJ to make sure that GdiplusStartup and GdiplusShutdown are called at appropriate times.
If I were you I would pick one of the three bullet point options listed above. The third of these options is not very practical, but the first two are good choices. Were it me, I would opt for the first option and modify the initialization and finalization code in your GdiPlus library to look more like that found in WinApi.GDIPOBJ.
GdiPlusShutdown (and GdiPlusStartup btw) cannot be called from DllMain but DllMain is called by Windows and Delphi runtime when FreeLibrary is called: Delphi call the finalization section of all units used by the DLL and GdiPlus finalization section calls GdiPlusShutdown (This is perfectly OK when used from an executable). Similar behavior with initialization section.
I have fixed the issue by adding a test for IsLibrary in both initialization and finalization sections to avoid calling the offending functions, also added two public procedures InitializeForDll and FinalizeForDll. With those little changes, the DLL is able to export functions calling InitializeForDll and FinalizeForDll. Those exported function have to be called by the hosting application right after loading the DLL and just before unloading the DLL.
Here are the changes I made to GdiPlus.pas:
In the interface section:
var
procedure InitializeForDll;
procedure FinalizeForDll;
In the implementation section:
procedure InitializeForDll;
begin
Initialize;
end;
procedure FinalizeForDll;
begin
Finalize;
end;
Also updated the initialization and finalization sections like this:
Initialization
if not IsLibrary then
Initialize;
Finalization
if not IsLibrary then
Finalize;
In the DLL, I exported those functions:
procedure Initialize; stdcall;
begin
GdiPlus.InitializeForDll;
end;
procedure Finalize; stdcall;
begin
GdiPlus.FinalizeForDll;
end;
Initialize and Finalize are called by the hosting application right after calling LoadLibrary and just before calling FreeLibrary (Or whatever will load/unload the DLL).
I hope this will help others.
btw: Thanks to Eric Bilsen for providing Delphi GdiPlus Library

Dynamicly handle not defined functions in a dll in dlephi

I have a dll which defines a simple functions to show a message:
library testdll;
uses SysUtils, Classes, Dialogs;
{$R *res}
procedure ShowDll;stdcall;
begin
ShowMessage('ShowDLL testdll.dll');
end;
exports ShowDLL;
begin
end.
And my main file call this dll dynamicly using this procedure:
i defined a new type:
type
testdll = procedure;stdcall;
Then on my button click event:
procedure TForm1.Button1Click(Sender:TObject);
var
dllHandle: THandle; //Don't use Cardinal; it's limited to 32-bits
test : testdll;
begin
dllHandle := LoadLibrary('testdll.dll');
if dllHandle <> 0 then
begin
#test := GetProcAddress(dllHandle,'ShowDLL');
if Assigned(test) then
ShowDLL
else
ShowMessage('Func not found');
end
else
ShowMessage('dll not found');
end;
This works. But I don't know if it's possible to handle not defined functions with my dll. My purpose here is to call a function without knowing if it will be defined in my dll. So i would like the dll to tell me if the functions exists or not.
For example here i only have a ShowDLL procedure. If i call another method which does not exists it will show 'Func not found' from my main application. But i would my dll to tell me this. Is this possible? If yes, how could i achieve this pls?
EDIT: I can't modify the main function this is only a test. In my final version there will be only the dll. So exporting all functions into my main application is not possible here. That's why i want to know id the dll alone can handle it rather than doing this in my main application which i can't do.
I don't have acces to the main application to modify any code in it. I only know what are functions that will be used in this application that i will later export in my dll using exports statement. So what i try to achieve is to catch a not defined function with the dll if it's possible.
Your code already demonstrates how to detect the presence of an export. Namely to call GetProcAddress and compare the result with nil.
You can reduce the amount of boiler plate code by using delay loading. This essentially lets the compiler generate the code the performs the checks. This relies on the delayed keyword.
procedure testdll; stdcall;
external 'testdll.dll` delayed;
When you call this function, if it cannot be found, an exception is raised. Indeed this behaviour can be customised, as described in the documentation: http://docwiki.embarcadero.com/RADStudio/en/Libraries_and_Packages#Delayed_Loading
Update
In a comment to this answer you state that the executable cannot be changed. In which case, the message that you wish to avoid will be shown if the function is missing. You ask if the missing export can be handled by the DLL but that is not possible. When you call GetProcAddress, no code inside the DLL is executed. All the processing is done external to the DLL by reading its PE metadata.
The obvious conclusion is that the DLL must export the function. If the true DLL does not, put another DLL in its place. Use an interposer DLL that exports the function. Implement the function by delegating to the true DLL, if the true DLL exports the function. Otherwise do something else, whatever you please.

Strange, when i call function from DLL, application not start but no error found

in mine project i have a Windows application and a dll. I have wrote dll so:
library MyDLL;
uses
System.SysUtils,
System.Classes;
{$R *.res}
function Prova: string; export;
begin
result := 'prova';
end;
exports Prova;
begin
end.
and in main program i have called routine so:
unit FrmMain;
interface
uses
// declaration uses //
function Prova: string; external 'MyDLL.dll';
type
// declaration type //
implementation
begin
...
TAdvEdit1.Text := Prova; // [1] //
...
end;
end.
When i compile all project not is reported error, and status report SUCCESS, but application not start.
If i remove the line [1] then it works correctly. In general, application not start when i call the function Prova.
What i can solve this problem? Thanks very much.
The behaviour you describe is what happens when your application fails to load. When run from the debugger you get a silent failure. When run without a debugger you will see an error message, "Application failed to initialize...". This will give details.
In your case it seems that the likely cause is that the dependency of the DLL cannot be resolved by the library loader. That's why the app runs when the call to the external function is removed. When you remove that call, you also remove the dependency on the external DLL. Solve the problem by making sure the DLL can be loaded. For example place it in the same directory as the executable.
The silent failure from the debugger is rather frustrating. Once you have experienced it a few times you'll know what to do - run without the debugger to find out what's really going wrong.
I also recommend that you don't pass managed Delphi strings across module boundaries. That will force you to use the same compiler for both executable and DLL. If you are going to accept that constraint then you may as well use packages. As your code stands, it would need to use ShareMem to work. But I don't recommend that.
Your program and your DLL have separate memory manager. As a general rule memory allocated from DLL should not be used inside your application (the opposite is also true).
Where the allocation comes from? In Delphi "string" is managed type i.e. when you assign some text to a string variable (in your case result := 'prova'), Delphi (behind the scene) allocates memory for that string using DLL's memory manager. Then, for example, if you assign other text value within your main application, the reallocation uses application's memory manager which is bad i.e. app's MM is touching memory that it hasn't allocated itself.
To solve this issue you have to include "SimpleShareMem" (Delphi >= 2010 IIRC?) unit as the first unit of your USES clause in the application (.dpr file) AND in the DLL:
library MyDLL;
uses
SimpleShareMem, // **MUST BE THE FIRST UNIT**
System.SysUtils,
System.Classes;
...
program YourApp;
uses
SimpleShareMem, // **MUST BE THE FIRST UNIT**
// declaration uses //
An example of this approach can be found in "Start > Programs > Embarcadero RAD Studio > Samples > Delphi > RTL > SimpleShareMem"
You can also use PCHAR to transfers strings between DLL and the APP.

Delphi - Form in DLL - Hints not showing

I have a Delphi form inside a DLL (I know that this restricts the use of the DLL to Delphi but this is not a problem in this case).
The DLL exports a function ShowForm that looks roughly like this:
procedure ShowForm (App : TApplication);
begin
OldApp := Application;
try
Application := App;
MyForm := TMyForm.Create (nil);
try
MyForm.ShowModal;
finally
FreeAndNil (MyForm);
end;
finally
Application := OldApp;
end;
end;
Now on the form I use a TAdvOfficeHint (from the TMS component pack). Unfortunately the hints do not show up.
Am I missing something here? How can I make the form behave exactly as it would if I showed it from the main application?
Thanks!
I don't know TAdvOfficeHint but I guess it hooks Application.OnShowHint to set its own THintWindowClass, and even if both the main executable and the DLL are linking in the TMS unit, they each have their own copy of the class which is where things go wrong.
Assigning Application is not enough: there are other global variables, like Screen, Mouse, etc. Others are even hidden in the implementation so I'd say your chances to make the form behave exactly as from the main application are slim.
Just found the reason why it does not work. As TOndrej states, TAdvOfficeHinthooks Application.OnShowHint and internally executes the following line of code:
FHintInfo.Assign (AHintInfo);
Assign internally uses a dynamic type check
if (Source is TAddvHintInfo) then ...
which fails due to the separate type registries of the DLL and the main application.
I have run into this problem a few times now and maybe I really have to switch to runtime packages to avoid all this stuff.
Anyway, if there's anything I can do to prevent this, please comment.
Wrong setting of Application.
Try this and see if it solves your problem:
procedure ShowForm (AppHandle : THandle);
begin
OldAppHandle := Application.Handle;
try
Application.Handle := AppHandle;
........
finally
Application.Handle := OldAppHandle;
end;
end;
I guess in Delphi 2006 and later versions you can call System.ShareMemoryManager method in the EXE code, so that its memory manager is shared with other modules loaded in the process memory space.

Resources