how in Delphi could I open binary file in non-text mode?
Like C function fopen(filename,"rb")
There are a few options.
1. Use a file stream
var
Stream: TFileStream;
Value: Integer;
....
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Stream.ReadBuffer(Value, SizeOf(Value));//read a 4 byte integer
finally
Stream.Free;
end;
2. Use a reader
You would combine the above approach with a TBinaryReader to make the reading of the values simpler:
var
Stream: TFileStream;
Reader: TBinaryReader;
Value: Integer;
....
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Reader := TBinaryReader.Create(Stream);
try
Value := Reader.ReadInteger;
finally
Reader.Free;
end;
finally
Stream.Free;
end;
The reader class has lots of functions to read other data types. And you can go in the opposite direction with a binary writer.
3. Old style Pascal I/O
You can declare a variable of type File and use AssignFile, BlockRead, etc. to read from the file. I really don't recommend this approach. Modern code and libraries almost invariably prefer the stream idiom and by doing the same yourself you'll make your code easier to fit with other libraries.
You have different options, two of them are:
Use the old school approach, like the C function you pointed out:
var
F: File;
begin
AssignFile(F, 'c:\some\path\to\file');
ReSet(F);
try
//work with the file
finally
CloseFile(F);
end
end;
Use a more modern approach to create a TFileStream based on the file:
var
F: TFileStream;
begin
F := TFileStream.Create('c:\some\path\to\file', fmOpenRead);
try
//work with the file
finally
F.Free;
end;
Related
I have this code, where I am reading buffers to memory stream and I want to save it to file stream. The problem is that memoryStream.GetBuffer() does not work for my Delphi 7 as it is undeclared identifier.
procedure Dictionary.WriteHeaderObj(filename: String);
var MemStream: TMemoryStream;
i: Integer;
begin
self.fileName := filename;
try
MemStream := TMemoryStream.Create;
try
fsOutput := TFileStream.Create(fileName, fmCreate);
try
MemStream.Write(VAR_META, lengths.VR_META);
for i:=0 to length(buffers)-1 do
MemStream.Write(self.buffers[i].b^,self.buffers[i].l^);
fsOutput.Write(MemStream.GetBuffer(), 0, memoryStream.Position);
finally
MemStream.Free;
end;
finally
fsOutput.Free;
end;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end;
Your code showed several mistakes common to beginners.
There really is no need for the TMemoryStream. As David said, it hurts performance.
It's also not a good idea to write everything to a single TMemoryStream, because you may ran into "Out of memory" problem.
If the buffer size is really small and using a memory buffer is beneficial, you should do it in two loops - the inner loop writes a certain number of data to the memory stream and the out loop copies the memory stream to the file stream and empty the memory stream.
Unfortunately Capacity (protected) of TMemoryStream is useless because TMemoryStream reallocates memory whenever Size is changed. Using a sufficiently big TBytes avoids memory reallocation but you need to keep track of the length.
Why did you just write the header object to the file? Do you intend to append the body to the file later? Append a file is usually a bad idea.
Some thing trivial: always pass string constants with const.
Some thing trivial: always start a type name with 'T'.
Here is how I would do it:
procedure TDictionary.SaveHeaderToStream(AStream: TStream);
// You want to write to a stream, not a file. This is more reusable.
// You don't want to put the create/free code here,
// this is more flexible as it can also be used by SaveToStream
var
i: Integer;
begin
for i := 0 to length(buffers) - 1 do
AStream.Write(self.buffers[i].b^, self.buffers[i].l^);
end;
procedure TDictionary.SaveHeaderToFile(const Filename: string);
var
fsOutput: TFileStream;
begin
fsOutput := TFileStream.Create(Filename, fmCreate);
try
SaveHeaderToStream(fsOutput); // fsOutput IS TStream
finally
fsOutput.Free;
end;
end;
procedure TDictionary.SaveBodyToStream(AStream: TStream);
begin
// Codes here
end;
procedure TDictionary.SaveToStream(AStream: TStream);
begin
SaveHeaderToStream(AStream);
SaveBodyToStream(AStream);
end;
procedure TDictionary.SaveToFile(const Filename: string);
var
fsOutput: TFileStream;
begin
fsOutput := TFileStream.Create(Filename, fmCreate);
try
SaveToStream(fsOutput);
finally
fsOutput.Free;
end;
end;
I'm modifying a program that is written in Delphi 6.0
I have a table in Oracle with a BLOB column named FILE_CONTENT.
I have already managed to upload an XML File that is about 100 KB. I have verified that the file content was correctly uploaded using SQL Developer.
The problem I have is when I try to download back the file content from DB to a file. This is an example code I'm using to donwload it:
procedure TfrmDownload.Save();
var
fileStream: TFileStream;
bField: TBlobField;
begin
dmDigital.qrGetData.Open;
dmDigital.RequestLive := True;
bField := TBlobField(dmDigital.qrGetData.FieldByName('FILE_CONTENT'));
fileStream := TFileStream.Create('FILE.XML', fmCreate);
bField.SaveToStream(fileStream);
FlushFileBuffers(fileStream.Handle);
fileStream.Free;
dmDigital.qrGetData.Close;
end;
The previous code already downloads the file content to FILE.XML. I'm using RequestLive:=True to be able to download a large BLOB (otherwise the file content is truncated to 32K max)
The resulting file is the same size as the original file. However, when I compare the downloaded file with the original one there are some differences (for example the last character is missing and other characters are also changed), therefore it seems to be a problem while downloading the content.
Do you know what cuould be wrong?
The problem seems to be related to Delphi code because I already tried with C# and the file content is downloaded correctly.
Don't use TBlobField.SaveToStream() directly, use TDataSet.CreateBlobStream() instead (which is what TBlobField.SaveToStream() uses internally anyway):
procedure TfrmDownload.Save;
var
fileStream: TFileStream;
bField: TField;
bStream: TStream;
begin
dmDigital.qrGetData.Open;
try
dmDigital.RequestLive := True;
bField := dmDigital.qrGetData.FieldByName('FILE_CONTENT');
bStream := bField.DataSet.CreateBlobStream(bField, bmRead);
try
fileStream := TFileStream.Create('FILE.XML', fmCreate);
try
fileStream.CopyFrom(bStream, 0);
FlushFileBuffers(fileStream.Handle);
finally
fileStream.Free;
end;
finally
bStream.Free;
end;
finally
dmDigital.qrGetData.Close;
end;
end;
TDataSet.CreateBlobStream() allows the DataSet to decide the best way to access the BLOB data. If the returned TStream is not delivering the data correctly, then either the TStream class implementation that CreateBlobStream() uses is broken, or the underlying DB driver is buggy. Try taking CopyFrom() out of the equation so you can verify the data as it is being retrieved:
procedure TfrmDownload.Save;
const
MaxBufSize = $F000;
var
Buffer: array of Byte;
N: Integer;
fileStream: TFileStream;
bField: TField;
bStream: TStream;
begin
dmDigital.qrGetData.Open;
try
dmDigital.RequestLive := True;
bField := dmDigital.qrGetData.FieldByName('FILE_CONTENT');
bStream := bField.DataSet.CreateBlobStream(bField, bmRead);
try
fileStream := TFileStream.Create('FILE.XML', fmCreate);
try
//fileStream.CopyFrom(bStream, 0);
SetLength(Buffer, MaxBufSize);
repeat
N := bStream.Read(PByte(Buffer)^, MaxBufSize);
if N < 1 then Break;
// verify data here...
fileStream.WriteBuffer(PByte(Buffer)^, N);
until False;
FlushFileBuffers(fileStream.Handle);
finally
fileStream.Free;
end;
finally
bStream.Free;
end;
finally
dmDigital.qrGetData.Close;
end;
end;
okay, so I (VERY) recently started playing with lazaruz/free pascal, and I'm a little stuck with reading files with TMemoryStream and it's streaming kin.
I'm trying to write a simple base64 encoder, that can encode strings of text, or files (like images and WAVs) to then be used in html and javascript.
The following code compiles great but I get EReadError Illegal stream image when trying to load a file. I'll include the working string only procedure for reference:
procedure TForm1.TextStringChange(Sender: TObject);
begin
Memo1.Lines.Text := EncodeStringBase64(TextString.Text);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Lines.Text := '';
Form1.BorderIcons := [biSystemMenu,biMinimize];
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
filename := OpenDialog1.Filename;
stream := TMemoryStream.Create;
try
StrStream := TStringStream.Create(s);
try
stream.LoadFromFile(filename);
stream.Seek(0, soFromBeginning);
ObjectBinaryToText(stream, StrStream);
StrStream.Seek(0, soFromBeginning);
Memo1.Lines.Text := EncodeStringBase64(StrStream.DataString);
finally
StrStream.Free;
end;
finally
stream.Free;
end;
end;
end;
Can anyone help me out?
You get the "illegal stream image" exception because the file you're loading probably isn't a binary DFM file. That's what ObjectBinaryToText is meant to process. It's not for arbitrary data. So get rid of that command.
You can skip the TMemoryStream, too. TStringStream already has a LoadFromFile method, so you can call it directly instead of involving another buffer.
StrStream.LoadFromFile(filename);
But a string isn't really the right data structure to store your file in prior to base64-encoding it. The input to base64 encoding is binary data; the output is text. Using a text data structure as an intermediate format means you may introduce errors into your data because of difficulties in encoding certain data as valid characters. The right interface for your encoding function is this:
function Base64Encode(Data: TStream): string;
You don't need to load the entire file into memory prior to encoding it. Just open the file with a TFileStream and pass it to your encoding function. Read a few bytes from it at a time with the stream's Read method, encode them as base64, and append them to the result string. (If you find that you need them, you can use an intermediate TStringBuilder for collecting the result, and you can add different buffering around the file reads. Don't worry about those right away, though; get your program working correctly first.)
Use it something like this:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
filename: string;
stream: TStream;
begin
if OpenDialog1.Execute then begin
filename := OpenDialog1.Filename;
stream := TFileStream.Create(filename, fmOpenRead);
try
Memo1.Lines.Text := Base64Encode(stream);
finally
stream.Free;
end;
end;
end;
I never heard before about ObjectBinaryToText(), but looks like funky one. Also, what is EncodeStringBase64() function?
At first place, you shouldn't convert binary stream to text to encode it, instead you should directly B64 encode binary data. B64 algorithm is intended to work on array of bytes.
Since Delphi 6, there is EncdDecd.pas unit, which implements B64 encoding methods. I'm not sure if Lazarus/FPC have this, but if they do, your code to B64 encode file should look like this (add EncdDecd to uses list):
procedure TForm1.Button1Click(Sender: TObject);
var
instream : TFileStream;
outstream: TStringStream;
begin
if OpenDialog1.Execute then
begin
instream := TFileStream.Create(OpenDialog1.FileName, fmOpenRead or fmShareDenyNone);
try
outstream := TStringStream.Create;
try
EncodeStream(instream, outstream);
Memo1.Lines.Text := outstream.DataString;
finally
outstream.Free;
end;
finally
instream.Free;
end;
end;
end;
I have a .txt file and I want to replace a line with a new one. These are the steps:
Read in the .txt file
Save Source to a TStringList
Modify some data in a particular line
Save the new data back to the original file.
How do I do this?
Like this:
var
Strings: TStringList;
....
Strings := TStringList.Create;
try
Strings.LoadFromFile(FileName);
Strings[LineIndex] := NewValue;
Strings.SaveToFile(FileName);
finally
Strings.Free;
end;
With newer Delphi's you can get the contents of a file as an array of strings in a single call TFile.ReadAllLines().
program TestModifyLine; {$APPTYPE CONSOLE}
uses Types,IoUtils;
procedure ModifyLine(fn:string;Index:integer;NewText:String);
var lines:TStringDynArray;
begin
lines := TFile.ReadAllLines(fn);
lines[Index] := NewText;
TFile.WriteAllLines(fn,lines);
end;
begin
ModifyLine('test.txt',12,'hello');
end.
If you don't want to waste memory loading the entire source file at one time, you can use TStreamReader and TStreamWriter to read/write the files one line at a time, modifying the desired line after reading it and before writing it.
Var
Reader: TStreamReader;
Writer: TStreamWriter:
Line: String;
LineNum: Integer;
Begin
Reader := TStreamReader.Create(...);
Writer := TStreamWriter.Create(...);
While not Reader.EndOfStream do
Begin
Line := Reader.ReadLine;
Inc(LineNum);
If LineNum = ... Then
Begin
...
End;
Writer.WriteLine(Line);
End;
Writer.Free;
Reader.Free;
End;
In this question is mentioned the wcrypt2.
What I need is simply calculate the MD5 of a file. It would be perfect if I could calculate it without having to save it because it is a downloaded file in stream format.
I would like to have the most straightforward way to do that.
Thanks!
Here is a working code for Indy 10:
function MD5File(const FileName: string): string;
var
IdMD5: TIdHashMessageDigest5;
FS: TFileStream;
begin
IdMD5 := TIdHashMessageDigest5.Create;
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Result := IdMD5.HashStreamAsHex(FS)
finally
FS.Free;
IdMD5.Free;
end;
end;
Regards,
OscaR1
Based on #dummzeuch answere I wrote this function:
function getMD5checksum(s: TStream): string;
var
md5: TIdHashMessageDigest5;
hash : T4x4LongWordRecord;
begin
md5 := TIdHashMessageDigest5.Create;
s.Seek(0,0);
hash := md5.HashValue(s);
result := IntToHex(Integer(hash[0]), 4) +
IntToHex(Integer(hash[1]), 4) +
IntToHex(Integer(hash[2]), 4) +
IntToHex(Integer(hash[3]), 4);
end;
Indy comes with functions for calculating several hashes, MD5 is one of them. Indy is included in all versions of Delphi since at least Delphi 2006 and available as a free download for older versions.
What about:
function GetFileMD5(const Stream: TStream): String; overload;
var MD5: TIdHashMessageDigest5;
begin
MD5 := TIdHashMessageDigest5.Create;
try
Result := MD5.HashStreamAsHex(Stream);
finally
MD5.Free;
end;
end;
function GetFileMD5(const Filename: String): String; overload;
var FileStream: TFileStream;
begin
FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Result := GetFileMD5(FileStream);
finally
FileStream.Free;
end;
end;
As you mentioned, the post you linked to talks about wcrypt2, which is a library of cryptographic routines, including MD5. The post you linked to also seems to indicate that it is available for Delphi 7 since the asker includes output labeled "Delphi 7." You have tagged this question delphi7, so I assume that's the version you're using, too. So what's stopping you from using wcrypt2?
The question links to a copy of wcrypt2.pas, and the copyright dates in that file appear to indicate that the unit was available by the time Delphi 7 was released. Check your installation; you might already have it. If not, then the unit also says that it was obtained via Project Jedi, so you could try looking there for the unit as well.
The answers to your referenced question include example Delphi code and the names of units that come with Delphi for doing MD5. They come with Delphi 2009, so you should check whether they're also available for your version.
Take a look at this implementation of MD5SUM in Delphi. It requires a string for input, but I imagine you can easily make it work with a stream.
MessageDigest_5 would work for this as well.
I use the following function in Delphi 7 with Indy 10.1.5
uses IdHashMessageDigest, idHash, Classes;
...
function cc_MD5File(const p_fileName : string) : string;
//returns MD5 has for a file
var
v_idmd5 : TIdHashMessageDigest5;
v_fs : TFileStream;
v_hash : T4x4LongWordRecord;
begin
v_idmd5 := TIdHashMessageDigest5.Create;
v_fs := TFileStream.Create(p_fileName, fmOpenRead OR fmShareDenyWrite) ;
try
v_hash := v_idmd5.HashValue(v_fs);
result := v_idmd5.AsHex(v_hash);
finally
v_fs.Free;
v_idmd5.Free;
end;
end;
If you use Overbyte http://www.overbyte.eu/frame_index.html just add unit and call function FileMD5 with name of file
uses OverbyteIcsMd5;
....
function GetMd5File:String;
begin
Result := FileMD5(FileName);
end;