I want to know the width and height of an image file before opening that file.
So, how can I do that?
This refers to JPEG, BMP, PNG and GIF types of image files.
If by 'image file' you mean those raster image files recognised by the VCL's graphics system, and by 'before opening' you mean 'before the user is likely to notice that the file is opened', then you can do this very easily:
var
pict: TPicture;
begin
with TOpenDialog.Create(nil) do
try
if Execute then
begin
pict := TPicture.Create;
try
pict.LoadFromFile(FileName);
Caption := Format('%d×%d', [pict.Width, pict.Height])
finally
pict.Free;
end;
end;
finally
Free;
end;
Of course, the file is opened, and this requires a lot of memory if the image is big. However, if you need to obtain metatada (like dimensions) without loading the file, I believe you need a more 'complicated' solution.
You can try this page. I have not tested it, but it seems pretty reasonable that it will work.
Also, different file types have different ways of getting the width and height.
One of the page answers:
unit ImgSize;
interface
uses Classes;
procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
procedure GetPNGSize(const sFile: string; var wWidth, wHeight: word);
procedure GetGIFSize(const sGIFFile: string; var wWidth, wHeight: word);
implementation
uses SysUtils;
function ReadMWord(f: TFileStream): word;
type
TMotorolaWord = record
case byte of
0: (Value: word);
1: (Byte1, Byte2: byte);
end;
var
MW: TMotorolaWord;
begin
// It would probably be better to just read these two bytes in normally and
// then do a small ASM routine to swap them. But we aren't talking about
// reading entire files, so I doubt the performance gain would be worth the trouble.
f.Read(MW.Byte2, SizeOf(Byte));
f.Read(MW.Byte1, SizeOf(Byte));
Result := MW.Value;
end;
procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
const
ValidSig : array[0..1] of byte = ($FF, $D8);
Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7];
var
Sig: array[0..1] of byte;
f: TFileStream;
x: integer;
Seg: byte;
Dummy: array[0..15] of byte;
Len: word;
ReadLen: LongInt;
begin
FillChar(Sig, SizeOf(Sig), #0);
f := TFileStream.Create(sFile, fmOpenRead);
try
ReadLen := f.Read(Sig[0], SizeOf(Sig));
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
ReadLen := 0;
if ReadLen > 0 then
begin
ReadLen := f.Read(Seg, 1);
while (Seg = $FF) and (ReadLen > 0) do
begin
ReadLen := f.Read(Seg, 1);
if Seg <> $FF then
begin
if (Seg = $C0) or (Seg = $C1) then
begin
ReadLen := f.Read(Dummy[0], 3); // don't need these bytes
wHeight := ReadMWord(f);
wWidth := ReadMWord(f);
end
else
begin
if not (Seg in Parameterless) then
begin
Len := ReadMWord(f);
f.Seek(Len - 2, 1);
f.Read(Seg, 1);
end
else
Seg := $FF; // Fake it to keep looping.
end;
end;
end;
end;
finally
f.Free;
end;
end;
procedure GetPNGSize(const sFile: string; var wWidth, wHeight: word);
type
TPNGSig = array[0..7] of byte;
const
ValidSig: TPNGSig = (137, 80, 78, 71, 13, 10, 26, 10);
var
Sig: TPNGSig;
f: tFileStream;
x: integer;
begin
FillChar(Sig, SizeOf(Sig), #0);
f := TFileStream.Create(sFile, fmOpenRead);
try
f.Read(Sig[0], SizeOf(Sig));
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
exit;
f.Seek(18, 0);
wWidth := ReadMWord(f);
f.Seek(22, 0);
wHeight := ReadMWord(f);
finally
f.Free;
end;
end;
procedure GetGIFSize(const sGIFFile: string; var wWidth, wHeight: word);
type
TGIFHeader = record
Sig: array[0..5] of char;
ScreenWidth, ScreenHeight: word;
Flags, Background, Aspect: byte;
end;
TGIFImageBlock = record
Left, Top, Width, Height: word;
Flags: byte;
end;
var
f: file;
Header: TGifHeader;
ImageBlock: TGifImageBlock;
nResult: integer;
x: integer;
c: char;
DimensionsFound: boolean;
begin
wWidth := 0;
wHeight := 0;
if sGifFile = '' then
exit;
{$I-}
FileMode := 0; // read-only
AssignFile(f, sGifFile);
reset(f, 1);
if IOResult <> 0 then
// Could not open file
exit;
// Read header and ensure valid file
BlockRead(f, Header, SizeOf(TGifHeader), nResult);
if (nResult <> SizeOf(TGifHeader)) or (IOResult <> 0)
or (StrLComp('GIF', Header.Sig, 3) <> 0) then
begin
// Image file invalid
close(f);
exit;
end;
// Skip color map, if there is one
if (Header.Flags and $80) > 0 then
begin
x := 3 * (1 SHL ((Header.Flags and 7) + 1));
Seek(f, x);
if IOResult <> 0 then
begin
// Color map thrashed
close(f);
exit;
end;
end;
DimensionsFound := False;
FillChar(ImageBlock, SizeOf(TGIFImageBlock), #0);
// Step through blocks
BlockRead(f, c, 1, nResult);
while (not EOF(f)) and (not DimensionsFound) do
begin
case c of
',': // Found image
begin
BlockRead(f, ImageBlock, SizeOf(TGIFImageBlock), nResult);
if nResult <> SizeOf(TGIFImageBlock) then
begin
// Invalid image block encountered
close(f);
exit;
end;
wWidth := ImageBlock.Width;
wHeight := ImageBlock.Height;
DimensionsFound := True;
end;
',' : // Skip
begin
// NOP
end;
// nothing else, just ignore
end;
BlockRead(f, c, 1, nResult);
end;
close(f);
{$I+}
end;
end.
And for BMP (also found at the page I mentioned):
function FetchBitmapHeader(PictFileName: String; Var wd, ht: Word): Boolean;
// similar routine is in "BitmapRegion" routine
label ErrExit;
const
ValidSig: array[0..1] of byte = ($FF, $D8);
Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7];
BmpSig = $4d42;
var
// Err : Boolean;
fh: HFile;
// tof : TOFSTRUCT;
bf: TBITMAPFILEHEADER;
bh: TBITMAPINFOHEADER;
// JpgImg : TJPEGImage;
Itype: Smallint;
Sig: array[0..1] of byte;
x: integer;
Seg: byte;
Dummy: array[0..15] of byte;
skipLen: word;
OkBmp, Readgood: Boolean;
begin
// Open the file and get a handle to it's BITMAPINFO
OkBmp := False;
Itype := ImageType(PictFileName);
fh := CreateFile(PChar(PictFileName), GENERIC_READ, FILE_SHARE_READ, Nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (fh = INVALID_HANDLE_VALUE) then
goto ErrExit;
if Itype = 1 then
begin
// read the BITMAPFILEHEADER
if not GoodFileRead(fh, #bf, sizeof(bf)) then
goto ErrExit;
if (bf.bfType <> BmpSig) then // 'BM'
goto ErrExit;
if not GoodFileRead(fh, #bh, sizeof(bh)) then
goto ErrExit;
// for now, don't even deal with CORE headers
if (bh.biSize = sizeof(TBITMAPCOREHEADER)) then
goto ErrExit;
wd := bh.biWidth;
ht := bh.biheight;
OkBmp := True;
end
else
if (Itype = 2) then
begin
FillChar(Sig, SizeOf(Sig), #0);
if not GoodFileRead(fh, #Sig[0], sizeof(Sig)) then
goto ErrExit;
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
goto ErrExit;
Readgood := GoodFileRead(fh, #Seg, sizeof(Seg));
while (Seg = $FF) and Readgood do
begin
Readgood := GoodFileRead(fh, #Seg, sizeof(Seg));
if Seg <> $FF then
begin
if (Seg = $C0) or (Seg = $C1) or (Seg = $C2) then
begin
Readgood := GoodFileRead(fh, #Dummy[0],3); // don't need these bytes
if ReadMWord(fh, ht) and ReadMWord(fh, wd) then
OkBmp := True;
end
else
begin
if not (Seg in Parameterless) then
begin
ReadMWord(fh,skipLen);
SetFilePointer(fh, skipLen - 2, nil, FILE_CURRENT);
GoodFileRead(fh, #Seg, sizeof(Seg));
end
else
Seg := $FF; // Fake it to keep looping
end;
end;
end;
end;
ErrExit: CloseHandle(fh);
Result := OkBmp;
end;
As a complement to Rafael's answer, I believe that this much shorter procedure can detect BMP dimensions:
function GetBitmapDimensions(const FileName: string; out Width,
Height: integer): boolean;
const
BMP_MAGIC_WORD = ord('M') shl 8 or ord('B');
var
f: TFileStream;
header: TBitmapFileHeader;
info: TBitmapInfoHeader;
begin
result := false;
f := TFileStream.Create(FileName, fmOpenRead);
try
if f.Read(header, sizeof(header)) <> sizeof(header) then Exit;
if header.bfType <> BMP_MAGIC_WORD then Exit;
if f.Read(info, sizeof(info)) <> sizeof(info) then Exit;
Width := info.biWidth;
Height := abs(info.biHeight);
result := true;
finally
f.Free;
end;
end;
If anyone yet interested in retrieving TIFF image dimensions without loading the graphic, there is a proven method that works perfectly for me in all environments. I also found another solution for that, but it returned wrong values from Illustrator-generated TIFFs. But there is a fantastic graphic library, called GraphicEx by Mike Lischke (TVirtualStringTree's very talented developer). There are implementations of many popular image formats and all of them descend from the base class TGraphicExGraphic, that implements ReadImageProperties virtual method. It is stream-based and only reads the fileheader in all implementations. So it is lightning-fast... :-)
So, here is a sample code, that retrieves a TIFF's dimensions (the method is the same for all graphic implementation, PNG,PCD,TGA,GIF,PCX,etc):
Uses ..., GraphicEx,...,...;
Procedure ReadTifSize (FN:String; Var iWidth,iHeight:Integer);
Var FS:TFileStream;
TIFF:TTIFFGraphic;
Begin
iWidth:=0;iHeight:=0;
TIFF:=TTIFFGraphic.Create;
FS:=TFileStream.Create(FN,OF_READ);
Try
TIFF.ReadImageProperties(FS,0);
iWidth:=TIFF.ImageProperties.Width;
iHeight:=TIFF.ImageProperties.Height;
Finally
TIFF.Destroy;
FS.Free;
End;
End;
That's all... :-) And this is the same for all the graphic implementations in the unit.
I don't like Rafael's solution for JPEG files too much because his algorithm parses every single byte until it hits FFC0. It doesn't make use of the fact that almost all markers (except FFD8, FFD9 and FFFE) are followed by two length bytes, allowing to skip from marker to marker. So I suggest the following procedure (which I condensed even a little more by stuffing checking for a marker and retrieving a value into the same function):
procedure GetJPGSize(const Filename: string; var ImgWidth, ImgHeight: word);
const
SigJPG : TBytes = [$FF, $D8];
SigC01 : TBytes = [$FF, $C0];
SigC02 : TBytes = [$FF, $C1];
var
FStream: TFileStream;
Buf: array[0..1] of Byte;
Offset,CheckMarker : Word;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function SameValue(Sig:TBytes):Boolean;
begin
Result := CompareMem(#Sig[0], #Buf[0], Length(Sig));
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function CheckMarkerOrVal(var Value:Word):Boolean;
begin
FStream.ReadData(Buf, Length(Buf));
Value := Swap(PWord(#Buf[0])^);
Result := (Buf[0] = $FF);
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
begin
FStream := TFileStream.Create(Filename, fmOpenRead);
Try
// First two bytes in a JPG file MUST be $FFD8, followed by the next marker
If not (CheckMarkerOrVal(CheckMarker) and SameValue(SigJPG))
then exit;
Repeat
If not CheckMarkerOrVal(CheckMarker)
then exit;
If SameValue(SigC01) or SameValue(SigC02) then begin
FStream.Position := FStream.Position + 3;
CheckMarkerOrVal(ImgHeight);
CheckMarkerOrVal(ImgWidth);
exit;
end;
CheckMarkerOrVal(Offset);
FStream.Position := FStream.Position + Offset - 2;
until FStream.Position > FStream.Size div 2;
Finally
FStream.Free;
end;
end;
Since GetGIFSize in Rafael's answer is broken and utterly complicated, here is my personal version of it:
function GetGifSize(var Stream: TMemoryStream; var Width: Word; var Height: Word): Boolean;
var
HeaderStr: AnsiString;
begin
Result := False;
Width := 0;
Height := 0;
//GIF header is 13 bytes in length
if Stream.Size > 13 then
begin
SetString(HeaderStr, PAnsiChar(Stream.Memory), 6);
if (HeaderStr = 'GIF89a') or (HeaderStr = 'GIF87a') then
begin
Stream.Seek(6, soFromBeginning);
Stream.Read(Width, 2); //Width is located at bytes 7-8
Stream.Read(Height, 2); //Height is located at bytes 9-10
Result := True;
end;
end;
end;
I found it by reading the RFC.
Data.Cloud.CloudAPI.pas has class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string; that return wrong SHA 256 on some file.
class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
LBytes : TBytes;
Hash: THashSHA2;
begin
LBytes := TBytesStream(Content).Bytes;
//Hash bytes
Hash := THashSHA2.Create;
Hash.Update(LBytes);
Result := Hash.HashAsString;
end;
AWS S3 return error:
The provided x-amz-content-sha256 header does not match what was computed
GetStreamToHashSHA256Hex seems produce a different sha256 from amazon:
<ClientComputedContentSHA256>f43ee89e2b7758057bb1f33eb8546d4c2c118f2ab932de89dbd74aabc0651053</ClientComputedContentSHA256>
<S3ComputedContentSHA256>3bbf5f864cc139cf6392b4623bd782a69d16929db713bffaa68035f8a5c3c0ce</S3ComputedContentSHA256>
I have made some tests wit a myfile.zip (600 MB) ...
TIdHashSHA256 an alternative from Indy return the right SHA256 (same of aws s3), eg.:
var
aFileStream: TFileStream;
aHash: TIdHashSHA256;
begin
aFileStream := TFileStream.Create('C:\myfile.zip', fmOpenRead or fmShareDenyWrite);
aHash := TIdHashSHA256.Create;
try
Result := aHash.HashStreamAsHex(aFileStream).ToLower;
finally
aFileStream.Free;
aHash.Free;
end;
end;
hash_file() from PHP return the right SHA256 (same of aws s3), eg.:
hash_file('sha256', 'C:\myfile.zip');
but THashSHA2 return a wrong sha256, eg.:
var
LBytes : TBytes;
Hash: THashSHA2;
begin
LBytes := TFile.ReadAllBytes('C:\myfile.zip');
Hash := THashSHA2.Create;
Hash.Update(LBytes);
Result := Hash.HashAsString;
end;
why?
UPDATE
this is my bug fix. Import Data.Cloud.CloudAPI.pas into the project and rewrite these function:
uses IdHash, IdHashSHA, IdSSLOpenSSL;
class function TCloudSHA256Authentication.GetHashSHA256Hex( HashString: string): string;
var
aHash: TIdHashSHA256;
begin
LoadOpenSSLLibrary;
try
if not(TIdHashSHA256.IsAvailable) then
raise Exception.Create('HashSHA256 Isn''t available!');
aHash := TIdHashSHA256.Create;
try
Result := aHash.HashStringAsHex(HashString).ToLower;
finally
aHash.Free;
end;
finally
UnLoadOpenSSLLibrary;
end;
end;
class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
aHash: TIdHashSHA256;
begin
LoadOpenSSLLibrary;
try
if not(TIdHashSHA256.IsAvailable) then
raise Exception.Create('HashSHA256 Isn''t available!');
aHash := TIdHashSHA256.Create;
try
Result := aHash.HashStreamAsHex(Content).ToLower;
finally
aHash.Free;
end;
finally
UnLoadOpenSSLLibrary;
end;
end;
UPDATE 2
i have also try to implement the FredS suggestion, it works:
class function TCloudSHA256Authentication.GetHashSHA256Hex( HashString: string): string;
var
Content: TStringStream;
Hash: THashSHA2;
LBytes: TArray<Byte>;
Buffer: PByte;
BufLen: Integer;
Readed: Integer;
begin
BufLen := 16 * 1024;
Buffer := AllocMem(BufLen);
Hash := THashSHA2.Create;
Content := TStringStream.Create(HashString);
try
while Content.Position < Content.Size do
begin
Readed := Content.Read(Buffer^, BufLen);
if Readed > 0 then
Hash.update(Buffer^, Readed);
end;
finally
Content.Free;
FreeMem(Buffer);
end;
Result := Hash.HashAsString;
end;
class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
LBytes : TBytes;
Hash: THashSHA2;
Buffer: PByte;
BufLen: Integer;
Readed: Integer;
begin
BufLen := 16 * 1024;
Buffer := AllocMem(BufLen);
Hash := THashSHA2.Create;
try
Content.Seek(0, soFromBeginning);
while Content.Position < Content.Size do
begin
Readed := Content.Read(Buffer^, BufLen);
if Readed > 0 then
Hash.update(Buffer^, Readed);
end;
Content.Seek(0, soFromBeginning);
finally
FreeMem(Buffer);
end;
Result := Hash.HashAsString;
end;
I just tested a +1.5 GB file using MS Cyrpto and THashSHA2 on Berlin, they both returned the same hash but MS Crypto like OpenSSL is much faster.
The problem is that the file is too large to hold in TBytes in one chunk.
My record helper has TBytes.MaxLen = $F000; {61440} so you need to use a TFileStream and read the file in chunks into HashSHA2.Update instead.
Update:
As per David Heffernan's comment I retested TBytes.MaxLen and it appears to be only limited by available memory.
Practical Example and Speed comparison between MS Crypto and Delphi HashSha2
Note: Requires Jedi API
program SHA2SpeedTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
JwaWindows, Winapi.Windows, System.SysUtils, System.Classes, System.Diagnostics, System.Hash;
const
SHA256_LEN = 256 div 8;
ChunkSize = $F000;
type
TBytesHelper = record helper for TBytes
public
function BinToHex: string;
end;
function TBytesHelper.BinToHex: string;
var
Len : Integer;
begin
Len := Length(Self);
SetLength(Result, Len * 2));
System.Classes.BinToHex(Self, PChar(Result), Len);
end;
procedure DelphiHash256(const AStream: TStream; out Bytes: TBytes);
var
HashSHA2: THashSHA2;
BytesRead: Integer;
begin
HashSHA2 := THashSHA2.create;
SetLength(Bytes, ChunkSize);
AStream.Position := 0;
repeat
BytesRead := AStream.Read(Bytes, ChunkSize);
if (BytesRead = 0) then Break; // Done
HashSHA2.Update(Bytes, BytesRead);
until False;
Bytes := HashSHA2.HashAsBytes;
end;
function CryptoHash256(const AStream: TStream; out Bytes: TBytes): Boolean;
var
SigLen : Cardinal;
hHash : HCRYPTHASH;
hProv : HCRYPTPROV;
BytesRead: Integer;
begin
hProv := 0; hHash := 0;
Result := False;
If not CryptAcquireContext(hProv, nil, nil, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) then Exit;
try
if not CryptCreateHash(hProv, CALG_SHA_256, 0, 0, hHash) then Exit;
try
SetLength(Bytes, ChunkSize);
AStream.Position := 0;
repeat
BytesRead := AStream.Read(Bytes, ChunkSize);
if (BytesRead = 0) then Break; // Done
if not CryptHashData(hHash, #Bytes[0], BytesRead, 0) then Exit;
until False;
SigLen := SHA256_LEN;
SetLength(Bytes, SigLen);
Result := CryptGetHashParam(hHash, HP_HASHVAL, #Bytes[0], SigLen, 0);
finally
CryptDestroyHash(hHash);
end;
finally
CryptReleaseContext(hProv, 0);
end;
end;
var
Stream: TStream;
Bytes : TBytes;
sw : TStopwatch;
CryptoTicks : int64;
FileName : string;
{* CheckFileName *}
function CheckFileName: boolean;
begin
if (FileName='') then FileName := ParamStr(0);
Result := FileExists(FileName);
if not Result then Writeln('Invalid File name');
end;
begin
repeat
Writeln('Please Enter a valid File name, empty for this Executable');
Readln(FileName);
until CheckFileName;
try
Stream := TFileStream.Create(FileName, fmOpenRead + fmShareDenyNone);
try
WriteLn('Crypto - Calculating Checksum');
sw.Start;
if not CryptoHash256(Stream, Bytes) then raise Exception.Create('Something Happened :)');
sw.Stop;
Writeln(Bytes.BinToHex);
WriteLn('Elapsed: ' + sw.Elapsed.ToString);
CryptoTicks := sw.ElapsedTicks;
WriteLn('Delphi - Calculating Checksum');
sw.Reset; sw.Start;
DelphiHash256(Stream, Bytes);
sw.Stop;
Writeln(Bytes.BinToHex);
WriteLn('Elapsed: ' + sw.Elapsed.ToString);
Writeln(Format('MS Crypto is %d%% faster', [(sw.ElapsedTicks-CryptoTicks) * 100 div CryptoTicks]));
finally
Stream.Free;
end;
Writeln('Hit <Enter> to exit');
Readln;
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
end.
I'm want write name of my pc to a txt file using SysUtils.FileWrite api, in my last attempt is wrote with sucess, but the trouble is that visually is cutting some characters, but size of text inside file have exactly the same size as if string is complete visually.
Eg: My pc is called of "TESTE-PC" (Without double quotes). The string "TESTE-PC" (Without double quotes) have exactly 8 bits, but SysUtils.FileWrite writes only "TEST" and size of file after is 8 bits. Very strange! :(
Thank you for any suggestion.
uses
Registry;
...
function GetCompName: string;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.rootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName', false) then
begin
Result := Reg.ReadString('ComputerName');
Reg.CloseKey;
end;
finally
Reg.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
hFile: THandle;
Str: PWideChar;
begin
if not fileexists('test.txt') then
begin
Str := PWideChar(GetCompName);
hFile:= CreateFile('test.txt', GENERIC_WRITE, 0, nil, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0);
FileWrite(hFile, Str^, Length(Str));
CloseHandle(hFile);
end;
end;
First off, using the Registry to get the computer name is wrong. Use the GetComputerName() function instead:
uses
Windows;
...
function GetCompName: string;
var
CompName: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
Size: DWORD;
begin
Size := Length(CompName);
if GetComputerName(CompName, Size) then
SetString(Result, CompName, Size-1)
else
Result := '';
end;
Second, your FileWrite() code fails because you are not handling character encodings correctly. FileWrite() operates on raw bytes only, but you are working with Unicode strings and not taking into account that SizeOf(WideChar) is 2, not 1 like your code assumes.
You should also be using the RTL's FileCreate() function with FileWrite(). If you use the Win32 CreateFile() function directly, you should be using the Win32 API WriteFile() directly as well.
And no matter how you choose to write the file, you should be using an absolute path to the file, never a relative path.
Try something more like this:
procedure TForm1.FormCreate(Sender: TObject);
var
FileName: string
hFile: THandle;
Str: string;
begin
FileName := 'C:\path to\test.txt';
if not FileExists(FileName) then
begin
Str := GetCompName;
hFile := FileCreate(FileName);
if hFile <> INVALID_HANDLE_VALUE then
begin
FileWrite(hFile, PChar(Str)^, Length(Str) * SizeOf(Char));
FileClose(hFile);
end;
end;
Note that the code above will create the file in UTF-16 encoding. If you wanted to use UTF-8 instead, it would look like this:
procedure TForm1.FormCreate(Sender: TObject);
var
FileName: string;
hFile: THandle;
Str: UTF8String;
begin
FileName := 'C:\path to\test.txt';
if not FileExists(FileName) then
begin
Str := UTF8String(GetCompName);
hFile := FileCreate(FileName);
if hFile <> INVALID_HANDLE_VALUE then
begin
FileWrite(hFile, PAnsiChar(Str)^, Length(Str));
FileClose(hFile);
end;
end;
Or any other encoding, for that matter:
procedure TForm1.FormCreate(Sender: TObject);
var
FileName: string;
hFile: THandle;
Enc: TEncoding;
Str: TBytes;
begin
FileName := 'C:\path to\test.txt';
if not FileExists(FileName) then
begin
Enc := TEncoding.GetEncoding('desired encoding');
try
Str := Enc.GetBytes(GetCompName);
finally
Enc.Free;
end;
hFile := FileCreate(FileName);
if hFile <> INVALID_HANDLE_VALUE then
begin
FileWrite(hFile, PByte(Str)^, Length(Str));
FileClose(hFile);
end;
end;
Whatever encoding you decide to use, a simpler solution would be to use the IOUtils.TFile.WriteAllText() method instead:
uses
IOUtils;
procedure TForm1.FormCreate(Sender: TObject);
var
FileName: string;
begin
FileName := 'C:\path to\test.txt';
if not FileExists(FileName) then
begin
TFile.WriteAllText(FileName, GetCompName, TEncoding.UTF8); // or TEncoding.Unicode, etc...
end;
end;
If you need to write wide chars, take their size into account:
FileWrite(hFile, Str^, Length(Str) * SizeOf(Char));
Change the type of str to RawByteString instead of PWideChar
procedure TForm1.FormCreate(Sender: TObject);
var
hFile: THandle;
sFileName: string;
Str: RawByteString;
begin
Str := PWideChar(GetCompName);
sFileName := 'Test.txt';
if fileExists(sFileName) then
hFile := fileOpen(sFileName,fmOpenReadWrite)
else
hFile := fileCreate(sFileName);
try
FileWrite(hFile,
PChar(Str)^, Length(Str));
finally
FileClose(hFile);
end;
end;
I have this code that gets called from an injected DLL from a foreign process. It sould read some memory ranges but I sometimes get a segmentation fault at this line DataBuffer := TCharPointer(Address + CharOffset)^;. So is there any way to check if the memory is readable?
function GetCurrentData(Address: Pointer): PChar;
var
DataBuffer: Char;
CharArray: Array of Char;
CharOffset: Integer;
ReadBytes: longword;
begin
CharOffset := 0;
SetLength(CharArray, 0);
repeat
DataBuffer := TCharPointer(Address + CharOffset)^;
CharOffset := CharOffset + 1;
SetLength(CharArray, CharOffset);
CharArray[CharOffset - 1] := DataBuffer;
until (Ord(DataBuffer) = 0);
Result := PChar(#CharArray[0]);
end;
i also tryed to catch the exception but for some reason this is not working. The host programm still crashes.
unit UnitEventBridgeExports;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Windows, ShellAPI, JwaTlHelp32, SimpleIPC;
type
TCharPointer = ^Char;
const
WOWEXE = 'TestProgramm.exe';
var
IPCClient: TSimpleIPCClient;
PID: DWord;
Process: THandle;
procedure EventCalled;
procedure InitializeWoWEventBridge; stdcall;
implementation
function GetProcessIDByName(Exename: String): DWord;
var
hProcSnap: THandle;
pe32: TProcessEntry32;
begin
Result := 0;
hProcSnap := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
if hProcSnap <> INVALID_HANDLE_VALUE then
begin
pe32.dwSize := SizeOf(ProcessEntry32);
if Process32First(hProcSnap, pe32) = True then
begin
while Process32Next(hProcSnap, pe32) = True do
begin
if pos(Exename, pe32.szExeFile) <> 0 then
Result := pe32.th32ProcessID;
end;
end;
CloseHandle(hProcSnap);
end;
end;
procedure InitializeEventBridge; stdcall;
begin
IPCClient := TSimpleIPCClient.Create(nil);
IPCClient.ServerID := 'EventBridgeServer';
IPCClient.Active := True;
IPCClient.SendStringMessage('init');
PID := GetProcessIDByName(EXE);
Process := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
end;
function GetCurrentData(Address: Pointer): PChar;
var
DataBuffer: Char;
CharArray: Array of Char;
CharOffset: Integer;
ReadBytes: longword;
CharPointer: TCharPointer;
BreakLoop: Boolean;
begin
CharOffset := 0;
SetLength(CharArray, 0);
BreakLoop := False;
repeat
try
CharPointer := TCharPointer(Address + CharOffset);
DataBuffer := CharPointer^;
CharOffset := CharOffset + 1;
SetLength(CharArray, CharOffset);
CharArray[CharOffset - 1] := DataBuffer;
except
BreakLoop := True;
end;
until (Ord(DataBuffer) = 0) or BreakLoop;
Result := PChar(#CharArray[0]);
end;
procedure EventCalled;
var
TmpAddress: Pointer;
StringData: PChar;
begin
{$ASMMODE intel}
asm
mov [TmpAddress], edi
end;
StringData := GetCurrentData(TmpAddress);
IPCClient.SendStringMessage('update:' + StringData);
//IPCClient.SendStringMessage('update');
end;
end.
Your GetCurrentData() implementation is returning a pointer to a local array that goees out of scope when the function exits, then EventCalled() tries to use that poiner after it is no longer valid. Try this instead:
function GetCurrentData(Address: Pointer): AnsiString;
var
Offset: Integer;
begin
Result := '';
Offset := 0;
repeat
try
if PByte(Longint(Address) + Offset)^ = #0 then Break;
Inc(Offset);
except
Break;
end;
until False;
SetString(Result, PAnsiChar(Address), Offset);
end;
procedure EventCalled;
var
TmpAddress: Pointer;
StringData: AnsiString;
begin
{$ASMMODE intel}
asm
mov [TmpAddress], edi
end;
StringData := GetCurrentData(TmpAddress);
IPCClient.SendStringMessage('update:' + StringData);
//IPCClient.SendStringMessage('update');
end;
IsBadReadPtr API is here to help. You give address and size, and you get the readability back. Raymond Chen suggests to never use it though.
Other than that, VirtualQuery should give you information about the address in question to tell its readability.
Since Ken in comments below re-warned about danger of IsBadReadPtr, I bring it up to the answer to not pass by. Be sure to read the comments and links to Raymdond's blog. Be sure to see also:
Most efficient replacement for IsBadReadPtr?
How to check if a pointer is valid?
Does anyone know of a class that can read multiframe icons? Searching the internet has not produced any information.
I tried using IconTools 2.0 by Alan Peter Stotz, which loads the icons into a list correctly but the bit-depth for 8-bit and 4-bit icons return as 0. The bitdepth for 32 and 24-bit icon frames is returned correctly, however.
The icon itself appears correct when viewing... just the bitdepth is wrong for the bits mentioned.
EDIT #2
Baised on the comment by TLama here is some untested code:
function NumberOfIcons ( AFileName: string ): integer;
var
iNumberOfIcons: Integer;
begin
iNumberOfIcons := ExtractIcon ( hInstance, PChar ( AFilename ), UINT ( -1 ) );
Result := iNumberOfIcons;
end;
function ExtractAnIcon ( AFilename: string; AIndex: integer ): TBitmap;
var
icoHandle: HIcon;
iBitmap: TBitmap;
iIcon: TIcon;
iNumberOfIcons, i: Integer;
begin
Result := nil;
iBitmap := TBitMap.Create;
iIcon := TIcon.Create;
try
// Get the number of Icons
iNumberOfIcons := ExtractIcon ( hInstance, PChar ( AFilename ), UINT ( -1 ) );
// Extract the icon frame
icoHandle := ExtractIcon ( hInstance, PChar ( AFileName ), AIndex );
iIcon.Handle := icoHandle;
iBitmap.Width := iIcon.Width;
iBitmap.Height := iIcon.Height;
// Draw the icon on your bitmap
DrawIcon ( iBitmap.Canvas.Handle, 0, 0, iIcon.Handle );
Result := iBitmap;
finally
iIcon.Free;
end;
end;
function PixelFormatToBitDepth ( APixelFormat: TPixelFormat ): integer;
// Convert TPixelFormat to integer
begin
Result := -1;
case APixelFormat of
pf32Bit:
Result := 32;
pf24bit:
Result := 24;
pf8bit:
Result := 8;
pf4Bit:
Result := 4;
pf1bit:
Result := 1;
end;
end;
Am I on the right track? In my testing I now get 1 icon but the NumberOfIcons function is returning 1?
EDIT#3
According to the help file "If the file is an .ICO file, the return value of ExtractIcon is 1." So what method can be used to get the number of icons in the ico file?
Here is a small code example:
uses ShellApi;
type
TICONDIRENTRY = packed record
bWidth: Byte; // Width, in pixels, of the image
bHeight: Byte; // Height, in pixels, of the image
bColorCount: Byte; // Number of colors in image (0 if >=8bpp)
bReserved: Byte; // Reserved ( must be 0)
wPlanes: Word; // Color Planes
wBitCount: Word; // Bits per pixel
dwBytesInRes: DWORD; // How many bytes in this resource?
dwImageOffset: DWORD; // Where in the file is this image?
end;
TICONDIR = packed record
idReserved: Word; // Reserved (must be 0)
idType: Word; // Resource Type (1 for icons)
idCount: Word; // How many images?
idEntries: array [0..255] of TICONDIRENTRY;
end;
PICONDIR=^TICONDIR;
function GetIconsCount(const FileName: string): Word;
var
Stream: TMemoryStream;
IconDir: PICONDIR;
begin
Result := 0;
if ExtractIcon(hInstance, PChar(FileName), UINT(-1)) <> 0 then
try
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile(FileName);
IconDir := Stream.Memory;
if IconDir.idType = 1 then
Result := IconDir.idCount;
finally
Stream.Free;
end;
except
// do not raise exceptions
end;
end;
function ExtractIcons(const FileName: string; IconList: TList): Boolean;
var
Stream: TMemoryStream;
NewIconStream: TMemoryStream;
IconDir: PICONDIR;
NewIconDir: PICONDIR;
Icon: TIcon;
I: Integer;
begin
Result := False;
if ExtractIcon(hInstance, PChar(FileName), UINT(-1)) <> 0 then
try
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile(FileName);
IconDir := Stream.Memory;
for I := 0 to IconDir.idCount-1 do
begin
NewIconStream := TMemoryStream.Create;
try
NewIconStream.Size := SizeOf(Word) * 3 + SizeOf(TICONDIRENTRY);
NewIconStream.Position:= SizeOf(Word) * 3 + SizeOf(TICONDIRENTRY);
NewIconDir := NewIconStream.memory;
NewIconDir.idCount := 1;
NewIconDir.idType := IconDir.idType;
NewIconDir.idReserved := IconDir.idReserved;
NewIconDir.idEntries[0] := IconDir.idEntries[I];
NewIconDir.idEntries[0].dwImageOffset := NewIconStream.Size;
Stream.Position := IconDir.idEntries[I].dwImageOffset;
NewIconStream.CopyFrom(Stream, IconDir.idEntries[I].dwBytesInRes);
NewIconStream.Position := 0;
Icon := TIcon.Create;
Icon.LoadFromStream(NewIconStream);
IconList.Add(Icon);
finally
NewIconStream.Free;
end;
IconList.Add(Icon);
end;
Result := True;
finally
Stream.Free;
end;
except
// do not raise exceptions
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
FileName: string;
Icon: TIcon;
List: TList;
I: Integer;
begin
FileName := 'c:\myicon.ico';
List := TList.Create;
try
if ExtractIcons(FileName, List) then
for I := 0 to List.Count - 1 do
begin
Icon := TIcon(List.Items[I]);
DrawIcon(Form1.Canvas.Handle, 10, I * 40, Icon.Handle);
Icon.Free;
end;
finally
List.Free;
end;
end;