2

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;
TLama
  • 75,147
  • 17
  • 214
  • 392
user1484977
  • 21
  • 1
  • 3
  • 4
    This code is wrong, BTW. Remove `frmMain`, and just use `Close`. Adding `frmMain` ties it to a single instance of the form instead of working on any instance. If you **must** qualify it for some reason, use `Self` instead of a specific variable name. – Ken White Jun 27 '12 at 12:46
  • 1
    Its wrong to call `Close()` from inside the `OnClose` event handler at all, as `Close()` is already running so this creates an endlesss recursive loop. – Remy Lebeau Jun 27 '12 at 21:09

2 Answers2

10

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;
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • 3
    It would be very unusual to use `CSIDL_LOCAL_APPDATA` since that is non-roaming. Use `CSIDL_APPDATA` instead. Also it is **far** easier to call `SHGetSpecialFolderPath` instead and avoid the PIDL dance, if you are prepared to ignore the dire MS warnings that `SHGetSpecialFolderPath` is unsupported! – David Heffernan Jun 27 '12 at 13:17
  • @David, good catch. Pre-`RaiseLastOSError` versions of Delphi did it with `SysErrorMessage` instead, and the old code snippet lib still had that in it (along with some other old stuff, as you saw). It's been updated now. :-) – Ken White Jun 27 '12 at 13:49
  • There is nothing wrong with using `CSIDL_LOCAL_APPDATA` if you don't need to support roaming. And `SHGetSpecialFolderPath()` is not supported by MS anymore, use `SHGetFolderPath()` instead. – Remy Lebeau Jun 27 '12 at 21:12
5

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

Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146