When I run the prog below, the result value of the stgOpenStorage is STG_E_SHAREVIOLATION. How should I close the IStorage to get it unlocked?
procedure TForm1.btnSaveClick(Sender: TObject);
var
fileName : string;
streamName : string;
procedure storeTextIntoStorageStream( text_ : string );
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
if ( fileExists( fileName ) ) then
deleteFile( fileName );
stgCreateDocfile( #fileName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage );
try
documentStorage.CreateStream( #streamName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream );
try
i := length( text_ );
levelIStream.write( #i, sizeOf( integer ), #j );
levelIStream.write( #text_[1], i*sizeOf( char ), #j );
finally
levelIStream.Commit( 0 );
levelIStream := NIL;
end;
finally
documentStorage.Commit( 0 );
documentStorage := NIL;
end;
end;
function readTextFromStorageStream : string;
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
i := stgOpenStorage( #fileName[1], NIL, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, NIL, 0, documentStorage );
try
documentStorage.OpenStream( #streamName[1], NIL, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, levelIStream );
try
levelIStream.read( #i, sizeOf( integer ), #j );
setLength( result, i );
levelIStream.read( #result[1], i*sizeOf( char ), #j );
finally
levelIStream := NIL;
end;
finally
documentStorage := NIL;
end;
end;
begin
fileName := 'c:\temp\test.stg';
streamName := 'Stream-0';
storeTextIntoStorageStream( memo1.Lines.DelimitedText );
memo1.Lines.DelimitedText := readTextFromStorageStream;
end;
And how could I set the IStorage/IStream default size / size step? Because my test 1.6K byte content stored in 16K.
There are two IStorage implementations in the Delphi source libraries.
WinApi.OLE2 and WinApi.ActiveX. Which one do you use? In the WinApi.OLE2 unit, the IStorage and IStream are CLASSES, they are not INTERFACES. If you use this unit, the interface garbage collection and so the automatic closing does not work on the variables. If you use the WinApi.ActiveX unit, the example will work just fine.
The code looks fine 1, so I have a feeling that the problem is due to your use of STGM_SHARE_EXCLUSIVE. The file is in use at the time you are trying to open it, so I'm betting your OS/AV is the one keeping the file open (ie, to scan its content), not the interfaces in storeTextIntoStorageStream(), which are long gone by the time readTextFromStorageStream() is entered.
1: well, aside from the lack of adequate error handling. And the redundant nil'ing of interface variables. And, consider replacing your string indexes with PChar() casts instead.
In readTextFromStorageStream(), try replacing STGM_SHARE_EXCLUSIVE (which makes sense for a writer, but not a reader) with STGM_SHARE_DENY_WRITE instead and see if the error goes away:
procedure TForm1.btnSaveClick(Sender: TObject);
var
fileName : string;
streamName : string;
procedure storeTextIntoStorageStream( const text_ : string );
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
if ( FileExists( fileName ) ) then
DeleteFile( fileName );
OleCheck( StgCreateDocFile( PChar(fileName), STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage ));
try
OleCheck( documentStorage.CreateStream( PChar(streamName), STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream ) );
try
i := Length( text_ );
OleCheck( levelIStream.Write( #i, SizeOf( i ), #j ) );
if ( i > 0 ) then
OleCheck( levelIStream.Write( PChar(text_), i * SizeOf( Char ), #j ) );
finally
levelIStream.Commit( 0 );
end;
finally
documentStorage.Commit( 0 );
end;
end;
function readTextFromStorageStream : string;
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
Result := '';
OleCheck( StgOpenStorage( PChar(fileName), nil, STGM_READ or STGM_SHARE_DENY_WRITE or STGM_DIRECT, NIL, 0, documentStorage ) );
OleCheck( documentStorage.OpenStream( PChar(streamName), nil, STGM_READ or STGM_SHARE_DENY_WRITE or STGM_DIRECT, 0, levelIStream ) );
OleCheck( levelIStream.Read( #i, SizeOf( i ), #j ) );
if ( i > 0 ) then
begin
SetLength( Result, i );
OleCheck( levelIStream.Read( PChar(Result), i * SizeOf( Char ), #j ) );
end;
end;
begin
fileName := 'c:\temp\test.stg';
streamName := 'Stream-0';
storeTextIntoStorageStream( Memo1.Lines.DelimitedText );
Memo1.Lines.DelimitedText := readTextFromStorageStream;
end;
Related
I try to add some string (fake device) to Windows Device Manager. I tried this code but it doesn't work:
procedure AddSomeString(AHandle:THandle);
var
vItem: TLVItemW;
vPointer,vPointerText:Pointer;
vNumberOfBytesRead,vProcessId: SIZE_T;
vProcess: THandle;
vBuffer: array[ 0..255 ] of Char;
begin
GetWindowThreadProcessId( AHandle, #vProcessId );
vProcess := OpenProcess( PROCESS_ALL_ACCESS, False, vProcessId );
vPointer := VirtualAllocEx( vProcess, nil, 4096, MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE );
vPointerText := VirtualAllocEx( vProcess, nil,256,MEM_RESERVE or MEM_COMMIT,PAGE_READWRITE );
vBuffer := 'Test';
with vItem do
begin
mask := LVIF_TEXT;
iItem := 0;
iSubItem := 0;
cchTextMax := SizeOf( vBuffer );
pszText := vPointerText;
end;
WriteProcessMemory( vProcess, vPointer, #vItem, SizeOf( TLVItemW ), vNumberOfBytesRead );
WriteProcessMemory( vProcess, vPointerText, #vBuffer[ 0 ], SizeOf( vBuffer ), vNumberOfBytesRead );
SendMessage( AHandle, LVM_INSERTITEM, 0, lparam( vPointer ) );
SendMessage( AHandle, LVM_SETITEMTEXT, 0, lparam( vPointer ) );
VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE);
VirtualFreeEx(vProcess, vPointerText, 0, MEM_RELEASE);
CloseHandle(vProcess);
end;
I am passing to the function handle of SysTreeView32, but
SendMessage is always = 0
Can anyone suggest a working example?
I was trying out below code that should save clipboard text to a text file in Delphi XE6. The code runs fine but generates only junk values in the output file, even when the clipboard contains a copied text fragment. How can the code be changed to work properly?
function SaveClipboardTextDataToFile(
sFileTo : string ) : boolean;
var
ps1,
ps2 : PChar;
dwLen : DWord;
tf : TextFile;
hData : THandle;
begin
Result := False;
with Clipboard do
begin
try
Open;
if( HasFormat( CF_TEXT ) ) then
begin
hData :=
GetClipboardData( CF_TEXT );
ps1 := GlobalLock( hData );
dwLen := GlobalSize( hData );
ps2 := StrAlloc( 1 + dwLen );
StrLCopy( ps2, ps1, dwLen );
GlobalUnlock( hData );
AssignFile( tf, sFileTo );
ReWrite( tf );
Write( tf, ps2 );
CloseFile( tf );
StrDispose( ps2 );
Result := True;
end;
finally
Close;
end;
end;
end;
You see junk because CF_TEXT is ANSI. You request ANSI text, the OS converts the clipboard contents to ANSI, and you put it in unicode string. Use CF_UNICODETEXT for unicode applications.
Also consider the points raised in the comments to the question.
If you have Delphi XE6 then you can use some of the already implemented features
uses
System.SysUtils,
System.IOUtils,
Vcl.Clipbrd;
function SaveClipboardTextDataToFile( const sFileTo : string ) : boolean;
var
LClipboard : TClipboard;
LContent : string;
begin
// get the clipboard content as text
LClipboard := TClipboard.Create;
try
LContent := LClipboard.AsText;
finally
LClipboard.Free;
end;
// save the text - if any - into a file
if not LContent.IsEmpty
then
begin
TFile.WriteAllText( sFileTo, LContent );
Exit( True );
end;
Result := False;
end;
Is there a method to determine if an exe file has been compressed with UPX?
The function to determine if an exe file has been compressed is excellent except I found a problem with the code. If the function IsUPXCompressed is called then you try to run upx, upx can not save the file it modifies. There is something not sharing rights correctly in the function. I have tested this for several hours. If I do not call the method then UPX can write the files with no problem. You you call it then try to run UPX it will not save the file. UPX reports an IOException Permission denied error when trying to write the file.
Can anyone spot something wrong in the code that would cause this problem?
Thank-you
The function to determine if an exe file has been compressed is excellent except I found a problem with the code. If the function IsUPXCompressed is called then you try to run upx, upx can not save the file it modifies. There is something not sharing rights correctly in the function. I have tested this for several hours. If I do not call the method then UPX can write the files with no problem. You you call it then try to run UPX it will not save the file. UPX reports an IOException Permission denied error when trying to write the file.
Can anyone spot something wrong in the code that would cause this problem?
Thank-you
Another Method, when a exe is packed with the UPX tool, the section of the PE header contains sections called UPX0,UPX1, etc. so if read these sections and compare the name with the string UPX you can determine if the exe was compressed using the UPX packer.
check this function
uses
Windows;
function IsUPXCompressed(const Filename:TFileName): Boolean;
var
i : integer;
pBaseAddress : PByte;
pDosHeader : PImageDosHeader;
pNtHeaders : PImageNtHeaders;
hFile : Cardinal;
hFileMap : Cardinal;
pSectionHeader: PImageSectionHeader;
dwOffset : Cardinal;
SectName : AnsiString;
begin
Result:=False;
hFile := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile = INVALID_HANDLE_VALUE) then Exit;
hFileMap := CreateFileMapping(hFile, nil, PAGE_READONLY or SEC_IMAGE, 0, 0, nil);
if (hFileMap = 0) then
begin
CloseHandle(hFile);
Exit;
end;
pBaseAddress := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (pBaseAddress = nil) then
begin
CloseHandle(hFileMap);
CloseHandle(hFile);
Exit;
end;
try
dwOffset := Cardinal(pBaseAddress);
pDosHeader := PImageDosHeader(pBaseAddress);
pNtHeaders := PImageNtHeaders(dwOffset + Cardinal(pDosHeader._lfanew));
pSectionHeader := pImageSectionHeader(Cardinal(pNtHeaders) + SizeOf(TImageNtHeaders));
for i := 0 to pNtHeaders.FileHeader.NumberOfSections-1 do
begin
SetString(SectName, PAnsiChar(#pSectionHeader.Name), SizeOf(pSectionHeader.Name));
Result:=Pos('UPX',SectName)>0;
If Result then break;
Inc(pSectionHeader);
end;
finally
UnmapViewOfFile(pBaseAddress);
CloseHandle(hFileMap);
CloseHandle(hFile);
end;
end;
UPX itself does it like this:
if (memcmp(isection[0].name,"UPX",3) == 0)
throwAlreadyPackedByUPX();
This is the implementation for 32-bit PEs; 64-bit PEs need different offsets,
and other executable formats have to be handled separately.
#include <stdio.h>
typedef unsigned int uint;
uint peek_d( FILE* f, uint offs ) {
fseek( f, offs, SEEK_SET );
uint a = 0;
fread( &a, 1,sizeof(a), f );
return a;
}
int main( int argc, char** argv ) {
FILE* f = fopen( argv[1], "rb" ); if( f==0 ) return 1;
uint p,n,x,y;
p = peek_d( f, 0x3C ); // PE header offset
n = peek_d( f, p+0x74 ); // pointer table size
x = p + 0x78 + n*8;
y = peek_d( f, x+0*0x28+0 ); // 1st section name
if( (y&0xFFFFFF) == ('U'+('P'<<8)+('X'<<16)) ) {
printf( "UPX detected!\n" );
} else {
printf( "No UPX!\n" );
}
return 0;
}
try to uncompress it with upx?
// Returns IsUPXCompressed - Modified for Delphi 2010
function IsUPXCompressed( const Filename: TFileName ): Boolean;
var
i: integer;
pBaseAddress: PByte;
pDosHeader: PImageDosHeader;
pNtHeaders: PImageNtHeaders;
hFile: Cardinal;
hFileMap: Cardinal;
pSectionHeader: PImageSectionHeader;
dwOffset: Cardinal;
SectName: AnsiString;
begin
Result := False;
hFile := CreateFile( PChar( Filename ), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 );
if ( hFile = INVALID_HANDLE_VALUE ) then
Exit;
hFileMap := CreateFileMapping( hFile, nil, PAGE_READONLY or SEC_IMAGE, 0, 0, nil );
if ( hFileMap = 0 ) then
begin
CloseHandle( hFile );
Exit;
end;
pBaseAddress := MapViewOfFile( hFileMap, FILE_MAP_READ, 0, 0, 0 );
if ( pBaseAddress = nil ) then
begin
CloseHandle( hFileMap );
CloseHandle( hFile );
Exit;
end;
dwOffset := Cardinal( pBaseAddress );
pDosHeader := PImageDosHeader( pBaseAddress );
pNtHeaders := PImageNtHeaders( dwOffset + Cardinal( pDosHeader._lfanew ) );
pSectionHeader := pImageSectionHeader( Cardinal( pNtHeaders ) + SizeOf( TImageNtHeaders ) );
for i := 0 to pNtHeaders.FileHeader.NumberOfSections - 1 do
begin
SetString( SectName, PAnsiChar( #pSectionHeader.name ), SizeOf( pSectionHeader.name ) );
if Pos( 'UPX', SectName ) > 0 then
begin
Result := True;
exit;
end;
Inc( pSectionHeader );
end;
end;
Thanks Rob for the pointers.
The section names are not included the UPX word always. It mabebe contain another name changed by user. For certain. Ypu must search for UPX copmpressor signature in the whole file.
In my project there is a TADOQuery tdm_Company that gets filled with a set of fields, provided with proper labels and fields set to visible=false where appropriate.The query returns a single result.
I have a detail screen that needs a bunch of labels and edit textboxes for these fields.
Is it possible to auto generate these in the editor? What if I need those controls to be controls from the DevExpress components (TcxDBTextEdit and TcxLabel for example)?
I have never tried this, but there is (or was? - sorry, can't check) a Database Form Wizard. If you want to have other controls than those the wizard generates, there are possibilities to change these afterwards, e.g. GExperts' Replace Components.
In a very similar case (a query to return a single record showing contact data from an entity - company, customer etc.) we use DevExpress's TcxDBVerticalGrid. It scales much better and is more flexible (especially when resizing the form) when it comes to display a bunch of data which represents a single object.
Of course, you are not tied to the above component, you can obtain good results with (almost) any vertical grid / DBIspector but since you asked about a DevExpress component I gave you the above solution.
HTH
A long time ago I actually created my own Wizard for this, based on an actual Custom Form I wrote for a FrameWork of mine. When the Dialog for the wizard was shown, it would display all fields in a grid and allow the user to indicate which component should be used to display that field.
In my case depending on the Type of field it was prefilled with specific components (eg a TcxDateEdit for a TDateTime field, ...). The user could still change that though, and indicate which fields he wanted to add to the form. Once the user closes the form it was just matter of itterating over all the fields and creating the corresponding control.
Searched through my code and found this back :
{ Custom Devia Development Framework RecordView Module which adds functionality to
create the DB Aware Controls for the RecordView }
TDevFrameWorkRecordViewModule = class( TCustomModule )
protected
procedure CreateDBAwareComponents( aParent : TComponent; aDataSource : TDataSource; aFields : TFields; aFieldDefs : TFieldDefs ); virtual;
function DefaultWizardClass : TDBAwareControlWizardClass; virtual;
function DefaultLabelClass : TComponentClass; virtual;
function MaxFieldCaptionLength ( aFields : TFields ) : Integer; virtual;
protected
function GetSelectedComponents : IDesignerSelections;
function GetSelectedControl : TControl;
property SelectedControl : TControl read GetSelectedControl;
property SelectedComponents : IDesignerSelections read GetSelectedComponents;
public
procedure DevAddDBAwareComponentsWizard( aParent : TControl ); virtual;
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;
...
procedure TDevFrameWorkRecordViewModule.CreateDBAwareComponents(
aParent : TComponent; aDataSource : TDataSource; aFields : TFields; aFieldDefs : TFieldDefs );
var
lcv : Integer;
aLabel : TControl;
aEdit : TWinControl;
aDataBinding : TcxDBEditDataBinding;
aTop , aLeft : Integer;
aWidth : Integer;
aMaxCaptionWidth: Integer;
aDBLeft : Integer;
aRecordView : IDevFrameWorkRecordView;
aDBAwareClass : TComponentClass;
aDBAwareVisible : Boolean;
aWizardForm : TfrmDevFrameWorkAddDataAwareControlsWizard;
begin
{ First make sure the procedure was triggered on a FrameWorkRecordView }
if ( Supports( Root, IDevFrameWorkRecordView, aRecordView ) ) then
begin
{ Now Create and Show the wizard so the user can specify all the options }
aWizardForm := DefaultWizardClass.Create( Nil );
try
aWizardForm.RecordDataSet := aRecordView.DataSource.DataSet;
aWizardForm.InitialiseSettings;
{ If the user closed the Wizard using the OK button, we can continue the
process }
if ( aWizardForm.ShowModal = mrOK ) then
begin
{ By default the label components should start at 8,8 in the Parent Container }
aTop := 8;
aLeft := 8;
aWidth:= 121;
aMaxCaptionWidth := MaxFieldCaptionLength( aFields );
{ Now set the intial Left Position for our DBAware controls according
to the MaxCaptionWidth }
aDBLeft := 24 + ( ( ( aMaxCaptionWidth div 8 ) + 1 ) * 8 );
{ Loop over all fields to create the Label and DBAwareComponent }
for lcv := 0 to Pred( aFields.Count ) do
begin
{ Get some settings from the Wizard form }
aDBAwareClass := aWizardForm.GetDBAwareComponentClass( aFields[ lcv ] );
aDBAwareVisible := aWizardForm.GetDBAwareComponentVisible( aFields[ lcv ] );
{ Only create the components if the user indicated he wants to see them }
if aDBAwareVisible then
begin
{ Now create the Label and the DBAware Control }
aLabel := TControl ( Designer.CreateComponent( DefaultLabelClass, aParent, aLeft , aTop, aMaxCaptionWidth, 17 ) );
aEdit := TWinControl( Designer.CreateComponent( aDBAwareClass, aParent, aDBLeft, aTop, aWidth, 21 ) );
{ Now Set the Label Properties }
aLabel.Name := Designer.UniqueName( 'cxlbl' + aFields[ lcv ].FieldName );
aLabel.HelpType := htKeyWord;
aLabel.HelpKeyword := Root.Name + '.' + aFields[ lcv ].FieldName;
{ Set the additional properties using RTTI }
if ( IsPublishedProp( aLabel, 'FocusControl' ) ) then
begin
SetObjectProp( aLabel, 'FocusControl', aEdit );
end;
if ( IsPublishedProp( aLabel, 'Caption' ) ) then
begin
SetStrProp( aLabel, 'Caption', aFields[ lcv ].DisplayLabel );
end;
{ Now set the Edit Properites }
aEdit.Name := Designer.UniqueName( {'cxlbl' +} aFields[ lcv ].FieldName );
aEdit.HelpType := htKeyWord;
aEdit.HelpKeyword := Root.Name + '.' + aFields[ lcv ].FieldName;
{ Set the additional properties using RTTI }
if ( IsPublishedProp( aEdit, 'DataBinding' ) ) then
begin
aDataBinding := TcxDBEditDataBinding( GetObjectProp( aEdit, 'DataBinding' ) );
SetObjectProp( aDataBinding, 'DataSource', aDataSource );
SetStrProp ( aDataBinding, 'DataField' , aFields[ lcv ].FieldName );
end;
if ( aEdit is TcxCustomDropDownEdit ) then
begin
aEdit.Width := aWidth + 16;
end;
{ Now increment the Top position for the next control }
inc( aTop, ( ( ( aEdit.Height div 8 ) + 1 ) * 8 ) );
end;
end;
end;
finally
FreeAndNil( aWizardForm );
end;
end;
end;
function TDevFrameWorkRecordViewModule.DefaultLabelClass: TComponentClass;
begin
Result := TLabel;
end;
function TDevFrameWorkRecordViewModule.DefaultWizardClass: TDBAwareControlWizardClass;
begin
Result := TfrmDevFrameWorkAddDataAwareControlsWizard;
end;
procedure TDevFrameWorkRecordViewModule.ExecuteVerb(Index: Integer);
var
aSelections : IDesignerSelections;
lcv : Integer;
begin
aSelections := TDesignerSelections.Create;
Designer.GetSelections( aSelections );
for lcv := 0 to Pred( aSelections.Count ) do
begin
{$IFDEF CODESITE}
csFWRecordView.Send( 'aSelection.Items[ lcv ]', aSelections.Items[ lcv ] );
{$ENDIF}
end;
Case Index of
0 : DevAddDBAwareComponentsWizard( SelectedControl );
else Inherited ExecuteVerb( Index );
end;
end;
{*****************************************************************************
This function will be used to return a list of selected components on the
current designer.
#Name TDevFrameWorkRecordViewModule.GetSelectedComponents
#author Devia - Stefaan Lesage
#param None
#return None
#Exception None
#See None
******************************************************************************}
function TDevFrameWorkRecordViewModule.GetSelectedComponents: IDesignerSelections;
begin
Result := TDesignerSelections.Create;
Designer.GetSelections( Result );
end;
function TDevFrameWorkRecordViewModule.GetSelectedControl: TControl;
var
lcv : Integer;
begin
Result := Nil;
if ( Assigned( SelectedComponents ) ) then
begin
if ( SelectedComponents.Count <> 0 ) then
begin
for lcv := 0 to Pred( SelectedComponents.Count ) do
begin
if ( SelectedComponents.Items[ lcv ] is TControl ) then
begin
Result := TControl( SelectedComponents.Items[ lcv ] );
Break;
end;
end;
end;
end;
end;
function TDevFrameWorkRecordViewModule.GetVerb(Index: Integer): string;
begin
Case Index of
0 : Result := 'Dev.AddDataAwareComponents';
end;
end;
function TDevFrameWorkRecordViewModule.GetVerbCount: Integer;
begin
Result := 1;
end;
{*****************************************************************************
This function will determine the length of the Longest field's caption.
#Name TDevFrameWorkRecordViewModule.MaxFieldCaptionLength
#author Devia - Stefaan Lesage
#param None
#return Returns the length of the longest field's catpion.
#Exception None
#See None
******************************************************************************}
function TDevFrameWorkRecordViewModule.MaxFieldCaptionLength(
aFields: TFields): Integer;
var
aMaxCaptionWidth : Integer;
aCanvas : TCanvas;
lcv : Integer;
aCaption : String;
begin
aMaxCaptionWidth := 0;
{ First Determine how long the largest caption will be }
aCanvas := TDevFrameWorkRecordView( Root ).Canvas;
{ Loop over each field to dertermin which caption is the longest one }
for lcv := 0 to Pred( aFields.Count ) do
begin
if ( aFields[ lcv ].DisplayLabel <> '' ) then
begin
aCaption := aFields[ lcv ].DisplayLabel;
end
else
begin
aCaption := aFields[ lcv ].FieldName;
end;
if ( aCanvas.TextWidth( aCaption ) >
aMaxCaptionWidth ) then
begin
aMaxCaptionWidth := aCanvas.TextWidth( aCaption );
end;
end;
{ Return the Length of the Longest Caption }
Result := aMaxCaptionWidth;
end;
procedure TDevFrameWorkRecordViewModule.DevAddDBAwareComponentsWizard( aParent : TControl );
var
aRecordView : IDevFrameWorkRecordView;
aDataSource : TDataSource;
begin
{$IFDEF CODESITE}
csFWRecordView.EnterMethod( Self, 'DevAddDBAwareComponentsWizard' );
{$ENDIF}
if ( Supports( Root, IDevFrameWorkRecordView, aRecordView ) ) then
begin
{$IFDEF CODESITE}
csFWRecordView.SendMsg( csmInfo, 'Root supports I®FrameWorkRecordView' );
{$ENDIF}
aDataSource := TDataSource( Designer.GetComponent( 'srcMain' ) );
if ( Assigned( aDataSource ) ) and
( Assigned( aDataSource.DataSet ) ) then
begin
{$IFDEF CODESITE}
csFWRecordView.SendMsg( csmInfo, 'aRecordView.DataSource Assigned' );
csFWRecordView.SendMsg( csmInfo, 'aRecordView.DataSource.DataSet Assigned' );
{$ENDIF}
CreateDBAwareComponents( aParent, aDataSource, aDataSource.DataSet.Fields, aDataSource.DataSet.FieldDefs );
end;
end;
{$IFDEF CODESITE}
csFWRecordView.ExitMethod( Self, 'DevAddDBAwareComponentsWizard' );
{$ENDIF}
end;
Of course this won't compile for you. It is something I wrote for a development framework in Delphi 7 a few years ago. It should give you an idea though on how you could actually do it.
Regards,
Stefaan
Do you know any free component, compatible with Delphi 2010 or XE to manage ZIP archives (actually, only reading archive contents and extracting files required)?
Please no betas.
I thought about ZipForge from ComponentAce, but it's free only for personal use. No software distribution allowed.
You can get the TurboPower Abbrevia for 2010 from:
http://tpabbrevia.sourceforge.net/
you can take a look at this if you like 7zip
If you only need decoding (developed for Delphi 2007, not yet tested under Delphi 2010/XE):
unit UnitZip;
interface
uses
SysUtils, Classes;
type
EZipException = class( Exception );
TZipFileInfo = record
LastModified: TDateTime;
Crc32: Longword;
CompressedSize: Longword;
UncompressedSize: Longword;
end;
TZipFileReader = class
private
// Information about the memory mapped file
FFileHandle: THandle;
FFileMapping: THandle;
FMappedAddress: Pointer;
// Location of the ZIPfile in memory. Currently we only support memory mapped ZIPfiles without disk spanning.
FStart: Pointer;
FSize: Longword;
// ZIP file contents
FFilenames: TStrings;
function GetZipFileInfo ( const FileName: AnsiString ): TZipFileInfo;
public
constructor Create ( const FileName: string; ZipStartOffset: Int64 = 0; Size: Longword = 0 ); overload;
constructor Create ( const ResourceName, ResourceType: string; Instance: HMODULE = 0 ); overload;
constructor Create ( Buffer: Pointer; Size: Longword ); overload;
destructor Destroy; override;
function GetFile ( const FileName: string ): TBytes; overload;
function GetFile ( FileID: Integer ): TBytes; overload;
property FileNames: TStrings read FFileNames;
property FileInfo [ const FileName: AnsiString ]: TZipFileInfo read GetZipFileInfo;
end;
implementation
uses
ZLib, Windows;
const
cResourceNotFound = 'Resource not found: %s.%s.';
cResourceNotLoaded = 'Resource not loaded: %s.%s.';
cCannotOpenFile = 'Cannot open file %s: OS error: %d.';
cCannotGetFileSize = 'Cannot get file size of file %s: OS error: %d.';
cCannotMapFile = 'Cannot create file mapping of file %s: OS error: %d.';
cZipFileTooSmall = 'ZIP file is too small.';
cZipFileFormatError = 'ZIP file is invalid.';
cZipBufferInvalid = 'ZIP memory buffer is invalid.';
cUnsupportedMethod = 'ZIP unsupported compression method: %d.';
cFileNotFoundInZip = 'File not found in ZIP content: %s';
// ZIP file format records.
// The generic zip file format is ( TLocalFileHeader; Name; Extra; compressed data )* ( TFileHeader; Name; Extra; Remark )* TLastHeader
type
TFileInfo = packed record
NeededVersion: Word; // 20
Flags: Word; // 1=Text,4=extra present
ZipMethod: Word; // 0=stored 8=deflate
LastModified: Longword; // time in dos format or Unix Timestamp
Crc32: Longword;
CompressedSize: Longword;
UncompressedSize: Longword;
NameSize: Word;
ExtraSize: Word;
end;
TFileHeader = packed record
Signature: Longword; // $02014b50 PK#1#2
MadeBy: Word; // Version number, 20
FileInfo: TFileInfo;
CommentSize: Word; // 0
FirstDiskNumber: Word; // 0
IntFileAttr: Word; // 0 = binary; 1 = text
ExtFileAttr: Longword; // DOS file attributes (Archived=32)
LocalFileHeaderHeadOff: Longword; // #TLocalFileHeader
end;
PFileHeader = ^TFileHeader;
TLocalFileHeader = packed record
Signature: Longword; // $02014b50 PK#3#4
FileInfo: TFileInfo;
end;
PLocalFileHeader = ^TLocalFileHeader;
TLastHeader = packed record
Signature: Longword; // $02014b50 PK#5#6
ThisDiskNumber: Word;
CentralDirDisk: Word;
ThisDiskFileCount: Word;
TotalFileCount: Word;
FileHeaderSize: Longword;
FileHeaderOffset: Longword;
CommentSize: Word;
end;
PLastHeader = ^TLastHeader;
const
MagicLastHeader = $06054b50;
MagicLocalHeader = $04034b50;
MagicFileHeader = $02014b50;
type
IntPtr = Longword; // NativeInt on Delphi2007 is an Int64 ??
{$if CompilerVersion < 19}
procedure SetAnsiString( var S: AnsiString; P: PAnsiChar; L: Integer ); inline;
begin
SetString( S, P, L );
end;
{$ifend}
{ TZipFileReader }
constructor TZipFileReader.Create( const FileName: string; ZipStartOffset: Int64; Size: Longword );
begin
// Open the file in question.
FFileHandle := CreateFile( PChar( FileName ), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0 );
if FFileHandle = INVALID_HANDLE_VALUE then raise EZipException.CreateFmt( cCannotOpenFile, [ Filename, GetLastError() ] );
if Size = 0 then Size := GetFileSize( FFileHandle, nil );
if Size = INVALID_FILE_SIZE then raise EZipException.CreateFmt( cCannotGetFileSize, [ Filename, GetLastError() ] );
try
// Create a file mapping of the file in question
FFileMapping := CreateFileMapping( FFileHandle, nil, PAGE_READONLY, 0, 0, nil);
if FFileMapping = 0 then raise EZipException.CreateFmt( cCannotMapFile, [ Filename, GetLastError() ] );
try
// Get the file mapped in memory (NOTE: The offset needs to be on the memory allocation granularity of the system)
// Hence we assign it it's own pointer -> todo rounding etc.
FMappedAddress := MapViewOfFile( FFileMapping, FILE_MAP_READ, Int64Rec( ZipStartOffset ).Hi, Int64Rec( ZipStartOffset ).Lo, Size );
if not Assigned( FMappedAddress ) then EZipException.CreateFmt( cCannotMapFile, [ Filename, GetLastError() ] );
Create( FMappedAddress, Size );
except
CloseHandle( FFileMapping );
FFileMapping := 0;
raise;
end;
except
CloseHandle( FFileHandle );
FFileHandle := 0;
raise;
end;
end;
constructor TZipFileReader.Create( const ResourceName, ResourceType: string; Instance: HMODULE );
var
Resource: HRSRC;
Global: HGLOBAL;
begin
Resource := FindResource( Instance, PChar( ResourceName ), PChar( ResourceType ) );
if Resource = 0 then raise EZipException.CreateFmt( cResourceNotFound, [ ResourceName, ResourceType ] );
Global := LoadResource( Instance, Resource );
if Global = 0 then raise EZipException.CreateFmt( cResourceNotLoaded, [ ResourceName, ResourceType ] );
Create( LockResource( Global ), SizeofResource( HInstance, Resource ) );
// Note: kb57808: SizeofResource() returns the resource size rounded up to the alignment size.
end;
constructor TZipFileReader.Create( Buffer: Pointer; Size: Longword );
var
LastHeader: PLastHeader;
FileHeader: PFileHeader;
i, Off: Longword;
Name: AnsiString;
begin
// Note the location.
FStart := Buffer;
FSize := Size;
// Some sanity checks.
if FSize < sizeof( TLocalFileHeader ) + sizeof( TFileHeader ) + sizeof( TLastHeader ) then raise EZipException.Create( cZipFileTooSmall );
if IsBadReadPtr( Buffer, Size ) then raise EZipException.Create( cZipBufferInvalid );
if PLongword( Buffer )^ <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
// Find the last header. Due to the alignment of SizeofResource, we need o search for it.
LastHeader := Pointer( IntPtr( Buffer ) + Size - sizeof( TLastHeader ) );
for i := 0 to 31 do begin
if LastHeader^.Signature = MagicLastHeader then Break;
Dec( IntPtr( LastHeader ) );
end;
if LastHeader^.Signature <> MagicLastHeader then raise EZipException.Create( cZipFileFormatError );
FFilenames := TStringList.Create();
Off := LastHeader^.FileHeaderOffset;
for i := 0 to LastHeader^.TotalFileCount - 1 do begin
// Get header
if Off + sizeof( TFileHeader ) >= Size then raise EZipException.Create( cZipFileFormatError );
FileHeader := Pointer( IntPtr( Buffer ) + Off );
Inc( Off, sizeof( TFileHeader ) );
if FileHeader^.Signature <> MagicFileHeader then raise EZipException.Create( cZipFileFormatError );
// Get filename
if Off + FileHeader^.FileInfo.NameSize + FileHeader^.FileInfo.ExtraSize >= Size then raise EZipException.Create( cZipFileFormatError );
SetAnsiString( Name, Pointer( IntPtr( Buffer ) + Off ), FileHeader^.FileInfo.NameSize );
Inc( Off, FileHeader^.FileInfo.NameSize + FileHeader^.FileInfo.ExtraSize );
// Save filename and offset into ZIPfile where it can be found.
FFileNames.AddObject( Name, Pointer( FileHeader^.LocalFileHeaderHeadOff ) );
end;
// For quick access.
TStringList( FFilenames ).Sorted := True;
end;
destructor TZipFileReader.Destroy;
begin
if Assigned( FMappedAddress ) then UnmapViewOfFile( FMappedAddress );
if FFileMapping <> 0 then CloseHandle( FFileMapping );
if FFileHandle <> 0 then CloseHandle( FFileHandle );
inherited Destroy;
end;
function TZipFileReader.GetFile( const FileName: string ): TBytes;
var
ID: Integer;
begin
// Convert filename in FileID and access by ID.
ID := FFilenames.IndexOf( FileName );
if ID < 0 then raise EZipException.CreateFmt( cFileNotFoundInZip, [ FileName ] );
Result := GetFile( ID );
end;
function TZipFileReader.GetFile( FileID: Integer ): TBytes;
var
Off: Longword;
Local: PLocalFileHeader;
ZRec: TZStreamRec;
const
ZLibHeader: array [ 0..1 ] of Byte = ( $78, $01 ); // Deflate 32KB window size no preset dictionary.
begin
// Sanity check
if ( FileID < 0 ) or ( FileID >= FFilenames.Count ) then raise EZipException.CreateFmt( 'Invalid File ID: %d', [ FileID ] );
// Get the file header and perform sanity check
Off := Longword( FFilenames.Objects[ FileID ] );
if Off + sizeof( TLocalFileHeader ) >= FSize then raise EZipException.Create( cZipFileFormatError );
Local := Pointer( IntPtr( FStart ) + Off );
if Local^.Signature <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
Inc( Off, sizeof( TLocalFileHeader ) + Local^.FileInfo.NameSize + Local^.FileInfo.ExtraSize );
if Off + Local^.FileInfo.CompressedSize >= FSize then raise EZipException.Create( cZipFileFormatError );
// note: should we check the name again?
SetLength( Result, Local^.FileInfo.UncompressedSize );
if Length( Result ) > 0 then case Local^.FileInfo.ZipMethod of
0: begin // STORED
if Local^.FileInfo.CompressedSize <> Local^.FileInfo.UncompressedSize then raise EZipException.Create( cZipFileFormatError );
Move( Pointer( IntPtr( FStart ) + Off )^, Result[ 0 ], Local^.FileInfo.UncompressedSize );
end;
8: begin // DEFLATE
ZeroMemory( #ZRec, sizeof( ZRec ) );
ZRec.next_in := #ZLibHeader;
ZRec.avail_in := sizeof( ZLibHeader );
ZRec.total_in := sizeof( ZLibHeader ) + Local^.FileInfo.CompressedSize;
ZRec.next_out := #Result[ 0 ];
ZRec.avail_out := Local^.FileInfo.UncompressedSize;
ZRec.total_out := Local^.FileInfo.UncompressedSize;
ZRec.zalloc := zlibAllocMem;
ZRec.zfree := zlibFreeMem;
if inflateInit_( ZRec, zlib_Version, sizeof( ZRec ) ) <> 0 then raise EZipException.Create( cZipFileFormatError );
try
if not( inflate( ZRec, Z_FULL_FLUSH ) in [ Z_OK, Z_STREAM_END ] ) then raise EZipException.Create( cZipFileFormatError );
ZRec.next_in := Pointer( IntPtr( FStart ) + Off );
ZRec.avail_in := Local^.FileInfo.CompressedSize;
if not( inflate( ZRec, Z_FINISH ) in [ Z_OK, Z_STREAM_END ] ) then raise EZipException.Create( cZipFileFormatError );
finally
inflateEnd( ZRec );
end;
end;
else raise EZipException.CreateFmt( cUnsupportedMethod, [ Local^.FileInfo.ZipMethod ] );
end;
// todo: CRC32 sanity check if requested.
end;
function TZipFileReader.GetZipFileInfo( const FileName: AnsiString ): TZipFileInfo;
var
FileID: Integer;
Off: Longword;
Local: PLocalFileHeader;
begin
// Get the correct file ID
FileID := FFilenames.IndexOf( FileName );
if FileID < 0 then raise EZipException.CreateFmt( cFileNotFoundInZip, [ FileName ] );
// Get the file header and perform sanity check
Off := Longword( FFilenames.Objects[ FileID ] );
if Off + sizeof( TLocalFileHeader ) >= FSize then raise EZipException.Create( cZipFileFormatError );
Local := Pointer( IntPtr( FStart ) + Off );
if Local^.Signature <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
Inc( Off, sizeof( TLocalFileHeader ) + Local^.FileInfo.NameSize + Local^.FileInfo.ExtraSize );
if Off + Local^.FileInfo.CompressedSize >= FSize then raise EZipException.Create( cZipFileFormatError );
// Return requested data.
Result.LastModified := Local^.FileInfo.LastModified;
Result.Crc32 := Local^.FileInfo.Crc32;
Result.CompressedSize := Local^.FileInfo.CompressedSize;
Result.UncompressedSize := Local^.FileInfo.UncompressedSize;
end;
end.
Take a look at this OpenSource SynZip unit. It's even faster for decompression than the default unit shipped with Delphi, and it will generate a smaller exe (crc tables are created at startup).
No external dll is needed.
I just made some changes to handle Unicode file names inside Zip content, not only Win-Ansi charset but any Unicode chars. Feedback is welcome.
I like the WinZip compatible TZipMaster for Delphi, available here: http://www.delphizip.org/
TZipMaster is a non-visual VCL wrapper
created by ChrisVleghert and
EricW.Engler for their freeware Zip
and Unzip DLLs.
Those DLLs are based on the InfoZip
Official Freeware Zip/Unzip source
code, but are NOT equivalent to
InfoZip's DLLs. The InfoZip source
code has been modified to enhance
their ease-of-use, power, and
flexibility for use with Delphi and
C++ Builder.
Also, this question has been covered before on Stack Overflow, which may yield some other solutions for you.
If distributing an ActiveX DLL with your project is not a problem for you, then Chilkat Zip (http://www.chilkatsoft.com/zip-activex.asp) seems to do the trick. Delphi examples are here: http://www.example-code.com/delphi/zip.asp
DotNetZip is a managed code (.NET) library, that exposes a COM interface.
Free.
Open source
MS-PL licensed.