DLL_PROCESS_DETACH won't get called? - delphi

A C# program calls a DLL which I have written in Delphi:
[DllImport("ABCDEF.dll")]
private static extern void OpenDrawer();
The DLL in Delphi has the function (among of other things) to open the drawer for an Epson POS printer.
Its DLLMain contains DLL_PROCESS_ATTACH, which loads EpsStmApi.dll, and its DLL_PROCESS_DETACH method frees EpsStmApi.dll again, as well as closes the printer handle, if one exists. The DLL-function OpenDrawer will call the DLL function and save the printer handle as global variable (this is important for the performance).
library Test;
var
hEpsonDLL: cardinal = 0; // DLL handle for EpsStmApi.dll
hMonPrinter: integer = 0; // Printer handle. Stays open while the DLL is loaded, for performance
type
TFuncBiOpenMonPrinter = function (nType: Integer; pName: LPSTR): Integer; stdcall;
TFuncBiOpenDrawer = function (nHandle: Integer; drawer: Byte; pulse: Byte): Integer; stdcall;
TFuncBiCloseMonPrinter = function (nHandle: Integer): Integer; stdcall;
var
funcBiOpenMonPrinter : TFuncBiOpenMonPrinter;
funcBiOpenDrawer : TFuncBiOpenDrawer;
funcBiCloseMonPrinter : TFuncBiCloseMonPrinter;
procedure OpenDrawer; stdcall; export;
begin
if hEpsonDLL <> 0 then
begin
// DLL missing. Probably no Epson printer installed.
exit;
end;
if hMonPrinter = 0 then
begin
// Initialize the printer.
#funcBiOpenMonPrinter := GetProcAddress(hEpsonDLL, 'BiOpenMonPrinter') ;
if Assigned(funcBiOpenMonPrinter) then hMonPrinter := funcBiOpenMonPrinter(TYPE_PRINTER, 'EPSON TM-T88V Receipt');
end;
if hMonPrinter <> 0 then
begin
// Try to open the drawer
#funcBiOpenDrawer := GetProcAddress(hEpsonDLL, 'BiOpenDrawer') ;
if Assigned(funcBiOpenDrawer) then funcBiOpenDrawer(hMonPrinter, EPS_BI_DRAWER_1, EPS_BI_PULSE_400);
end;
end;
procedure DllMain(reason: Integer);
begin
if reason = DLL_PROCESS_ATTACH then
begin
// Note: Calling BiOpenMonPrinter here will cause a deadlock.
hEpsonDLL := LoadLibrary('EpsStmApi.dll') ;
end
else if reason = DLL_PROCESS_DETACH then
begin
// Destroy the printer object
if hMonPrinter <> 0 then
begin
#funcBiCloseMonPrinter := GetProcAddress(hEpsonDLL, 'BiCloseMonPrinter') ;
if Assigned(funcBiCloseMonPrinter) then funcBiCloseMonPrinter(hMonPrinter);
end;
// Free the library
if hEpsonDLL <> 0 then FreeLibrary(hEpsonDLL);
end;
end;
exports
OpenDrawer;
begin
DllProc := DllMain;
DllMain(DLL_PROCESS_ATTACH);
end.
It works so far, but I wonder if it is clean and correct, because I noticed that DLL_PROCESS_DETACH will never be called (I checked it by writing to log files), therefore the printer handle stays open. I do not think it is a big deal, but I am unsure if something is not correct in-memory.
Is it possible that DLL_PROCESS_DETACH does not get called because the DLL itself has another DLL loaded?
(Note: There is a reason why I don't call EpsStmApi.dll via C# directly, which is not relevant to this topic.)

To begin with, what you report cannot be readily reproduced. A simple C# console application that calls a trivial Delphi DLL does indeed fire DLL_PROCESS_DETACH when the process terminates.
library Project1;
uses
Windows;
procedure foo; stdcall;
begin
end;
procedure DllMain(reason: Integer);
begin
if reason = DLL_PROCESS_ATTACH then begin
OutputDebugString('DLL_PROCESS_ATTACH');
end else if reason = DLL_PROCESS_DETACH then begin
OutputDebugString('DLL_PROCESS_DETACH');
end;
end;
exports
foo;
begin
DllProc := DllMain;
DllMain(DLL_PROCESS_ATTACH);
end.
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport(#"Project1.dll")]
static extern void foo();
static void Main(string[] args)
{
foo();
}
}
}
Similarly if this DLL is hosted in an executable that loads it with LoadLibrary and then unloads it with FreeLibrary, the DllMain call with reason DLL_PROCESS_DETACH does fire. Most likely you are simply mistaken in your diagnosis.
If you wish to perform tasks when the DLL is loaded and unloaded, then perhaps a simpler way is to add code to a unit initialization and finalization sections. However, that code is still invoked from DllMain which severely limits what you can do, leading me to my next point.
You are breaking the rules for DllMain. The documentation for DllMain says
The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order.
I guess you are getting away with this, at least for now. But things could easily change. Moving the code to initialization and finalization changes nothing because they are still invoked from DllMain.
I strongly advise you to remove all of your DllMain code. If you wish to delay load the DLL then do so using delayed or move the loading of the DLL into another exported function that performs initialisation.

Related

MQL5 - Launching a form embedded in a DLL

In MetaTrader 4/MQL4 I'm able to display a non-modal form (i.e., simple window with string grid) and pass data to this form from an indicator using the DLL interface. Been doing this for a while now and it works perfectly.
Trying to accomplish the same thing in MetaTrader 5/MQL5 but having some trouble. The form displays but immediately goes into an unresponsive state (application not responding).
I did run a test using the same 64-bit DLL on a different 64-bit platform (eSignal 12) and the form loads just fine.
I am using Delphi XE5 Update 2 to build the DLLs. 32-bit for MT4 and 64-bit for MT5. MT5 build is 1881. I created a very simple MQL5 script and Delphi DLL to test it:
MQL5
#import "TestForm.dll"
int showAForm(int hwnd);
int closeAForm();
#import
void OnStart()
{
//---
int hwnd=ChartGetInteger(0,CHART_WINDOW_HANDLE,0);
showAForm(hwnd);
Sleep(5000);
closeAForm();
//---
}
Delphi
library TestForm;
uses
System.SysUtils,
System.Classes,
Windows,
Vcl.Forms, Vcl.Dialogs;
{$R *.res}
var
myForm: TForm;
function showAForm(handle: HWND): integer; stdCall;
var
myHandle: HWND;
begin
myHandle := FindWindow('MetaQuotes::MetaTrader::5.00', nil);
showMessage(IntToStr(myHandle));
try
myForm := TForm.Create(nil);
// myForm:=TForm.CreateParented(myHandle);
// Windows.SetParent(myForm.Handle, myHandle);
myForm.Show;
result := 1;
except
result := -2;
end;
end;
function closeAForm(): integer; stdCall;
begin
myForm.Close();
FreeAndNil(myForm);
result := 1;
end;
exports
showAForm,
closeAForm;
begin
IsMultiThread := true;
end.
I have tried a few different methods of creating the form (nil, parented) but it makes no difference. If I use ShowModal instead of Show the form displays correctly but of course the MT5 GUI is blocked.
No exceptions are generated, and both functions return correctly...it is just that the form itself will not display. Would be grateful if anyone could shed some light on what may be going on, and any possible workarounds.
This is the problem:
showAForm(hwnd);
Sleep(5000);
closeAForm();
Your Sleep(5000); blocks the GUI thread for full 5 seconds.
So you create the form showAForm(), block the GUI thread for 5 seconds, then close the form closeAForm().
The Delphi form needs a working message pump to be able to work properly,
but the Sleep(5000); blocks the thread and doesn't let the message pump to operate.
In Delphi applications, eg. "mydelphiapp.exe", the Application.ProcessMessage function handles a message, and inside the Application.Run procedure, there is code like:
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
To solve the problem, you need to build in Delphi, a message-pumping alternative to Sleep():
procedure MessagePumpingSleep(ADelay:Longword); stdcall;
var
T1, T2, dT : Longword;
begin
T1 := GetTickCount;
repeat
Application.ProcessMessages;
Sleep(1); // this will stop this procedure from consuming 100% CPU.
T2 := GetTickCount;
dT := T2 - T1;
until dT >= ADelay;
end;
exports MessagePumpingSleep;
Then call MessagePumpingSleep(5000) instead of Sleep(5000);
Obviously, to be able to call MessagePumpingSleep(5000), you need to write the proper import code in MQL5 to import the MessagePumpingSleep() -function from the Delphi DLL.

Updating Inno Setup progress bar from Dephi DLL

I need your help, please. I'm trying to call function from DLL written in Delphi 10 Seattle from Inno Setup (ANSI). But I do not understand what is the problem. If I make application in Delphi and call this function from DLL, it works perfectly! See code listing:
Delphi DLL:
function Process(Pb: TProgressBar): Integer; stdcall;
var
I: integer;
begin
for I := 0 to 1000 do
begin
Pb.Position := I;
Pb.Update;
Sleep(10);
end;
end;
Exports
Process;
Inno Setup (ANSI):
function Count(Progr: TNewProgressBar): integer; external 'Process#files:CallC.dll stdcall delayload';
procedure NewButton1Click(Sender: TObject);
begin
Count(NewProgressBar1);
end;
After call I get Access Violation. But, comment in dpr file i read, ShareMem write first line, but zero effect.
Show me how to correctly update progress bar in Inno Setup from Delphi DLL, please.
You cannot call object methods this way. You may be lucky to get this working, if you use exactly the same version of Delphi as the one Inno Setup is built with, as your tests with Delphi application shows. But it is still wrong and unreliable, do not do it. As you use a different Delphi version, the layout of the progress bar class in memory is different, hence the "Access violation".
For this particular task, you can easily do with just a handle to the progress bar:
function Process(Handle: THandle): Integer;
var
I: Integer;
begin
SendMessage(Handle, PBM_SETRANGE, 0, 1000 shl 16);
for I := 0 to 1000 do
begin
SendMessage(Handle, PBM_SETPOS, I, 0);
UpdateWindow(Handle);
Sleep(10);
end;
end;
In Inno Setup, call the function like:
function Count(Handle: THandle): integer;
external 'Process#files:CallC.dll stdcall delayload';
procedure NewButton1Click(Sender: TObject);
begin
Count(NewProgressBar1.Handle);
end;
For more advanced tasks, you need to use a callback.
See
Using callback to display filenames from external decompression dll (Inno Setup)
Call C# DLL from Inno Setup with callback

Delphi - procedure fails to complete, but works fine with "showmessage" between.?

I'm not quite sure how to even ask this question, since I don't know whether it is related to the execution time, application process.message procedure or anything else.
I'm having (for me) weird situations, where the procedure fails to run and raises system exception on run, while it runs completely flawless if I put "showmessage" there in between (which I put so that I could quickly see what's going on in between. I prefer that way over watches somehow...).
I'm not sure whether the code matters or not, but I'll give it below:
procedure LoadSettings;
var SettingsBuffToLoad: TStringList;
begin
SettingsBuffToLoad:=TStringList.Create;
Encoding:=TEncoding.ANSI;
SettingsBuffToLoad.LoadFromFile('bin/settings.txt', Encoding);
// showmessage(settingsbufftoload.Strings[0]);
SettingsBuffer:=Decode(SettingsBuffToLoad);
// showmessage(settingsbuffer.Strings[0]); //decode
end;
The Decode procedure is declared as external and is read from the dll.
If I just remove those "/" , so that it becomes the code instead of comment, it works just fine. However, set as you see now, it raises exception, but after the procedure is already done. (the debugger last break point is stopped at "end;", after continuing however it raises exception instead of showing the form; this procedure is called as the last thing in FormCreate procedure.
Is there anything that has to do with the timing, which ShowMessage solves, or...? :/
Update:
The decode functions, as asked:
this is how it's declared, right above of the implementation and variables of the form:
function Decode(Buff: TStringList): TStringList; StdCall; external 'bin\settings.txt';
And this is in the dll:
function Decode(Buff: TStringList): TStringList; export;
var
t, u, h: integer;
s: String;
begin
DecodeBuffer.Clear;
DecodeBuffer:=Buff;
for h := 0 to DecodeBuffer.Count-1 do
begin
s := DecodeBuffer.Strings[h];
t := Length(s);
if t > 0 then
begin
for u := 0 to t-1 do
begin
s[u+1] := DecodeChar(s[u+1], (h mod 5) + 1);
end;
DecodeBuffer.Strings[h] := s;
end;
end;
Result:=DecodeBuffer;
end;
This code was discussed in a question at Delphi changing Chars in string - missunderstood behavior - XE3 and is used from Remy's answer. The DecodeChar is, I believe simply unimportant here, or is it?
Also, the same goes with the function to save settings, which is called at FormClose event:
This is:
procedure TScribbles.SaveSettings;
var SettingsBuffToSave: TStringList;
begin
SettingsBuffToSave:=TStringList.Create;
Encoding := TEncoding.ANSI;
// Showmessage(settingsbuffer.Strings[0]);
SettingsBuffToSave:=Encode(SettingsBuffer);
// Showmessage(settingsbufftosave.Strings[0]);
SettingsBuffToSave.SaveToFile('bin/settings.txt', Encoding);
end;
With the first ShowMessage used as code instead of comment, it works, while otherwise in a comment function as it is written above, it calls external exception the same way as on Decode.
Is it possible, that the SettingsBuffToSave is just not yet created when it already calls the function Encode, or what?
At that time, the SettingsBuffer exists and is populated, so it really seems weird that it raises errors, which disappears with simply putting ShowMessage in there.
(Function Encode is basically a mirror of Decode, so the code is not important here...)
This code is VERY VERY VERY dangerous on many levels. Using objects across the DLL boundary in an unsafe manner. Mismanagement of object pointers across function calls. You need a redesign. Try the following as a start:
procedure Decode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; export;
var
u: integer;
begin
for u := 0 to BuffLen-1 do
begin
Buff^ := DecodeChar(Buff^, (ListIndex mod 5) + 1);
Inc(Buff);
end;
end;
procedure Encode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; export;
var
u: integer;
begin
for u := 0 to BuffLen-1 do
begin
Buff^ := EncodeChar(Buff^, (ListIndex mod 5) + 1);
Inc(Buff);
end;
end;
procedure Decode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; external '...';
procedure Encode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; external '...';
procedure LoadSettings;
var
h: Integer;
begin
SettingsBuffer := TStringList.Create;
SettingsBuffer.LoadFromFile('bin/settings.txt', TEncoding.ANSI);
for h := 0 to SettingsBuff.Count-1 do
begin
Decode(PChar(SettingsBuff[h]), Length(SettingsBuff[h]), h);
end;
end;
procedure TScribbles.SaveSettings;
var
h: Integer;
begin
for h := 0 to SettingsBuff.Count-1 do
begin
Encode(PChar(SettingsBuff[h]), Length(SettingsBuff[h]), h);
end;
SettingsBuff.SaveToFile('bin/setpb95enc.dll', TEncoding.ANSI);
end;
The obvious problem here is that the code exists in a DLL. Most likely you didn't arrange for the DLL to share its host's heap. And a Delphi class cannot be passed across a DLL boundary.
If you want to share Delphi classes between modules, you must use packages. Of course, another option is to put all the code in the same module. That is remove the DLL, and compile everything in the executable. The final option is to use valid interop types for DLLs.
Of course, there could be other reasons for the actual error. The code smells bad. For instance, what is this:
DecodeBuffer:=Buff;
Is DecodeBuffer a global variable? If so then it is plausible that you refer to the object after it has been destroyed. Not that I can see evidence of anything being destroyed. Without wishing to seem rude, your code looks like it may have multiple problems. As a matter of urgency you need to:
Deal with the DLL problem described above.
Remove global variables.
Fix lifetime issues. Stop leaking.
Enable range checking to locate buffer overruns.
Add FastMM in debug mode to try to catch heap corruptions.
I think I know what's going on here: I think your stack is getting smashed.
Furthermore, I rather suspect the actual cause is the Decode procedure using an uninitialized variable. Your ShowMessage statement (it would be the first one that matters if I'm right) changes what's on the stack and thus changes the uninitialized variable.
If I'm right this is going to have some heisenbug attributes--anything you do to find out what's going on will change the value of the uninitialized variable.
One thing to try: Declare a large local variable (the idea is to use up stack space) and make sure it's not discarded by the compiler. This will move things in memory and thus likely defuse the blowup. If it works it's pretty conclusive at to what's going on.

finalization section not being run in a dll

I am trying to create a DLL in Delphi XE2 which will popup a form with a TWebBrowser component in it. When the WebBrowser.Navigate2 method is called the finalization section of the unit (or any unit) is not called when the application ends. If Navigate2 is not called, the finalization section happens just fine.
The dll is being called from C++ (VS 2010 MFC console at the moment) and linked via in import library.
There are other ways of doing this, but I would like to reuse the code we already have written.
Does anyone have any idea what is going on?
Thanks.
Here is a simple recreation of the problem:
library DisplayPatientAlertsIntf;
exports DisplayPatientAlertsA name 'DisplayPatientAlertsA#4';
begin
end.
unit uAlertWindow;
interface
uses
Winapi.ActiveX,
Forms,
SHDocVw,
Graphics, Controls;
function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; export; stdcall;
implementation
var ts : TStringList;
function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; export; stdcall;
var Form1 : TForm;
WebBrowser1 : TWebBrowser;
DidCoInit : Boolean;
begin
DidCoInit := Succeeded(CoInitialize(nil));
try
Form1 := TForm.Create(nil);
try
WebBrowser1 := TWebBrowser.Create(nil);
try
WebBrowser1.ParentWindow := Form1.Handle;
WebBrowser1.Align := alClient;
WebBrowser1.Navigate2('file://c:\temp.html');
Form1.ShowModal;
finally
WebBrowser1.Free;
end;
finally
Form1.Free;
end;
finally
if DidCoInit then
CoUninitialize;
end;
Result := 0;
end;
initialization
ts := TStringList.Create;
finalization
ts.Free;
end.
Update 2013.03.19
While solving another problem (dbExpress drivers in a dll), I changed it from a statically linked dll with an import library to a dynamically loaded dll and everything started working.
Do not call CoInitialize() or CoUninitialize() during the DLL's initialization/finalization. That is a very bad place to do that, and besides, it is not the DLL's responsibility to call them anyway. It is the responsibility of the thread that is calling the DLL functions. If you must call them, then at least do so inside of your exported function instead.
As for the exported function itself, use WebBrowser1.Parent instead of WebBrowser1.ParentWindow, use Form1.Free instead of Form1.Release, and get rid of Application.ProcessMessages altogether.
And lastly, do not export the function using a manually decorated name. That is not the DLL's responsibility to do, either. Let the compiler handle the decorating. If there is a naming mismatch when importing the function, that needs to be addressed in the calling app, not the DLL itself.
Your misuse of both COM and the VCL (especially since the problem only occurs once the exported DLL function is called) are likely leading to deadlocks, preventing the DLL from unloading from memory correctly, and thus none of its finalization sections would be called because its DLL entry point is not able to be called. COM is very sensitive when it comes to its initialization/cleanup, so you have to make sure you do it correctly, and in the correct context.
Try this:
library DisplayPatientAlertsIntf;
uses
uAlertWindow;
exports
DisplayPatientAlertsA;
begin
end.
.
unit uAlertWindow;
interface
uses
Winapi.ActiveX,
Forms,
SHDocVw,
Graphics, Controls;
function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; stdcall;
implementation
function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; stdcall;
var
Form1 : TForm;
WebBrowser1 : TWebBrowser;
DidCoInit: Boolean;
begin
Result := 0;
try
DidCoInit = Succeeded(CoInitialize(nil));
try
Form1 := TForm.Create(nil);
try
WebBrowser1 := TWebBrowser.Create(Form1);
WebBrowser1.Parent := Form1;
WebBrowser1.Align := alClient;
WebBrowser1.Navigate2('file://c:\temp.html'); //This contains 'ASDF'
Form1.ShowModal;
finally
Form1.Free;
end;
finally
if DidCoInit then
CoUninitialize;
end;
except
Result := -1;
end;
end;
end.
Delphi does not make heavy use of plain DLLs and its support is basic and scarcely documented
While Delphi makes good work for EXE files, intercepting WinMain and bringing its semantics to Turbo Pascal style context, for DLL you have to do it manually.
Start with reading DLL-Main Microsoft documentation and tutorials.
Then you can add into your DLL.dpr something like
begin
DLLProc := #DLLMain;
DLLMain(DLL_PROCESS_ATTACH);
end.
And then in some unit of DLL you can implement it like this:
procedure DLLMain(dwReason: DWORD);
begin
case dwReason of
DLL_PROCESS_ATTACH:
begin
Application.HelpFile := HelpFileName;
dmSelVars := TdmSelVars.Create(nil);
end {= DLL_PROCESS_ATTACH =};
DLL_PROCESS_DETACH:
begin
Application.Handle := 0;
FreeAndNil(dmSelVars);
g_pSvRec := nil;
end {= DLL_PROCESS_DETACH =};
end {= case =};
end {= DLLMain =};
PS. Why using DLL, when you can use Delphi-native (since 1997) BPL instead ?
It solves many problems and it provides much better finalization support:
for manually-loaded packages (via LoadPackage(...) call) finalization called for all units by granted
for manually-loaded packages (via Project Options / Packages / Link with Runtime packages list ) finalization is called for all units, referenced in "uses" sections of your host EXE.
PPS. Starting MSIE for merely displaying one page - doesn't it look overkill ?
Perhaps native HTML support would suffice, even if limited somewhat ? And it is capable of loading page from TStream or from String w/o tinkering with intermediate temporary files. (Well, MSIE is capable as well, though, after some scaffolding).
You might find that an exception is being raised when one of the units is being finalized, preventing other units from being finalized.
I'm not sure about XE2, but older versions of Delphi tended to be very fussy about the ComObj unit being "high up" in the uses/initialization so it would be one of the last to finalize.
The problem was that if ComObj was finalized too soon, it would CoUninitialize too soon - effectively ripping the rug from under other code that still expected COM to be initialized.
If the XE2 version of SHDocVw still uses ComObj in its implementation section, then ComObj will be initialized relatively 'late'. So that could very well be your problem. In which case simply adding it explicitly and high up in your source should do the trick.

delphi 7, FastMM4 cannot install work around

i am working on an application that uses FastMM4, from sourceforge.net.
So i have added the FastMM4.pas to the uses clause right at the beginning. In the application i need to run a batch file after FinalizeMemoryManager; in the finalization of unit FastMM4; like this
initialization
RunInitializationCode;
finalization
{$ifndef PatchBCBTerminate}
FinalizeMemoryManager;
RunTheBatFileAtTheEnd; //my code here..calling a procedure
{$endif}
end.
then my code for RunTheBatFileAtTheEnd is :
procedure RunTheBatFileAtTheEnd;
begin
//some code here....
sFilePaTh:=SysUtils.ExtractFilePath(applicaTname)+sFileNameNoextension+'_log.nam';
ShellExecute(applcatiOnHAndle,'open', pchar(sExeName),pchar(sFilePaTh), nil, SW_SHOWNORMAL) ;
end;
For this i need to use SysUtils,shellapi in the uses clause of fastmm4 unit. But using them
this message comes
But if i remove SysUtils,shellapi from the uses it works.
I still need all the features of fastmm4 installed but with SysUtils,shellapi, fastmm4 is not installed
I have a unit of my own but its finalization is executed before fastmm4 finalization.
can anyone tell me can how to fix this problem?
EDIT- 1
unit FastMM4;
//...
...
implementation
uses
{$ifndef Linux}Windows,{$ifdef FullDebugMode}{$ifdef Delphi4or5}ShlObj,{$else}
SHFolder,{$endif}{$endif}{$else}Libc,{$endif}FastMM4Messages,SysUtils,shellapi;
my application
program memCheckTest;
uses
FastMM4,
EDIT-2 :
(after #SertacAkyuz answer),i removed SysUtils and it worked , but i still need to run the batch file to open an external application through RunTheBatFileAtTheEnd. The Reason is ..i want a external application to run only after FastMM4 as been out of the finalization. The sExeName is the application that will run the file sFilePaTh(.nam) . can any one tell how to do this? without uninstalling FastMM4.
FastMM checks to see if the default memory manager is set before installing its own by a call to IsMemoryManagerSet function in 'system.pas'. If the default memory manager is set, it declines setting its own memory manager and displays the message shown in the question.
The instruction in that message about 'fastmm4.pas' should be the first unit in the project's .dpr file has the assumption that 'fastmm4.pas' itself is not modified.
When you modify the uses clause of 'fastmm4.pas', if any of the units that's included in the uses clause has an initialization section, than that section of code have to run before the initialization section of 'fastmm4.pas'. If that code requires allocating/feeing memory via RTL, then the default memory manager is set.
Hence you have to take care changing 'fastmm4.pas' to not to include any such unit in the uses clause, like 'sysutils.pas'.
Below sample code (no error checking, file checking etc..) shows how can you launch FastMM's log file with Notepad (provided the log file exists) without allocating any memory:
var
CmdLine: array [0..300] of Char; // increase as needed
Len: Integer;
SInfo: TStartupInfo;
PInfo: TProcessInformation;
initialization
... // fastmm code
finalization
{$ifndef PatchBCBTerminate}
FinalizeMemoryManager; // belongs to fastmm
// Our application is named 'Notepad' and the path is defined in AppPaths
CmdLine := 'Notepad "'; // 9 Chars (note the opening quote)
Len := windows.GetModuleFileName(0, PChar(#CmdLine[9]), 260) + 8;
// assumes the executable has an extension.
while CmdLine[Len] <> '.' do
Dec(Len);
CmdLine[Len] := #0;
lstrcat(CmdLine, '_MemoryManager_EventLog.txt"'#0); // note the closing quote
ZeroMemory(#SInfo, SizeOf(SInfo));
SInfo.cb := SizeOf(SInfo);
CreateProcess(nil, CmdLine, nil, nil, False,
NORMAL_PRIORITY_CLASS, nil, nil, sInfo, pInfo);
{$endif}
end.
I agree with Sertac's answer, but also would like to give a recommendation, if you insist on using SysUtils.pas. The answer is don't use it, and extract what you need out of it and put it in your own copy. Here's what you would need below - ExtractFilePath used LastDeliminator, which used StrScan, and also 2 constants, so I copied them into this new unit and named it MySysUtils.pas.
This is also widely used for people who don't want to have a bunch of extra code compiled which they will never use (You would have to be absolutely sure it's not used anywhere in any units though).
unit MySysUtils;
interface
const
PathDelim = '\';
DriveDelim = ':';
implementation
function StrScan(const Str: PWideChar; Chr: WideChar): PWideChar;
begin
Result := Str;
while Result^ <> #0 do begin
if Result^ = Chr then
Exit;
Inc(Result);
end;
if Chr <> #0 then
Result := nil;
end;
function LastDelimiter(const Delimiters, S: string): Integer;
var
P: PChar;
begin
Result := Length(S);
P := PChar(Delimiters);
while Result > 0 do begin
if (S[Result] <> #0) and (StrScan(P, S[Result]) <> nil) then
Exit;
Dec(Result);
end;
end;
function ExtractFilePath(const FileName: string): string;
var
I: Integer;
begin
I := LastDelimiter(PathDelim + DriveDelim, FileName);
Result := Copy(FileName, 1, I);
end;
end.

Resources