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.
Related
I want to run a command that the user defined in an INI file.
The commands can be EXE files, or other files (e.g. DOC files) and parameters should be allowed.
Since WinExec() can handle arguments (e.g. "cmd /?"), but ShellExec() can handle Non-EXE files (e.g. "Letter.doc"), I am using a combination of these both.
I am concerned about future Windows versions, because WinExec() is deprecated, and even from the 16 bit era.
Here is my current function:
procedure RunCMD(cmdLine: string; WindowMode: integer);
procedure ShowWindowsErrorMessage(r: integer);
begin
MessageDlg(SysErrorMessage(r), mtError, [mbOK], 0);
end;
var
r, g: Cardinal;
begin
// We need a function which does following:
// 1. Replace the Environment strings, e.g. %SystemRoot% --> ExpandEnvStr
// 2. Runs EXE files with parameters (e.g. "cmd.exe /?") --> WinExec
// 3. Runs EXE files without path (e.g. "calc.exe") --> WinExec
// 4. Runs EXE files without extension (e.g. "calc") --> WinExec
// 5. Runs non-EXE files (e.g. "Letter.doc") --> ShellExecute
// 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes.
cmdLine := ExpandEnvStr(cmdLine);
// TODO: Attention: WinExec() is deprecated, but there is no acceptable replacement
g := WinExec(PChar(cmdLine), WindowMode);
r := GetLastError;
if g = ERROR_BAD_FORMAT then
begin
// e.g. if the user tries to open a non-EXE file
ShellExecute(0, nil, PChar(cmdLine), '', '', WindowMode);
r := GetLastError;
end;
if r <> 0 then ShowWindowsErrorMessage(r);
end;
function ExpandEnvStr(const szInput: string): string;
// http://stackoverflow.com/a/2833147/3544341
const
MAXSIZE = 32768;
begin
SetLength(Result, MAXSIZE);
SetLength(Result, ExpandEnvironmentStrings(pchar(szInput),
#Result[1],length(Result)));
end;
Microsoft recommends using CreateProcess(), but I do not accept it as a real replacement for WinExec().
For example, given following command line:
"C:\Program Files\xyz.exe" /a /b /c
Since ShellExecute() and CreateProcess() require a strict separation of command and arguments, I would have to parse this string myself. Is that really the only way I can go? Has someone written a public available code featuring this functionality?
Additional note: The process should not be attached to the caller. My program will close, right after the command has started.
CreateProcess() is the replacement for WinExec(). The documentation explicitly states as much.
And BTW, the error handling in your original code is completely wrong. You are misusing GetLastError(). In fact, neither WinExec() nor ShellExecute() even report errors with GetLastError() to begin with. So, even if WinExec() or ShellExecute() are successful (and you are not even checking if ShellExecute() succeeds or fails), you risk reporting random errors from earlier API calls.
Try something more like this:
procedure RunCMD(cmdLine: string; WindowMode: integer);
procedure ShowWindowsErrorMessage(r: integer);
var
sMsg: string;
begin
sMsg := SysErrorMessage(r);
if (sMsg = '') and (r = ERROR_BAD_EXE_FORMAT) then
sMsg := SysErrorMessage(ERROR_BAD_FORMAT);
MessageDlg(sMsg, mtError, [mbOK], 0);
end;
var
si: TStartupInfo;
pi: TProcessInformation;
sei: TShellExecuteInfo;
err: Integer;
begin
// We need a function which does following:
// 1. Replace the Environment strings, e.g. %SystemRoot% --> ExpandEnvStr
// 2. Runs EXE files with parameters (e.g. "cmd.exe /?") --> WinExec
// 3. Runs EXE files without path (e.g. "calc.exe") --> WinExec
// 4. Runs EXE files without extension (e.g. "calc") --> WinExec
// 5. Runs non-EXE files (e.g. "Letter.doc") --> ShellExecute
// 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes.
cmdLine := ExpandEnvStr(cmdLine);
{$IFDEF UNICODE}
UniqueString(cmdLine);
{$ENDIF}
ZeroMemory(#si, sizeof(si));
si.cb := sizeof(si);
si.dwFlags := STARTF_USESHOWWINDOW;
si.wShowWindow := WindowMode;
if CreateProcess(nil, PChar(cmdLine), nil, nil, False, 0, nil, nil, si, pi) then
begin
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
Exit;
end;
err := GetLastError;
if (err = ERROR_BAD_EXE_FORMAT) or
(err = ERROR_BAD_FORMAT) then
begin
ZeroMemory(#sei, sizeof(sei));
sei.cbSize := sizeof(sei);
sei.lpFile := PChar(cmdLine);
sei.nShow := WindowMode;
if ShellExecuteEx(#sei) then Exit;
err := GetLastError;
end;
ShowWindowsErrorMessage(err);
end;
Although similar ShellExecute and CreateProcess serve a different purpose.
ShellExecute can open non executable files. It looks up the information in the registry for the associated program for the given file and will execute it. ShellExecute is also great for launching the default web browser and you can pass a URL to it it.
So if you were to pass an TXT file to ShellExecute it would open the associated program such as notepad. However it would fail with CreateProcess.
CreateProcess is a lower level function, that allows you to have better control over the input and output from the the process. For example you can call command line programs that have text output with CreateProcess and capture that output and react according.
Given the concerns you have you will need to use ShellExecute. You will however need to split the command from the parameters. This is would be the first non escaped whitespace character.
I personally rarely call ShellExecute or CreateProcess directly. I tend to use on of the following functions from the JCL that wrap these functions.
JclMiscel.pas
CreateDosProcessRedirected
WinExec32
WinExec32AndWait
WinExecAndRedirectOutput
CreateProcessAsUser
CreateProcessAsUserEx
JclShell.pas
ShellExecEx
ShellExec
ShellExecAndWwait
RunAsAdmin
JclSysUtils.pas
Execute (8 Overloaded versions and is the one I use the most)
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.
I want to be able to determine if a particular unit has been compiled into a Delphi program, e.g. the unit SomeUnitName is part of some of my programs but not of others. I would like to have a function
function IsSomeUnitNameInProgram: boolean;
(which is of course not declared in SomeUnitName because in that case it would always be included) that at runtime returns true, if the unit has been compiled into the program, and false, if not.
My thoughts so far have gone along the lines of using the jcl debug information (compiled from a detailed map file) which I basically add to all my programs to determine this information, but I would prefer it, if jcl were not required.
Adding code to SomeUnitName is not an option.
This is currently for Delphi 2007 but preferably should also work for Delphi XE2.
Any thoughts?
some background on this since #DavidHeffernan asked:
This is not only for one program but for more than 100 different ones. Most of them are used internally but some also get delivered to customers. Since we use quite a few libraries, some bought others under various open source licenses, I wanted to be able to add a "credits" tab to the about box which displays only those libraries actually compiled into the program rather than all of them. Thanks to the answer from TOndrej this works now exactly as I wanted it to:
The code checks for a unit which is always linked if a library is used by the program and if it is there, it adds the library name, the copyright and a link to it to the about box.
Unit names are compiled into the 'PACKAGEINFO' resource where you can look it up:
uses
SysUtils;
type
PUnitInfo = ^TUnitInfo;
TUnitInfo = record
UnitName: string;
Found: PBoolean;
end;
procedure HasUnitProc(const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer);
begin
case NameType of
ntContainsUnit:
with PUnitInfo(Param)^ do
if SameText(Name, UnitName) then
Found^ := True;
end;
end;
function IsUnitCompiledIn(Module: HMODULE; const UnitName: string): Boolean;
var
Info: TUnitInfo;
Flags: Integer;
begin
Result := False;
Info.UnitName := UnitName;
Info.Found := #Result;
GetPackageInfo(Module, #Info, Flags, HasUnitProc);
end;
To do this for the current executable pass it HInstance:
HasActiveX := IsUnitCompiledIn(HInstance, 'ActiveX');
(GetPackageInfo enumerates all units which may be inefficient for executables with many units, in that case you can dissect the implementation in SysUtils and write your own version which stops enumerating when the unit is found.)
This function will return the list of unit names included in an application. Works in Delphi 2010. Not verified for other compilers.
function UnitNames: TStrings;
var
Lib: PLibModule;
DeDupedLibs: TList<cardinal>;
TypeInfo: PPackageTypeInfo;
PInfo: GetPackageInfoTable;
LibInst: Cardinal;
u: Integer;
s: string;
s8: UTF8String;
len: Integer;
P: PByte;
begin
result := TStringList.Create;
DeDupedLibs := TList<cardinal>.Create;
Lib := LibModuleList;
try
while assigned( Lib) do
begin
LibInst := Lib^.Instance;
Typeinfo := Lib^.TypeInfo;
if not assigned( TypeInfo) then
begin
PInfo := GetProcAddress( LibInst, '#GetPackageInfoTable');
if assigned( PInfo) then
TypeInfo := #PInfo^.TypeInfo;
end;
if (not assigned( TypeInfo)) or (DeDupedLibs.IndexOf( LibInst) <> -1) then continue;
DeDupedLibs.Add( LibInst);
P := Pointer( TypeInfo^.UnitNames);
for u := 0 to TypeInfo^.UnitCount - 1 do
begin
len := P^;
SetLength( s8, len);
if len = 0 then Break;
Inc( P, 1);
Move( P^, s8[1], len);
Inc( P, len);
s := UTF8ToString( s8);
if Result.IndexOf( s) = -1 then
Result.Add( s)
end
end
finally
DeDupedLibs.Free
end
end;
Example to use in the was suggested in the question...
function IsSomeUnitNameInProgram: boolean;
var
UnitNamesStrs: TStrings;
begin
UnitNamesStrs := UnitNames;
result := UnitNamesStrs.IndexOf('MyUnitName') <> -1;
UnitNamesStrs.Free
end;
I'm using this code to inject my function, but it causes the target process to crash. Does anyone know why?
program Sky;
{$IMAGEBASE $13140000}
uses
Unit2 in 'Unit2.pas',
chstrDec in 'chstrDec.pas',Psapi,
unitinject in 'unitinject.pas', ShellAPI,dialogs,registry, Windows, Messages, tlhelp32, SysUtils, Variants, Classes, Graphics, Controls, Forms;
{$R *.res}
function GetProcessID(ProcessName:string):Integer;
var
Handle:tHandle;
Process:tProcessEntry32;
GotProcess:Boolean;
begin
Handle:=CreateToolHelp32SnapShot(TH32CS_SNAPALL,0) ;
Process.dwSize:=SizeOf(Process);
GotProcess := Process32First(Handle,Process);
{$B-}
if GotProcess and (Process.szExeFile<>ProcessName) then
repeat
GotProcess := Process32Next(Handle,Process);
until (not GotProcess) or (Process.szExeFile=ProcessName);
{$B+}
if GotProcess then Result := Process.th32ProcessID
else Result := 0;
CloseHandle(Handle);
end;
{$IMAGEBASE $13140000}
function Main(dwEntryPoint: Pointer): longword; stdcall;
var
s : String;
begin
ShowMessage('hi');
Result := 0;
Sleep(2000);
Main(dwEntryPoint);
end;
var
x:pointer;
Handle:tHandle;
PID:Cardinal;
begin
Pid:=getProcessID('calc.exe');
Handle := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
Inject(Handle,#Main);
CloseHandle(Handle);
end.
//inject
procedure Inject(ProcessHandle: longword; EntryPoint: pointer);
var
Module, NewModule: Pointer;
Size, BytesWritten, TID: longword;
begin
Module := Pointer(GetModuleHandle(nil));
Size := PImageOptionalHeader(Pointer(integer(Module) + PImageDosHeader(Module)._lfanew + SizeOf(dword) + SizeOf(TImageFileHeader))).SizeOfImage;
VirtualFreeEx(ProcessHandle, Module, 0, MEM_RELEASE);
NewModule := VirtualAllocEx(ProcessHandle, Module, Size, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(ProcessHandle, NewModule, Module, Size, BytesWritten);
CreateRemoteThread(ProcessHandle, nil, 0, EntryPoint, Module, 0, TID);
end;
Err, GetModuleHandle(nil) is going to be value for your process, not the target process. Even if the values happen to be the same (or even if they are not) VirtualFreeEx ing that memory out from under the process is a bad idea, it might be you know, in the middle of executing some code there. That's the first place I see that can cause a potential crash. But let's assume that works somehow. So you allocate some new memory to scribble your code in, which you do. But you haven't relocated if you've needed to, and you also directly use EntryPoint, again not relocated. Why don't you use one of the "easy" code injection methods like a window hook?
Here are some examples:
http://www.codeproject.com/KB/threads/winspy.aspx
They are in C++, but you seem capable of "Delphi-ifying" them.
A simplification of what you are doing currently can be achieved by writing a DLL containing the code you want to inject, and using LoadLibrary to load it (by way of CreateRemoteThread). You use VirtualAllocEx to allocate space for the DLL name, WriteProcessMemory to write it over, and GetModuleHandle("kernel32.dll") for the handle to use with CreateRemoteThread and GetProcAddress("LoadLibraryW") (or LoadLibraryA) to pass to CreateRemoteThread. You should definitely never release memory you haven't allocated like you're currently doing. Every process is guaranteed to have kernel32 loaded in the same place (even with ASLR), so by bootstrapping with LoadLibrary you avoid a lot of the issues you'd have to deal with to get something like your current code working reliably.
I've heard that some custom component authors use an RTL routine that checks to see if Delphi is running in order to set up shareware restrictions. Does anyone know what this routine is? Checking obvious names like "DelphiRunning" or "IsDelphiRunning" doesn't turn up anything useful.
There are 2 different ideas here:
- Delphi is up and running
- The application is running under the debugger
The common way to test if Delphi is running is to check the presence of known IDE Windows which have a specific classname like TAppBuilder or TPropertyInspector.
Those 2 works in all version of Delphi IIRC.
If you want to know if your application is running under the debugger, i.e. launched normally from the IDE with "Run" (F9) or attached to the debugger while already running, you just have to test the DebugHook global variable.
Note that "Detach from program" does not remove the DebugHook value, but "Attach to process" sets it.
function IsDelphiRunning: Boolean;
begin
Result := (FindWindow('TAppBuilder', nil) > 0) and
(FindWindow('TPropertyInspector', 'Object Inspector') > 0);
end;
function IsOrWasUnderDebugger: Boolean;
begin
Result := DebugHook <> 0;
end;
If the goal is to restrict the use of a trial version of your component to when the application is being developped, both have flaws:
- Hidden windows with the proper Classname/Title can be included in the application
- DebugHook can be manually set in the code
You can use DebugHook <> 0 from your component code. DebugHook is a global variable (IIRC, it's in the Systems unit) that's set by the Delphi/RAD Studio IDE, and couldn't be set from anywhere else.
There are other techniques (FindWindow() for TAppBuilder, for instance), but DebugHook takes all of the work out of it.
This is a code snippet from www.delphitricks.com/source-code/misc/check_if_delphi_is_running.html.
function WindowExists(AppWindowName, AppClassName: string): Boolean;
var
hwd: LongWord;
begin
hwd := 0;
hwd := FindWindow(PChar(AppWindowName), PChar(AppClassName));
Result := False;
if not (Hwd = 0) then {window was found if not nil}
Result := True;
end;
function DelphiLoaded: Boolean;
begin
DelphiLoaded := False;
if WindowExists('TPropertyInspector', 'Object Inspector') then
if WindowExists('TMenuBuilder', 'Menu Designer') then
if WindowExists('TAppBuilder', '(AnyName)') then
if WindowExists('TApplication', 'Delphi') then
if WindowExists('TAlignPalette', 'Align') then
DelphiLoaded := True;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if DelphiLoaded then
begin
ShowMessage('Delphi is running');
end;
end;
function DelphiIsRunning: Boolean;
begin
Result := DebugHook <> 0;
end;