I get the common app data folder to store my application related files. Eg log files and ini config files. I use the following code:
const
CSIDL_COMMON_APPDATA = $0023;
function TSpecialFolders.GetSpecialFolder(
const ASpecialFolderID: Integer): string;
var
vSFolder : pItemIDList;
vSpecialPath : array[0..MAX_PATH] of Char;
begin
SHGetSpecialFolderLocation(0, ASpecialFolderID, vSFolder);
SHGetPathFromIDList(vSFolder, vSpecialPath);
Result := vSpecialPath;
end;
function TSpecialFolders.GetCommonDocumentsFolder: string;
begin
result := GetSpecialFolder( CSIDL_COMMON_DOCUMENTS );
end;
This works fine on my Windows 7 box and allows my app to write folders and files.
On a customers PC (I think its an older version of windows, because the screen shot has the word "start" on the start button) my app is unable to write files into that path.
On my PC the returned path is:
C:\ProgramData\
with me adding extra folders like so:
mycompany\myapp\logs\
mycompany\myapp\db\
with text files being saved in those folders.
On my customer's PC the function returns:
C:\Documents and Settings\All Users\Application Data
I am unable to write text files into the directory structure there.
Is there a better function I should be using, or a better common file folder?
Edit for SilverWarrior
these are the special folders on my customer's PC.
AppDataFolder : C:\Documents and Settings\Admin\Application Data
CommonAppDataFolder : C:\Documents and Settings\All Users\Application Data
CommonDocumentsFolder : C:\Documents and Settings\All Users\Documents
LocalAppDataFolder : C:\Documents and Settings\Admin\Local Settings\Application Data
MyDocumentsFolder : C:\Documents and Settings\Admin\My Documents
The LocalAppDataFolder has "Admin" in the path suggesting that admin rights would be needed. Is that right?
The CSIDL_COMMON_APPDATA folder is protected by default and only adminstrators have write access to it.
To gain access for all users the administrator has to create a directory inside and grant the needed rights to the users. This task is typically done by an installer for your application (fi InnoSetup has also an option to grant the needed rights to such folders).
The problem is that you are trying to save that data into AppData folder which is intended for all users. By default writing into this folder requires Administrative rights.
So instead of using CSIDL_COMMON_APPDATA (AppData folder for all users) use CSIDL_LOCAL_APPDATA (curent users AppData folder). Writing to curent user AppData folder does not require elevated rights so it shoud work fine.
Well, armed with info from here and a double-hop remote desktop connection to my customer's pc I went in prepared to do battle with the Windows permissions system - only to find that the problem was due to the application's ini file was set to read only.
Clickity click. Problem solved.
All comments about the common app data folder being restricted may still apply as the customer is running his account as administrator on XP SP3.
Thank you for your help.
Related
I am working on a project where I need to play a video from a file in Delphi. I often work from home and school, and I have the problem that at home, my USB is drive 'J' and at school my USB is drive 'D'.
I manually go and change it every time. Is there a way for Delphi to automatically get the video from where ever it is?
Each sector has an image component laid over it for selecting the sector.
*Note, I know I can search for a specific file's location in Delphi, but I have over 24 different places where I need to play different videos, so searching would probably be my last resort, unless I use a procedure and set constants for each sector to differenciate between them.
The code currently looks as follows:
procedure TtForm.imgSector1Click(Sender: TObject);
begin
//Variables,this is for initializing them when I create them later.
//Procedures
SectorDeselect; //Procedure to turn all sector borders white
// Video
WindowsMediaPlayer1.Controls.stop;
WindowsMediaPlayer1.URL := 'J:\IT\PAT\phase 2\Videos\Footage1.mp4'; //Where my problem lies
WindowsMediaPlayer1.Controls.Play;
// Sector Info. The memos and Rich edits
redSectorInfo.Lines.Clear;
redSectorInfo.Lines.Add('');
// Sector. Highlighting the sector borders surrounding the sector
SectorBordr1.Brush.Color := clGreen;
SectorBorder10.Brush.Color := clGreen;
end;
I would suggest adding a TEdit control in your app's UI to let you specify the base drive/path for the files on the machine the app is currently running on. Your code can then construct individual file paths at runtime that are relative to that base path. Don't use hard-code paths in your code.
You can then save that base path into the Windows Registry in a new key you create, ie HKEY_CURRENT_USER\Software\MyApp. Or, you can save the path in a configuration file (INI, XML, JSON, etc) created in a subfolder in your Windows user profile, like %APPDATA%\MyApp. Your code can then read in that base path each time the app is run.
If the files are stored on a USB drive, an alternative solution would be to simply enumerate the available drives at runtime, such as with GetLogicalDriveStrings(). For each drive, append a relative path for a given file onto the end of it, and then check if that file exists, such as with FileExists(). If so, you now know which drive to use for all of the files until the next time your app is run (you can save the drive path between runs, as described above). If the file is not found, move on to the next drive.
What about adding a parameter on the CommandLine?
Start
D:\myfolder\myfile D
OR
Start
J:\myfolder\myfile J
GUI files can accept a parameter. Capture it with code like:
DriveLetter := ParamStr(1);
I have created an application that manage an embedded database
My Customer want this application to be in one file
My Task is to modify my application so it can extract database file from exe , edit it ,and include if again at run time not in compile time
An executable file cannot be modified while the executable is running. Which means that in order to achieve your goal you would need another process. You could do the following:
Start your process.
Extract the DB from the process image.
Make changes to the DB.
Extract another executable file from the original image.
Start a second process based on this extracted images.
Terminate the first process.
Have the second process update the disk image with the modified DB.
Frankly this is a quite terrible idea. Don't even attempt this. The complexity serves no useful purpose, and the whole concept feels brittle.
Keep the data in a file separate from the program, as nature intended.
This is of course a bad idea, just think of what virusscanners are going to think of this approach. Also what happens if the exe crashes, will your db now lose all of its updates?
You can have the exe create a self extracting archive containing all the files needed.
This works as follows (the steps are the same as #David above, except that the components listed do most of the work for you).
Extract self extracting zip.
this contains: the real exe to be started upon extract
the database
some files needed to recreate a new self extracting exe
Upon closing the program it will create a new zip file, including:
Itself (in readonly form)
The database
some files needed to recreate a new self extracting exe
It will then transform the zip-file into a new self-extracting exe
the new self-extracting archive will start the exe included in it's embedded zip file as per #1.
Here's some sample code from sfx-zip-delphi.
program SelfExtractingZip;
{$APPTYPE CONSOLE}
uses
// Add a ZipForge unit to the program
SysUtils, ZipForge, Classes;
var
archiver : TZipForge;
begin
// Create an instance of the TZipForge class
archiver := TZipForge.Create(nil);
try
with archiver do
begin
// Set the name of the archive file we want to create.
// We set extension to exe because we create a SFX archive
FileName := 'C:\test.exe';
// See SFXStub demo in ZipForge\Demos\Delphi folder
// to learn how to create a SFX stub
SFXStub := 'C:\SFXStub.exe';
// Because we create a new archive,
// we set Mode to fmCreate
OpenArchive(fmCreate);
// Set base (default) directory for all archive operations
BaseDir := 'C:\';
// Add the C:\test.txt file to the archive
AddFiles('c:\test.txt');
CloseArchive();
end;
except
on E: Exception do
begin
Writeln('Exception: ', E.Message);
// Wait for the key to be pressed
Readln;
end;
end;
end.
Solutions for self extracting exe's
Paid
If you want a paid solution: http://www.componentace.com/sfx-zip-delphi.htm
Free
Or free: http://tpabbrevia.sourceforge.net/Self-Extracting_Archives
The abbrevia docs for self-extracting files are here: https://sourceforge.net/projects/tpabbrevia/postdownload?source=dlp
See page 293.
I've got some code to install a post-script based virtual printer with a port monitor (for printing to PDF). The code works fine on x86 and x64 platforms from WinXP to Win7, unless the PScript5 set of files isn't in the "root" drivers folder. On a few of my test PCs the files were already there, but on a newer Win7 PC I have the files were not already there.
For example, since I know the above is clear as mud, on Windows XP 32-bit, if these files:
ps5ui.dll
pscript5.dll
pscript.hlp
pscript.ntf
Are located in c:\windows\system32\spool\drivers\w32x86\, then my code works. If they aren't, my code fails. The files are always in c:\windows\system32\spool\drivers\w32x86\3\, and the outcome is the same (apparently Windows doesn't look in the "3" sub-folder).
Do I need to copy them from the 3 sub-folder -- is this what others are doing? Doesn't seem like "good practice" for some reason. According to this on MSDN, I can maybe redistribute the files, but I need to contact Microsoft I guess, and I can't figure out how to do that (links are weird, typical).
This is my (cleaned up) code as it runs on Win7 64-bit (32-bit just uses "Windows NT x86" instead of "Windows x64"):
DRIVER_INFO_3 di;
memset(&di,0,sizeof(di));
di.cVersion = 3;
di.pName = "My PDF Printer";
di.pEnvironment = "Windows x64";
di.pDriverPath = "pscript5.dll";
di.pDataFile = "mypdf.ppd";
di.pConfigFile = "ps5ui.dll";
di.pHelpFile = "pscript.hlp";
di.pDependentFiles = "pscript.ntf\0\0";
di.pMonitorName = NULL;
di.pDefaultDataType = "RAW";
if(!AddPrinterDriverEx(NULL,3,(BYTE*)&di,APD_COPY_ALL_FILES|APD_INSTALL_WARNED_DRIVER))
{
char err[1024];
sprintf(err,"Error adding printer driver: 0x%08X",GetLastError());
Prompt(err);
return;
}
AddPrinterDriverEx fails with error code 2, file not found, if any of the above files are not in the root folder. If I copy the files from the "3" sub-folder and then run the exact code again, it works. I've tried without the APD_COPY_ALL_FILES flag also, same error (2) if files not found, and some other error if they are there (I assume an error code meaning files already exist, shouldn't matter as not related to real issue anyway).
You don't need to contact Microsoft; you can freely redistribute the PScript5 files. However, to use AddPrinterDriverEx you must ensure that all required files are in the \windows\system32\spool\drivers\w32x86 folder, and you shouldn't assume they'll be in the \windows\system32\spool\drivers\w32x86\3 folder to copy from. You should provide a copy of them with your installer and copy them there yourself before calling AddPrinterDriverEx.
Are you sure you can freely redistribute the pscript5 files?
According to this article from Xeros you must ask to Microsoft to redistribute them:
Other manufacturers such as Xerox can obtain redistribution rights
for this file and can then incorporate this DLL with their software
applications and print drivers for Microsoft operating systems.
I am able to open a CHM file by passing a ShortInteger and casting it as a Word for the dwData parameter. I.E.
Unit Help; //this is where the Id's are set with their description
Interface
Const
Address_File = 35; //delphi identifies Address_File as a shortint
etc..
Call get help pass my ID
GetHelp(Address_File); //call get help pass my ID to open to the Address_File topic
GetHelp procedure
procedure GetHelp(HelpID : Word);
begin
Application.HelpFile := ProgramPath + 'help.chm';
HtmlHelpW(0, PWideChar(Application.HelpFile),HH_HELP_CONTEXT , HelpID);
end;
HtmlHelpW function
function HtmlHelpW(hwndCaller : HWND; pszFile: PWideChar; uCommand : Integer;
dwData : DWORD) : HWND; stdcall; external 'hhctrl.ocx' name 'HtmlHelpW';
As I pass different ShortIntegers I am able to initialize the help file at different sections.
However I can't figure out how the values are mapped. There are some sections in the chm file that I want to be able to map to but the short Integer or context ID associated with them is not documented in the program or is not mapped.
Free Pascal comes with a chmls.exe util that has a command that tries to recover the alias (context) data:
chmls, a CHM utility. (c) 2010 Free Pascal core.
Usage: chmls [switches] [command] [command specific parameters]
Switches :
-h, --help : this screen
-p, --no-page : do not page list output
-n,--name-only : only show "name" column in list output
Where command is one of the following or if omitted, equal to LIST.
list <filename> [section number]
Shows contents of the archive's directory
extract <chm filename> <filename to extract> [saveasname]
Extracts file "filename to get" from archive "filename",
and, if specified, saves it to [saveasname]
extractall <chm filename> [directory]
Extracts all files from archive "filename" to directory
"directory"
unblockchm <filespec1> [filespec2] ..
Mass unblocks (XPsp2+) the relevant CHMs. Multiple files
and wildcards allowed
extractalias <chmfilename> [basefilename] [symbolprefix]
Extracts context info from file "chmfilename"
to a "basefilename".h and "basefilename".ali,
using symbols "symbolprefix"contextnr
extracttoc <chmfilename> [filename]
Extracts the toc (mainly to check binary TOC)
extractindex <chmfilename> [filename]
Extracts the index (mainly to check binary index)
This might be a start, since at least you'll know which pages are exported using an ID, and maybe the URL names will give some information.
The util is in recent releases (make sure you get 2.6.0) and also available in Free Pascal source, which should be convertable to Delphi with relatively minor effort.
Basically the chmls tool was created out of various test codebases. The testprograms decompiled and printed contents of different CHM sections and were used while creating the helpfile compiler, chmcmd, which is also part of FPC.
In Delphi, calling a help file is rather easy. In any VCL Forms application, you can set the HelpContext property of almost any control to a unique Context ID, which corresponds to a particular topic in the Help File. The Help File was compiled with these mappings, but when you decompile it, these mappings are no longer there. You must have access to the original help file project in order to know these ID's.
Set HelpContext of controls to the corresponding Context ID in the Help File
Set HelpType of controls to htContext to use the HelpContext ID
Assign Application.HelpFile to the appropriate location of the CHM file
When pressing F1 anywhere in your application, the help file will open based on the Help Context ID on the control, or its parent control
If you don't have the original project, and you don't want to re-create it, then you would have a long task of iterating through the Context ID's of your help file. Try to call the help file starting from 0 through 1,000 or possibly 50,000, depending on the size of it.
A practice I implement is a set of constants in a designated unit called HelpConstants.pas which is shared across our common application base. Each constant name uniquely and briefly describes the topic which it represents. Upon starting the application, I dynamically assign these Context ID's to their corresponding controls (usually forms) and VCL takes care of the rest.
I got the utility Marco suggested from
https://github.com/alrieckert/freepascal_arm/blob/master/packages/chm/bin/i386-win32/chmls.exe
(download by selecting View Raw).
I was able to extract all the context tags from the .chm help file and add the one I was interested in to my C++ Builder program by calling Application->HelpJump().
HTH
How can I make a single executable package that contains DLL and Image Resource Files?
Then how do I extract them from my Executable at Runtime?
Option 1 using the IDE (Delphi 2007 or Higher):
You can click the Project menu, then select Resources..., which you can load any file into. For your purpose this would be RC_DATA.
Option 2 without the IDE
If you do not have the above option, you will need to use the BRCC32 (Borland Resource Compiler) to create a .RES file from RC file, which you then link to your Application. To link Resource files without using the IDE, try the following:
Lets say for example we want to add a a couple of DLL files, and the name of the DLL files are MyLib1.dll and MyLib2.dll, to add this open Notepad, and type the following:
MYLIB1 RCDATA "..\MyLib1.dll"
MYLIB2 RCDATA "..\MyLib2.dll"
Make sure the ..\xxx.dll paths are correct, so obviously you need to edit that.
Now you need to save this as a .rc file, so File>Save As..(make sure the dropdown filter is All Files .) and name it MyResources.rc. Now you need to use the Resource Compiler to generate the Res file, using this console command:
BRCC32 MyResources.RC
You can write that command by using the Command Prompt, Start Menu > Run > cmd.exe, alternatively you can find the BRCC32.exe inside the bin folder of your Delphi setup and drag the MyResource.RC file onto.
This will create a Res file named MyResources.RES which you can include inside the Main Delphi form of your Application, like so:
{$R *.dfm}
{$R MyResources.res}
you can extract the resources by using something like this:
procedure ExtractResource(ResName: String; Filename: String);
var
ResStream: TResourceStream;
begin
ResStream:= TResourceStream.Create(HInstance, ResName, RT_RCDATA);
try
ResStream.Position:= 0;
ResStream.SaveToFile(Filename);
finally
ResStream.Free;
end;
end;
What I've found out to be convenient, is to use a .zip container.
Then you'll have two implementations:
Append some .zip content to an existing .exe, and the .exe code will retrieve the .zip content on request;
Embed the .zip content as a resource, then extract on request each content.
Solution 1 will add the .zip content after compilation. Whereas 2 will add the .zip content at compilation. For a setup program, I think solution 1 makes sense to me. For a way of retrieving some needed files (libraries, and even bitmaps or text) which are linked to a particular exe release, solution 2 could be envisaged.
Using .zip as format make it easy to parse the content, and allow compression. Using a tool like TotalCommander, you can even read the .zip file content with Ctrl+PgDown over the .exe. Very convenient.
You'll find in this link how you implement solution 1, and in this link (same page, but another post) how to use the TZipRead.Create() constructor to directly access to a .zip bundled as resource. You'll find in our repository how it works with working applications: e.g. how we embedded icons, textual content and graphviz + spell-checker libraries in the SynProject executable.
About performance, there is no difference between the two solutions, at least with our code. Both use memory mapped files to access the content, so it will be more or less identical: very fast.