My application is build in delphi and it runs perfect on other platforms except Windows 7 64bit machine. Each and everytime try to close the application is giving me this error
'Unable to write to application file.ini'
here is my code for closing
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
frmMain.close;
end;
This error is usually caused by trying to write to your app's own folder under Program Files, which is not allowed for a non-Administrator under Vista and higher (and XP, if you're not running as an Administrator or Power User).
Here's some code for getting the proper folder for your .INI file:
uses
Windows,
ShlObj; // For SHGetSpecialFolderPath
function GetFolderLocation(Handle: HWnd; Folder: Integer): string;
begin
Result := '';
SetLength(Result, MAX_PATH);
if not SHGetSpecialFolderPath(Handle, PChar(Result), Folder, False) then
RaiseLastOSError;
end;
I use these in my application to retrieve the non-roaming profile folder, and use a sub-folder created beneath that for my app's data. It's set up during the creation of a TDataModule:
procedure TAppData.Create(Sender.TObject);
begin
// DataPath is a property of the datamodule, declared as a string
// CSIDL_LOCAL_APPDATA is the local non-roaming profile folder.
// CSIDL_APPDATA is for the local roaming profile folder, and is more typically used
DataPath := GetFolderLocation(Application.Handle, CSIDL_LOCAL_APPDATA);
DataPath := IncludeTrailingPathDelimiter(DataPath) + 'MyApp\';
end;
See MSDN's documentation page on the meaning of the various CSIDL_ or FOLDERID_ values. The FOLDERID_ values are similar, but are available only on Vista and above and used with SHGetKnownFolderIDList.
For those of you not willing to disregard MS's warnings about SHGetSpecialFolderPath not being supported, here's an alternate version of GetFolderLocation using SHGetFolderPath, which is preferred:
uses
ShlObj, SHFolder, ActiveX, Windows;
function GetFolderLocation(Handle: HWnd; Folder: Integer): string;
begin
Result := '';
SetLength(Result, MAX_PATH);
if not Succeeded(SHGetFolderPath(Handle, Folder, 0, 0, PChar(Result))) then
RaiseLastOSError();
end;
And finally, for those working with only Vista and higher, here's an example using SHGetKnownFolderPath - note this isn't available in pre-XE versions of Delphi (AFAIK-may be in 2009 or 2010), and you'll need to use KNOWNFOLDERID values instead of CSIDL_, like FOLDERID_LocalAppData:
uses
ShlObj, ActiveX, KnownFolders;
// Tested on XE2, VCL forms application, Win32 target, on Win7 64-bit Pro
function GetFolderLocation(const Folder: TGuid): string;
var
Buf: PWideChar;
begin
Result := '';
if Succeeded(SHGetKnownFolderPath(Folder, 0, 0, Buf)) then
begin
Result := Buf;
CoTaskMemFree(Buf);
end
else
RaiseLastOSError();
end;
You should not write ini files to the program directory. Although it worked in the past, it has never been a good practice.
You should be using %APPDATA% for user specific application data.
You might want to read Best practices storing application data
Related
I wrote an unattended HTTPS upload client which shouldn't require user interaction.
Everything was flawless until I deployed it blindly to a remote system that I do not have a remote desktop access to. The tool reported in logs that SSL libraries are missing.
Ok, I deployed LIBEAY32.dll and SSLEAY32.dll to the application folder on the remote system, but then the tool hung and I couldn't figure out what was going on until I wrote a tool which makes a remote screenshot.
On the screenshot I see a modal window from csrss.exe process with a message:
The program can't start because MSVCR120.dll is missing from your computer.
The window appeared despite of having a lot of try except blocks and Application.OnException handler.
I would like that in such cases application would not be hold up so it can report failure to it's log.
How to achieve this?
In a current implementation TIdHttp.Post call just hangs.
P.S. I solved the absence of DLL by copying it to app folder, but my question is about catching such errors.
To avoid this error, you can use the OpenSSL DLLs available on https://indy.fulgan.com/SSL/
They do not have this dependency on MSVCRT.
Or, use TNetHTTPClient.
From this answer:
TNetHTTPClient was introduced in Delphi XE8.
The most important benefit of TNetHTTPClient is that it allows your
application to support HTTPS without having to provide your own
support for SSL/TLS. TNetHTTPClient relies on the SSL/TLS support
provided by the operating system.
Using the information from Remy Lebeau's comment, I experimented with SetErrorMode function in Delphi 6.
The setup consists of three projects:
Host application, that links caller DLL dynamically,
caller DLL, that links worker DLL statically,
and a worker DLL.
At first I place both DLLs in host app folder, test that everything works, and then delete worker DLL and test without it.
Here is some code:
Host application
program MyHost;
uses
Windows, SysUtils;
var
hLib: HMODULE;
procedure Box(msg: WideString);
begin
MessageBoxW(0, PWideChar(msg), 'MyHost Message', 0);
end;
procedure ShowLastError();
begin
Box('LastError: ' + SysErrorMessage(GetLastError()));
end;
type
TDoWork = procedure();
var
DoWork: TDoWork;
begin
SetErrorMode(SEM_FAILCRITICALERRORS);
try
{Without SetErrorMode it displays modal dialog.
No exception is generated.
After clicking at [Ok], it goes to "if hLib = 0".
With SetErrorMode it just goes to "if hLib = 0"}
hLib := LoadLibrary('CallerLib.dll');
if hLib = 0 then begin
ShowLastError();
Halt(1);
end;
try
#DoWork := GetProcAddress(hLib, 'DoWork');
if #DoWork <> nil then DoWork();
finally
FreeLibrary(hLib);
end;
except
on ex: Exception do Box(ex.ClassName + ': ' + ex.Message);
end;
end.
Caller DLL
library CallerLib;
uses
Windows;
//Static linking
procedure Function1(); stdcall; external 'MyLib.dll';
procedure Function2(); stdcall; external 'MyLib.dll';
//To be dynamically linked
procedure DoWork(); stdcall; export;
begin
Function1();
Function2();
end;
exports
DoWork;
end.
Worker DLL
library MyLib;
uses
Windows;
procedure Function1(); stdcall; export;
begin
MessageBox(0, 'MyDLL.Function1', 'MyDLL', 0);
end;
procedure Function2(); stdcall; export;
begin
MessageBox(0, 'MyDLL.Function2', 'MyDLL', 0);
end;
exports
Function1, Function2;
end.
I have written a small DLL in Delphi 10.2 to make use of the FTPS functionality that the Indy components have.
Calling the DLL from a test app, also written in Delphi 10, works 100%. However, calling the DLL from a Delphi 6 app gives me an access violation in borlndmm.dll. Here is a simple example of code to connect to the FTP server.
{The function in my object:}
function TQXFTP.Connect: Boolean;
begin
Result := False;
IdFTP.Host := fHost;
IdFTP.Username := fUserName;
IdFTP.Password := fPassword;
try
IdFTP.Connect;
if IdFTP.Connected then
begin
Result := True;
end;
except
on E:Exception do
begin
fErrorOccurred := True;
fErrorMessage := E.Message;
end;
end;
end;
{Code from my DLL}
function FTPConnect(Host: string;
UserName: string;
PassWord: string): string; stdcall;
var
QXFTP: TQXFTP;
begin
Result := '';
QXFTP := TQXFTP.Create;
try
QXFTP.Host := Host;
QXFTP.UserName := UserName;
QXFTP.PassWord := PassWord;
if QXFTP.Connect then
QXFTP.Disconnect;
if QXFTP.ErrorOccurred then
Result := QXFTP.ErrorMessage
else
Result := '';
finally
FreeAndNil(QXFTP);
end;
end;
exports
FTPConnect;
{Code from my D6/D10 Test App}
function FTPConnect(Host: string;
UserName: string;
PassWord: string): string; stdcall; external QXFTPDLL.dll';
procedure TfrmFTP.btnFTPConnectClick(Sender: TObject);
var
lsFTPResult: string;
begin
lsFTPResult := FTPConnect(edtHost.Text,
edtUserName.Text,
edtPassword.Text);
if lsFTPResult = '' then
ShowMessage('FTP was sucessfull')
else
ShowMessage('FTP Failed: - ' + lsFTPResult);
end;
Replace String type with WideString in exported function declaration. This is the only way, because strings have completely different format and memory layout in Delphi 10.2 and Delphi 6. On the other hand, WideString is just a wraper around Microsoft's BSTR, and is the same in all versions of Delphi.
Thank you very much for everybody's input. I first changed all my strings to PChar. This removed the access violation but I still could not connect to my FTP host. I knew it must've been a string conversion error since hard coding the host, user and pwd in the DLL allowed me to connect even if I call the DLL from my Delphi 6 app. I then changed all the PChar variables & parameters to WideString (as suggested above) and that seems to have solved to problem. I can now connect and copy the files to my FTP server successfully.
The cause for your problem is that you have designed your DLL in a way that it is using memory sharing with the main application. And in order to do so both DLL and main aplication must be using same memory mannager.
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Sharing_Memory
Now Delphi 10 is using FastMM memory manager, but if my memory serves me corectly Delphi 6 is still using old Borland Memory manager. This is then causing incompatibility between DLL memory manager and the one used in your main application.
You might sovle this problem by replacing the old Borland Memory Manager with FastMM memory manager which is open source and can be retrived from here https://github.com/pleriche/FastMM4
For ages, Delphi has supported the Enable runtime themes switch on the Application Settings tab. However, this only works for executables. DLLs are assumed to take over the theming (and other) setings from their parent application.
Unfortunately, Microsoft Office doesn't play nice there. Their 'themed' look is achieved using custom controls, not through Windows' own Common Controls.
In the MSDN article 830033 - How to apply Windows XP themes to Office COM add-ins
Microsoft explains how to apply a manifest to a DLL, making it Isolation Aware such that settings from the parent process are ignored.
Basically, it comes down to two steps:
Include the default manifest resource in your process, using an int-resource id of 2 (as opposed to the 1 you'd normally use).
Compile with the ISOLATION_AWARE_ENABLED define. **Which isn't available in Delphi.**
I think I've got (1) nailed down, although I'm never quite sure whether brcc32 picks up resource IDs as integers or as literal strings. The real problem lies with (2). Supposedly, this define changes several DLL function bindings.
Has anyone solved this problem in Delphi? Should I further investigate this route, should I try and manually creating activation contexts, or are there other elegant solutions to this problem?
I've done this for my COM add-in. I used activation contexts. It's pretty easy for a COM add-in because the surface area of the add-in interface is so small. I could post code but I won't be at a machine with it on until tomorrow. Hope this helps!
UPDATE
As promised, here is the code that I use:
type
(* TActivationContext is a loose wrapper around the Windows Activation Context API and can be used
to ensure that comctl32 v6 and visual styles are available for UI elements created from a DLL .*)
TActivationContext = class
private
FCookie: LongWord;
FSucceeded: Boolean;
public
constructor Create;
destructor Destroy; override;
end;
var
ActCtxHandle: THandle=INVALID_HANDLE_VALUE;
CreateActCtx: function(var pActCtx: TActCtx): THandle; stdcall;
ActivateActCtx: function(hActCtx: THandle; var lpCookie: LongWord): BOOL; stdcall;
DeactivateActCtx: function(dwFlags: DWORD; ulCookie: LongWord): BOOL; stdcall;
ReleaseActCtx: procedure(hActCtx: THandle); stdcall;
constructor TActivationContext.Create;
begin
inherited;
FSucceeded := (ActCtxHandle<>INVALID_HANDLE_VALUE) and ActivateActCtx(ActCtxHandle, FCookie);
end;
destructor TActivationContext.Destroy;
begin
if FSucceeded then begin
DeactivateActCtx(0, FCookie);
end;
inherited;
end;
procedure InitialiseActivationContext;
var
ActCtx: TActCtx;
hKernel32: HMODULE;
begin
if IsLibrary then begin
hKernel32 := GetModuleHandle(kernel32);
CreateActCtx := GetProcAddress(hKernel32, 'CreateActCtxW');
if Assigned(CreateActCtx) then begin
ReleaseActCtx := GetProcAddress(hKernel32, 'ReleaseActCtx');
ActivateActCtx := GetProcAddress(hKernel32, 'ActivateActCtx');
DeactivateActCtx := GetProcAddress(hKernel32, 'DeactivateActCtx');
ZeroMemory(#ActCtx, SizeOf(ActCtx));
ActCtx.cbSize := SizeOf(ActCtx);
ActCtx.dwFlags := ACTCTX_FLAG_RESOURCE_NAME_VALID or ACTCTX_FLAG_HMODULE_VALID;
ActCtx.lpResourceName := MakeIntResource(2);//ID of manifest resource in isolation aware DLL
ActCtx.hModule := HInstance;
ActCtxHandle := CreateActCtx(ActCtx);
end;
end;
end;
procedure FinaliseActivationContext;
begin
if ActCtxHandle<>INVALID_HANDLE_VALUE then begin
ReleaseActCtx(ActCtxHandle);
end;
end;
initialization
InitialiseActivationContext;
finalization
FinaliseActivationContext;
When you want to use this, you simply write code like so:
var
ActivationContext: TActivationContext;
....
ActivationContext := TActivationContext.Create;
try
//GUI code in here will support XP themes
finally
ActivationContext.Free;
end;
You need each entry point that does GUI work to be wrapped in such code.
Note that in my COM add-in DLL I have taken special measures to avoid running code during DLLMain, and so my calls to InitialiseActivationContext and FinaliseActivationContext are not in unit initialization/finalization sections. However, I see no reason why this code would not be safe to place there.
i am trying to store some settings in resource of my application
but failed
i dont want to use ini file or registry methods
i am using this code
var
data :string;
procedure WriteSettings(ServerFile: string; Settings: string);
var
ResourceHandle: THandle;
pwServerFile: PWideChar;
begin
GetMem(pwServerFile, (Length(ServerFile) + 1) * 2);
try
StringToWideChar(ServerFile, pwServerFile, Length(ServerFile) * 2);
ResourceHandle := BeginUpdateResourceW(pwServerFile, False);
UpdateResourceW(ResourceHandle, MakeIntResourceW(10), 'SETTINGS', 0, #Settings[1], Length(Settings) + 1);
EndUpdateResourceW(ResourceHandle, False);
finally
FreeMem(pwServerFile);
end;
end;
function ReadSettings(ServerFile: string): string;
var
ServerModule: HMODULE;
ResourceLocation: HRSRC;
ResourceSize: dword;
ResourceHandle: THandle;
ResourcePointer: pointer;
begin
ServerModule := LoadLibrary(pchar(ServerFile));
try
ResourceLocation := FindResource(ServerModule, 'SETTINGS', RT_RCDATA);
ResourceSize := SizeofResource(ServerModule, ResourceLocation);
ResourceHandle := LoadResource(ServerModule, ResourceLocation);
ResourcePointer := LockResource(ResourceHandle);
if ResourcePointer <> nil then
begin
SetLength(Result, ResourceSize - 1);
CopyMemory(#Result[1], ResourcePointer, ResourceSize);
FreeResource(ResourceHandle);
end;
finally
FreeLibrary(ServerModule);
end;
end;
procedure TForm1.saveClick(Sender: TObject);
begin
writesettings(paramastr(0),'true');
end;
procedure TForm1.ReadClick(Sender: TObject);
begin
data:=readsettings(paramstr(0));
end;
begin
if data='true' then checkbox1.checked:=true;
end
but is nit storing the that i wrote to resource :(
is there any other better options?
any help please
The documentation for BeginUpdateResource clearly states why your code doesn't work (emphasis added):
pFileName [in]
LPCTSTR
The binary file in which to update resources. An application must be able to obtain write-access to this file; the file referenced by pFileName cannot be currently executing. If pFileName does not specify a full path, the system searches for the file in the current directory.
You might have been able to deduce the cause of the error yourself if you were checking the API function's return value and calling GetLastError on failure, like the documentation advises.
You can store settings in a resource, but you can't store settings in a resource of the program whose settings you're trying to store. And now that we've established that you're not allowed to store settings in the program itself, you may as well just abandon the resource idea and use a more conventional method of storing settings in an external location, such as the registry, an INI file, or whatever. You might still wish to read a set of default settings from a resource if you find that the external location doesn't yet have any settings, as might happen after a fresh install.
Having your program modify itself is a bad idea. As a couple people already pointed out, this will fail badly under Vista and Win7 in most cases. It's better not to fight the operating system. Windows already provides a couple different ways for your program to store its settings. You can drop an INI or other config file in some folder outside of Program Files, or you can store it in the Registry, which is probably the best option.
I write a component which should store some information relative to the project directory. Every time a property of my component is changed it should write a file. So how can a component determine the current project directory at design time.
Thanks in advance
EDIT:
I want to generate a delphi source file every time a property of my component is changed, so that I always get the latest version when I compile my code. Think of it as a kind of code generator.
At the moment I set whole path and filename where the source should be stored but I prefer a relative path to the project (or the form/datamodule which contains my component) to make it easier to copy the project on different developer machines.
Thanks for the hints. Open Tools API is the way to go and using the Open Tools API from a component on a form at designtime is possible.
So here is my solution:
I need two units, one for the component and one for registering the component and the code which use the Open Tools API.
Here comes the component unit:
unit TestLabels;
interface
uses
SysUtils, Classes, Windows, Controls, StdCtrls;
type
TTestLabel = class(TLabel)
private
FTestProperty: Boolean;
procedure SetTestProperty(const Value: Boolean);
procedure Changed;
published
property TestProperty: Boolean read FTestProperty write SetTestProperty;
end;
var
OnGetUnitPath: TFunc;
implementation
{ TTestLabel }
procedure TTestLabel.Changed;
begin
if not (csDesigning in ComponentState) then
Exit; // I only need the path at designtime
if csLoading in ComponentState then
Exit; // at this moment you retrieve the unit path which was current before
if not Assigned(OnGetUnitPath) then
Exit;
// only for demonstration
Caption := OnGetUnitPath;
MessageBox(0, PChar(ExtractFilePath(OnGetUnitPath)), 'Path of current unit', 0);
end;
procedure TTestLabel.SetTestProperty(const Value: Boolean);
begin
if FTestProperty Value then
begin
FTestProperty := Value;
Changed;
end;
end;
end.
Here is the unit for registering the component and the call to the Open Tools API:
unit TestLabelsReg;
interface
uses
SysUtils, Classes, Controls, StdCtrls, TestLabels;
procedure register;
implementation
uses
ToolsAPI;
function GetCurrentUnitPath: String;
var
ModuleServices: IOTAModuleServices;
Module: IOTAModule;
SourceEditor: IOTASourceEditor;
idx: integer;
begin
Result := '';
SourceEditor := nil;
if SysUtils.Supports(BorlandIDEServices, IOTAModuleServices,
ModuleServices) then
begin
Module := ModuleServices.CurrentModule;
if System.Assigned(Module) then
begin
idx := Module.GetModuleFileCount - 1;
// Iterate over modules till we find a source editor or list exhausted
while (idx >= 0) and not SysUtils.Supports(Module.GetModuleFileEditor(idx), IOTASourceEditor, SourceEditor) do
System.Dec(idx);
// Success if list wasn't ehausted.
if idx >= 0 then
Result := ExtractFilePath(SourceEditor.FileName);
end;
end;
end;
procedure register;
begin
RegisterComponents('Samples', [TTestLabel]);
TestLabels.OnGetUnitPath := GetCurrentUnitPath;
end;
end.
Starting with delphi 7, the ToolsAPI unit defines a getActiveProject function which returns the IOTAProject interface of the current project.
The fileName property of IOTAProject returns the full path of the main source file of the project (typically the .dpr file).
So, in many cases, it's possible to use a simple instruction such as:
if csDesigning in componentState then
appFolderPath := extractFilePath( getActiveProject.fileName )
else
appFolderPath := extractFilePath( application.exeName );
(and there's no need of using two units as in Heinz's example above)
A component cannot access your source path, because a component is placed in your application and run as a part of your application out of Delphi IDE.
If you want to have access to project path, or automate any process inside IDE; you have to write an IDE expert using OpenTools API, not a component.
I don't think it can. You can determine the directory your EXE is running in easily enough, but your component at design-time is running as a part of the IDE. I doubt there's a way for the component to access project information through the IDE.
see
http://www.gexperts.org/otafaq.html#project
and then
www.href.com/pub/sw/ProjectOptions.html
may be it helps
I had A little bit different objective: how to know where the project exe will be at design time, so that the application at runtime can share some files with the design time functionality.
So, I combined the two best answers and created the following function to be used in applications:
unit MyUtils;
interface
function GetProjectTargetPath: string;
var
_GetProjectTargetPath: TFunc<string>;
...
implementation
function GetProjectTargetPath: string;
begin
Result := '';
if Assigned(_GetProjectTargetPath) then
Result := _GetProjectTargetPath;
if Result <> '' then
Result := IncludeTrailingPathDelimiter(Result);
end;
and then in the component registration unit (which I have separate and linked only to the design time package):
unit MyUtils;
...
implementation
function GetProjectTargetPath: String;
begin
Result := ExtractFilePath( GetActiveProject.ProjectOptions.TargetName );
end;
procedure Register;
begin
...
MyUtils._GetProjectTargetPath := GetProjectTargetPath;
end;
Thus avoiding the actual unit to depend on ToolsAPI.
MyUtils.GetProjectTargetPath returns an empty string at runtime, which corresponds to the application exe-directory in practice.