I want to distribute only a single .exe, however, at runtime I would like it to extract some embedded image resources to the users hard disk drive.
Can I, and if so, how?
Use Delphi's TResourceStream. It's constructor will find and load the resource into memory, and it's SaveToFile method will do the disk write.
Something similar to this should work:
var
ResStream: TResourceStream;
begin
ResStream := TResourceStream.Create(HInstance, 'YOURRESOURCENAME', RT_RCDATA);
try
ResStream.Position := 0;
ResStream.SaveToFile('C:\YourDir\YourFileName.jpg');
finally
ResStream.Free;
end;
end;
If you can use the resource ID instead of name, it's a little less memory. In that case, you'd resplace Create with CreateFromID, and supply the numeric ID rather than the string name.
Create a TResourceStream. You'll need the module instance handle (usually SysInit.HInstance for the current EXE file, or else whatever you get from LoadLibrary or LoadPackage), the resource type (such as rt_Bitmap or rt_RCData), and either the resource name or numeric ID. Then call the stream's SaveToFile method.
try
if not Assigned(Bitmap)
then
Bitmap := TBitmap.Create();
Bitmap.LoadFromResourceName(HInstance,SRC);
except
on E:Exception do
ShowMessage(e.Message);
end;
And then save the Bitmap to disk.
Maybe this might come in handy too if you need to work with the resources itself.
Delphidabbler / ResourceFiles
Related
I use the internal TZipFile.
When I open the zip then Delphi seems to open it exclusively.
As long as the zipfile isn't freed the file access is denied
lZipFile := tZipFile.Create;
if lZipFile.IsValid( sPath) then begin
lZipFile.Open( sPath, zmRead );
...
// access denied to sPath
end;
lZipFile.Free;
I only want to read. Why delphi is behaving that way?
If I want to access a zip-file several times then I have to make a local copy and work with that copy? I don't really like this workaround. First of all since the zipfile could be huge.
Any idea what I can do to access the same zip in a read-only mode at the same time more than once?
You can create a TFileStream instance opened with the desired share mode. Then use the overloaded Open method of TZipFile that accepts a TStream.
Be aware that TZipFile.IsValid will try to open the file exclusive, too. As IsValid does nothing what Open also does, I added a try-except block to catch any invalid or unaccessible target. The call to IsValid can thus be omitted.
zip := TZipFile.Create;
try
stream := TFileStream.Create(sPath, fmOpenRead + fmShareDenyWrite);
try
try
zip.Open(stream, zmRead);
except
on EZipException do begin
// access denied to sPath
end;
end;
finally
stream.Free;
end;
finally
zip.Free;
end;
I would like to add an ini file to my delphi project as a resource file.
I know where you go to add a file as a resource file:
Project > Resources and Images > Add
But once thats done what else do I need to do to be able to read from the file? I haven't used resource files before.
Is there any documentation on the process?
Thanks,
The built-in INI file classes in the RTL, providing in the System.IniFiles unit, require the INI file to be a disk file. So you could extract the resource to disk and read it from there.
If you don't like the idea of that then you could write your own INI file parser that operated on a stream rather than a file. You could use the code of TMemIniFile to guide you. Copy that code and replace LoadValues with code to read from a stream rather than a file. Or if you look hard enough then you may well find a third party INI parser that operates on streams.
If you are prepared to consider other formats then you might use JSON rather than INI. The built-in JSON parser does not require the input data to reside on disk. They can operate on in-memory strings, which sounds rather more convenient.
This above text is in fact nonsense. Thank you to Remy for pointing that out. You can use TMemIniFile and its SetStrings method to parse INI content that does not reside on disk. It goes like this:
Put your INI content into a resource as a string.
Load that resource into a string variable.
Create a TStringList, and assign the string variable to the Text property of the string list.
Create a TMemIniFile.
Call SetStrings on the TMemIniFile passing the string list.
Or:
Put your INI content into a resource as a string.
Create a TResourceStream object to read that resource.
Create a TStringList object.
Call LoadFromStream on the string list passing the resource stream.
Create a TMemIniFile.
Call SetStrings on the TMemIniFile passing the string list.
Having said all of this, it seems odd that you would choose to do this at all. Wouldn't it be simpler just to hard code the configuration information in a unit, as a series of constants?
Using the "Resources and Images" dialog, add the .ini file to the project as a RCDATA resource type. Then, you can load it at runtime like this:
uses
..., Classes, IniFiles;
var
Ini: TMemIniFile;
List: TStringList;
Strm: TResourceStream;
begin
Ini := TMemIniFile.Create;
try
List := TStringList.Create;
try
Strm := TResourceStream.Create(HInstance, 'ResourceIDHere', RT_RCDATA);
try
List.LoadFromStream(Strm);
finally
Strm.Free;
end;
Ini.SetStrings(List);
finally
List.Free;
end;
// use Ini as needed...
finally
Ini.Free;
end;
end;
To add the files in the executable, be sure to add a .rcfile (not the .ini files directly), for example inis.rc (so in the source file dpr you'll have {$R 'inis.res' 'inis.rc'}) and write in this file a list like this:
this 100 "this.ini"
that 100 "that.ini"
If you've stored the ini files in a (relative) directory, be sure to double the backslashes since this in C-syntax. (The 100here is the resource type, there's no number assigned to ini-files specifically so we'll use an unassigned number. The best next thing is 23 which is assigned to RT_HTML, see below)
If you're not using groups (and lines with just [GroupName]), I'd suggest you use plain TStringList objects and their Values property. To load them with the data, use something like this:
var
sl:TStringList;
r:TResourceStream;
begin
sl:=TStringList.Create;
try
r:=TResourceStream.Create(HInstance,'this',MAKEINTRESOURCE(100));
try
sl.LoadFromStream(r);
finally
r.Free;
end;
//sl.Values['Setting']
finally
sl.Free;
end;
end;
imho, an ini file is just a text file to distribute with an application so you can control behavior of the application in one particular environment. For example, you could store a language in "language.ini", read it from your source code, and present the GUI based on that language.
To accomplish this, your ini file contains:
[general]
language=Russian
then read it from Delphi:
...
uses Inifiles;
...
var CurrentLanguage:string;
...
Ini := TIniFile.Create('C:\somedir\languages.ini');
CurrentLanguage := Ini.ReadString('General', 'language', 'English');//if key isn't found, language is English
Ini.free();
So basically it contains TEXT info... as said above, if you add it as a resource, you might as well hardcode it. Resources should be used primarily for binary data (an image, audio file, video, etc).
I use this code to download small files:
Var
ms:TMemoryStream;
begin
ms:=TMemoryStream.Create;
Idhttp1.get('http://mydomain.com/myfile.zip',ms);
ms.SaveToFile('myfile.zip');
ms.Free;
end;
But file is saved in RAM before storing to disk, so it may be difficult to download files >1Gb, for example. Is there a way to download a file by its parts? Or do I need to use the WinInet? Thanks in advance!
TMemoryStream provides an in-memory buffer, so if you download into one, you need to have enough memory to hold everything you receive. It's not the only kind of stream, though. You can pass the Get method any kind of stream you want, including one that writes its contents to disk as it receives it. Use TFileStream, for example.
var
s: TStream;
s := TFileStream.Create('myfile.zip', fmCreate);
try
IdHttp1.Get(..., s);
finally
s.Free;
end;
Anywhere you call LoadFromFile or SaveToFile on a TMemoryStream, it's possible that TFileStream is a better choice.
I feel like this should be easy, but google is totally failing me at the moment. I want to open a file, or create it if it doesn't exist, and write to it.
The following
AssignFile(logFile, 'Test.txt');
Append(logFile);
throws an error on the second line when the file doesn't exist yet, which I assume is expected. But I'm really failing at finding out how to a) test if the file exists and b) create it when needed.
FYI, working in Delphi XE.
You can use the FileExists function and then use Append if exist or Rewrite if not.
AssignFile(logFile, 'Test.txt');
if FileExists('test.txt') then
Append(logFile)
else
Rewrite(logFile);
//do your stuff
CloseFile(logFile);
Any solution that uses FileExists to choose how to open the file has a race condition. If the file's existence changes between the time you test it and the time you attempt to open the file, your program will fail. Delphi doesn't provide any way to solve that problem with its native file I/O routines.
If your Delphi version is new enough to offer it, you can use the TFile.Open with the fmOpenOrCreate open mode, which does exactly what you want; it returns a TFileStream.
Otherwise, you can use the Windows API function CreateFile to open your file instead. Set the dwCreationDisposition parameter to OPEN_ALWAYS, which tells it to create the file if it doesn't already exist.
You should be using TFileStream instead. Here's a sample that will create a file if it doesn't exist, or write to it if it does:
var
FS: TFileStream;
sOut: string;
i: Integer;
Flags: Word;
begin
Flags := fmOpenReadWrite;
if not FileExists('D:\Temp\Junkfile.txt') then
Flags := Flags or fmCreate;
FS := TFileStream.Create('D:\Temp\Junkfile.txt', Flags);
try
FS.Position := FS.Size; // Will be 0 if file created, end of text if not
sOut := 'This is test line %d'#13#10;
for i := 1 to 10 do
begin
sOut := Format(sOut, [i]);
FS.Write(sOut[1], Length(sOut) * SizeOf(Char));
end;
finally
FS.Free;
end;
end;
If you are just doing something simple, the IOUtils Unit is a lot easier. It has a lot of utilities for writing to files.
e.g.
procedure WriteAllText(const Path: string; const Contents: string);
overload; static;
Creates a new file, writes the specified string to the file, and then
closes the file. If the target file already exists, it is overwritten.
You can also use the load/save feature in a TStringList to solve your problem.
This might be a bad solution, because the whole file will be loaded into memory, modified in memory and then saved to back to disk. (As opposed to your solution where you just write directly to the file). It's obviously a bad solution for multiuser situations.
But this approach is OK for smaller files, and it is easy to work with and easy understand.
const
FileName = 'test.txt';
var
strList: TStringList;
begin
strList := TStringList.Create;
try
if FileExists(FileName) then
strList.LoadFromFile(FileName);
strList.Add('My new line');
strList.SaveToFile(FileName);
finally
strList.Free;
end;
end;
I am loading a file into a array in binary form this seems to take a while
is there a better faster more efficent way to do this.
i am using a similar method for writing back to the file.
procedure openfile(fname:string);
var
myfile: file;
filesizevalue,i:integer;
begin
assignfile(myfile,fname);
filesizevalue:=GetFileSize(fname); //my method
SetLength(dataarray, filesizevalue);
i:=0;
Reset(myFile, 1);
while not Eof(myFile) do
begin
BlockRead(myfile,dataarray[i], 1);
i:=i+1;
end;
CloseFile(myfile);
end;
If your really want to read a binary file fast, let windows worry about buffering ;-) by using Memory Mapped Files. Using this you can simple map a file to a memory location an read like it's an array.
Your function would become:
procedure openfile(fname:string);
var
InputFile: TMappedFile;
begin
InputFile := TMappedFile.Create;
try
InputFile.MapFile(fname);
SetLength(dataarray, InputFile.Size);
Move(PByteArray(InputFile.Content)[0], Result[0], InputFile.Size);
finally
InputFile.Free;
end;
end;
But I would suggest not using the global variable dataarray, but either pass it as a var in the parameter, or use a function which returns the resulting array.
procedure ReadBytesFromFile(const AFileName : String; var ADestination : TByteArray);
var
InputFile : TMappedFile;
begin
InputFile := TMappedFile.Create;
try
InputFile.MapFile(AFileName);
SetLength(ADestination, InputFile.Size);
Move(PByteArray(InputFile.Content)[0], ADestination[0], InputFile.Size);
finally
InputFile.Free;
end;
end;
The TMappedFile is from my article Fast reading of files using Memory Mapping, this article also contains an example of how to use it for more "advanced" binary files.
You generally shouldn't read files byte for byte. Use BlockRead with a larger value (512 or 1024 often are best) and use its return value to find out how many bytes were read.
If the size isn't too large (and your use of SetLength seems to support this), you can also use one BlockRead call reading the complete file at once. So, modifying your approach, this would be:
AssignFile(myfile,fname);
filesizevalue := GetFileSize(fname);
Reset(myFile, 1);
SetLength(dataarray, filesizevalue);
BlockRead(myFile, dataarray[0], filesizevalue);
CloseFile(myfile);
Perhaps you could also change the procedure to a boolean function named OpenAndReadFile and return false if the file couldn't be opened or read.
It depends on the file format. If it consists of several identical records, you can decide to create a file of that record type.
For example:
type
TMyRecord = record
fieldA: integer;
..
end;
TMyFile = file of TMyRecord;
const
cBufLen = 100 * sizeof(TMyRecord);
var
file: TMyFile;
i : Integer;
begin
AssignFile(file, filename);
Reset(file);
i := 0;
try
while not Eof(file) do begin
BlockRead(file, dataarray[i], cBufLen);
Inc(i, cBufLen);
end;
finally
CloseFile(file);
end;
end;
If it's a long enough file that reading it this way takes a noticeable amount of time, I'd use a stream instead. The block read will be a lot faster, and there's no loops to worry about. Something like this:
procedure openfile(fname:string);
var
myfile: TFileStream;
filesizevalue:integer;
begin
filesizevalue:=GetFileSize(fname); //my method
SetLength(dataarray, filesizevalue);
myFile := TFileStream.Create(fname);
try
myFile.seek(0, soFromBeginning);
myFile.ReadBuffer(dataarray[0], filesizevalue);
finally
myFile.free;
end;
end;
It appears from your code that your record size is 1 byte long. If not, then change the read line to:
myFile.ReadBuffer(dataarray[0], filesizevalue * SIZE);
or something similar.
Look for a buffered TStream descendant. It will make your code a lot faster as the disk read is done fast, but you can loop through the buffer easily. There are various about, or you can write your own.
If you're feeling very bitheaded, you can bypass Win32 altogether and call the NT Native API function ZwOpenFile() which in my informal testing does shave a tiny bit off. Otherwise, I'd use Davy's Memory Mapped File solution above.