I haven't find a function to get a screenshot in FMX.Platform (anyway, nowhere else...).
With the VCL, there are many answers (stackoverflow, google, ...).
But how to get a screenshot in an image(bitmap or whatever) for Windows and Mac OS X?
Regards,
W.
Update:
The link from Tipiweb gives a good solution for OS X.
Regarding the Windows part: I have coded this, but I don't like to use the VCL, and a Stream to achieve it...
Any better suggestion, comments?
Thanks.
W.
uses ..., FMX.Types, Winapi.Windows, Vcl.Graphics;
...
function DesktopLeft: Integer;
begin
Result := GetSystemMetrics(SM_XVIRTUALSCREEN);
end;
function DesktopWidth: Integer;
begin
Result := GetSystemMetrics(SM_CXVIRTUALSCREEN);
end;
function DesktopTop: Integer;
begin
Result := GetSystemMetrics(SM_YVIRTUALSCREEN);
end;
function DesktopHeight: Integer;
begin
Result := GetSystemMetrics(SM_CYVIRTUALSCREEN);
end;
procedure GetScreenShot(var dest: FMX.Types.TBitmap);
var
cVCL : Vcl.Graphics.TCanvas;
bmpVCL: Vcl.Graphics.TBitmap;
msBmp : TMemoryStream;
begin
bmpVCL := Vcl.Graphics.TBitmap.Create;
cVCL := Vcl.Graphics.TCanvas.Create;
cVCL.Handle := GetWindowDC(GetDesktopWindow);
try
bmpVCL.Width := DesktopWidth;
bmpVCL.Height := DesktopHeight;
bmpVCL.Canvas.CopyRect(Rect(0, 0, DesktopWidth, DesktopHeight),
cVCL,
Rect(DesktopLeft, DesktopTop, DesktopLeft + DesktopWidth, DesktopTop + DesktopHeight)
);
finally
ReleaseDC(0, cVCL.Handle);
cVCL.Free;
end;
msBmp := TMemoryStream.Create;
try
bmpVCL.SaveToStream(msBmp);
msBmp.Position := 0;
dest.LoadFromStream(msBmp);
finally
msBmp.Free;
end;
I build a small application to take screenshot (Windows / Mac) and it works :-) !
For windows and Mac compatibility, I use a stream.
API Mac Capture --> TStream
API Windows Capture --> Vcl.Graphics.TBitmap --> TStream.
After that, I load my Windows or Mac TStream in a FMX.Types.TBitmap (with load from stream)
Windows Unit code :
unit tools_WIN;
interface
{$IFDEF MSWINDOWS}
uses Classes {$IFDEF MSWINDOWS} , Windows {$ENDIF}, System.SysUtils, FMX.Types, VCL.Forms, VCL.Graphics;
procedure TakeScreenshot(Dest: FMX.Types.TBitmap);
{$ENDIF MSWINDOWS}
implementation
{$IFDEF MSWINDOWS}
procedure WriteWindowsToStream(AStream: TStream);
var
dc: HDC; lpPal : PLOGPALETTE;
bm: TBitMap;
begin
{test width and height}
bm := TBitmap.Create;
bm.Width := Screen.Width;
bm.Height := Screen.Height;
//get the screen dc
dc := GetDc(0);
if (dc = 0) then exit;
//do we have a palette device?
if (GetDeviceCaps(dc, RASTERCAPS) AND RC_PALETTE = RC_PALETTE) then
begin
//allocate memory for a logical palette
GetMem(lpPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
//zero it out to be neat
FillChar(lpPal^, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)), #0);
//fill in the palette version
lpPal^.palVersion := $300;
//grab the system palette entries
lpPal^.palNumEntries :=GetSystemPaletteEntries(dc,0,256,lpPal^.palPalEntry);
if (lpPal^.PalNumEntries <> 0) then
begin
//create the palette
bm.Palette := CreatePalette(lpPal^);
end;
FreeMem(lpPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
end;
//copy from the screen to the bitmap
BitBlt(bm.Canvas.Handle,0,0,Screen.Width,Screen.Height,Dc,0,0,SRCCOPY);
bm.SaveToStream(AStream);
FreeAndNil(bm);
//release the screen dc
ReleaseDc(0, dc);
end;
procedure TakeScreenshot(Dest: FMX.Types.TBitmap);
var
Stream: TMemoryStream;
begin
try
Stream := TMemoryStream.Create;
WriteWindowsToStream(Stream);
Stream.Position := 0;
Dest.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
{$ENDIF MSWINDOWS}
end.
Mac Unit Code :
unit tools_OSX;
interface
{$IFDEF MACOS}
uses
Macapi.CoreFoundation, Macapi.CocoaTypes, Macapi.CoreGraphics, Macapi.ImageIO,
FMX.Types,
system.Classes, system.SysUtils;
procedure TakeScreenshot(Dest: TBitmap);
{$ENDIF MACOS}
implementation
{$IFDEF MACOS}
{$IF NOT DECLARED(CGRectInfinite)}
const
CGRectInfinite: CGRect = (origin: (x: -8.98847e+30; y: -8.98847e+307);
size: (width: 1.79769e+308; height: 1.79769e+308));
{$IFEND}
function PutBytesCallback(Stream: TStream; NewBytes: Pointer;
Count: LongInt): LongInt; cdecl;
begin
Result := Stream.Write(NewBytes^, Count);
end;
procedure ReleaseConsumerCallback(Dummy: Pointer); cdecl;
begin
end;
procedure WriteCGImageToStream(const AImage: CGImageRef; AStream: TStream;
const AType: string = 'public.png'; AOptions: CFDictionaryRef = nil);
var
Callbacks: CGDataConsumerCallbacks;
Consumer: CGDataConsumerRef;
ImageDest: CGImageDestinationRef;
TypeCF: CFStringRef;
begin
Callbacks.putBytes := #PutBytesCallback;
Callbacks.releaseConsumer := ReleaseConsumerCallback;
ImageDest := nil;
TypeCF := nil;
Consumer := CGDataConsumerCreate(AStream, #Callbacks);
if Consumer = nil then RaiseLastOSError;
try
TypeCF := CFStringCreateWithCharactersNoCopy(nil, PChar(AType), Length(AType),
kCFAllocatorNull); //wrap the Delphi string in a CFString shell
ImageDest := CGImageDestinationCreateWithDataConsumer(Consumer, TypeCF, 1, AOptions);
if ImageDest = nil then RaiseLastOSError;
CGImageDestinationAddImage(ImageDest, AImage, nil);
if CGImageDestinationFinalize(ImageDest) = 0 then RaiseLastOSError;
finally
if ImageDest <> nil then CFRelease(ImageDest);
if TypeCF <> nil then CFRelease(TypeCF);
CGDataConsumerRelease(Consumer);
end;
end;
procedure TakeScreenshot(Dest: TBitmap);
var
Screenshot: CGImageRef;
Stream: TMemoryStream;
begin
Stream := nil;
ScreenShot := CGWindowListCreateImage(CGRectInfinite,
kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault);
if ScreenShot = nil then RaiseLastOSError;
try
Stream := TMemoryStream.Create;
WriteCGImageToStream(ScreenShot, Stream);
Stream.Position := 0;
Dest.LoadFromStream(Stream);
finally
CGImageRelease(ScreenShot);
Stream.Free;
end;
end;
{$ENDIF MACOS}
end.
In your mainForm unit :
...
{$IFDEF MSWINDOWS}
uses tools_WIN;
{$ELSE}
uses tools_OSX;
{$ENDIF MSWINDOWS}
...
var
imgDest: TImageControl;
...
TakeScreenshot(imgDest.Bitmap);
If you have another idea, please talk to me :-)
Thanks to Tipiweb's code (in his answer), a github project has been started based on it; with some improvements (ability to take a screenshot only of a certain window, or take a full screenshot).
The unit is named xscreenshot.pas (single unit for all platforms)
The github project page:
https://github.com/z505/screenshot-delphi
The utilities available in this unit:
// take screenshot of full screen
procedure TakeScreenshot(...)
// take screenshot only of a specific window
procedure TakeWindowShot(...)
Finishing touches on MacOS need some work for taking a screenshot of a specific window.
Again, thanks to Tipiweb and his answer to get this project started.
You can use a good solution from this site to do a Mac OSX screenshot.
Do the same works with the Windows API like this:
procedure ScreenShot(x, y, Width, Height: integer; bm: TBitMap);
var
dc: HDC; lpPal : PLOGPALETTE;
begin
{test width and height}
if ((Width = 0) OR (Height = 0)) then exit;
bm.Width := Width;
bm.Height := Height;
//get the screen dc
dc := GetDc(0);
if (dc = 0) then exit;
//do we have a palette device?
if (GetDeviceCaps(dc, RASTERCAPS) AND RC_PALETTE = RC_PALETTE) then
begin
//allocate memory for a logical palette
GetMem(lpPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
//zero it out to be neat
FillChar(lpPal^, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)), #0);
//fill in the palette version
lpPal^.palVersion := $300;
//grab the system palette entries
lpPal^.palNumEntries :=GetSystemPaletteEntries(dc,0,256,lpPal^.palPalEntry);
if (lpPal^.PalNumEntries <> 0) then
begin
//create the palette
bm.Palette := CreatePalette(lpPal^);
end;
FreeMem(lpPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
end;
//copy from the screen to the bitmap
BitBlt(bm.Canvas.Handle,0,0,Width,Height,Dc,x,y,SRCCOPY);
//release the screen dc
ReleaseDc(0, dc);
end;
After that, include your different units with:
uses
{$IFDEF MSWINDOWS}
mytools_win,
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
mytools_mac,
{$ENDIF MACOS}
Related
I am developing an application in Delphi XE2 which inspects, through the functions EnumWindows and EnumChildWindows a window of a running application also written in Delphi.
This is the main code (adapted from an example: http://www.swissdelphicenter.ch/torry/showcode.php?id=410)
function EnumChildWindowsProc(Wnd: HWnd; Form: TForm1): Bool; export;
{$ifdef Win32} stdcall; {$endif}
var
Buffer: array[0..99] of Char;
begin
GetWindowText(Wnd, Buffer, 100);
if StrPas(Buffer) = '' then Buffer := 'Empty';
new(AWindows);
with AWindows^ do
begin
WindowHandle := Wnd;
WindowText := StrPas(Buffer);
end;
CNode := Form1.TreeView1.Items.AddChildObject(PNode,
AWindows^.WindowText + ':' +
IntToHex(AWindows^.WindowHandle, 8), AWindows);
if GetWindow(Wnd, GW_CHILD) = 0 then
begin
PNode := CNode;
Enumchildwindows(Wnd, #EnumChildWindowsProc, 0);
end;
Result := True;
end;
function EnumWindowsProc(Wnd: HWnd; Form: TForm1): Bool;
export; {$ifdef Win32} stdcall; {$endif}
var
Buffer: array[0..99] of Char;
begin
GetWindowText(Wnd, Buffer, 100);
if StrPas(Buffer) = '' then Buffer := 'Empty';
new(AWindows);
with AWindows^ do
begin
WindowHandle := Wnd;
WindowText := StrPas(Buffer);
end;
if Pos(Form1.edAppToFind.Text,AWindows^.WindowText) > 0 then // <- inspect child only for my Application
begin
PNode := Form1.TreeView1.Items.AddObject(nil, AWindows^.WindowText + ':' +
IntToHex(AWindows^.WindowHandle, 8), AWindows);
EnumChildWindows(Wnd, #EnumChildWindowsProc, 0);
end;
Result := True;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
EnumWindows(#EnumWindowsProc, self.Handle);
end;
Everything works well, except for the object TGroupBox after which the recursion stops. But control TGroupBox contains inside other elements (TLabel).
In fact, even writing a simple application in Delphi, by including in the Form a TGroupBox and then into the TGroupBox a TLabel, launching the Application and inspecting it with Spy++ (or with the Tool Autoit AU3Info) you can not enter into the TGroupBox: the TLabel inside is not inspected.
Is there a way to find TLabel control within the TGroupBox?
This is not an issue with the group box control. The issue is that the TLabel control is not windowed. There's no window handle associated with it and so it cannot be found by Spy++, EnumChildWindows etc.
i would like to port the following short code fragment from VCL to FM using Delphi XE2
with TFileOpenDialog.Create(nil) do
try
Title := 'Select Directory';
Options := [fdoPickFolders, fdoPathMustExist, fdoForceFileSystem];
OkButtonLabel := 'Select';
DefaultFolder := FDir;
FileName := FDir;
if Execute then
ShowMessage(FileName);
finally
Free;
end
I could not find the help to import the relevant units using FM framework. At least the filecrtl unit seem to be not available any longer
Furthermore the solution relevant question does nor come up with a window in my test application.
This works fine with a new FireMonkey HD application (running on Win7 64, both as a Win32 and Win64 target) in XE4.
It of course is specific to Windows. You can use OS-version specifics (WinVistaSelectFolder or WinXPSelectFolder), or just call the generic SelectFolder which does that for you.
unit WinFolderSelectUtils;
interface
uses
SysUtils;
function SelectFolder: string;
function WinVistaSelectFolder: string;
function WinXPSelectFolder: string;
implementation
uses
ShellAPI, ShlObj, ActiveX, Windows;
function SelectFolder: string;
begin
if TOSVersion.Check(6) then
Result := WinVistaSelectFolder
else
Result := WinXPSelectFolder;
end;
function WinXPSelectFolder: string;
var
BrowseInfo: TBrowseInfo;
ItemIDList: PItemIDList;
ItemSelected: PItemIDList;
NameBuffer: array[0..MAX_PATH] of Char;
begin
Result := '';
// Should be doing some error handling here. Omitted for clarity, but
// obviously should raise some sort of exception if anything fails instead
// of just returning an empty string.
ItemIDList := nil;
FillChar(BrowseInfo, SizeOf(BrowseInfo), 0);
BrowseInfo.hwndOwner := 0;
BrowseInfo.pidlRoot := ItemIDList;
BrowseInfo.pszDisplayName := NameBuffer;
BrowseInfo.lpszTitle := 'Select a directory';
BrowseInfo.ulFlags := BIF_RETURNONLYFSDIRS;
ItemSelected := SHBrowseForFolder(BrowseInfo);
if ItemSelected <> nil then
begin
SHGetPathFromIDList(ItemSelected, NameBuffer);
Result := NameBuffer;
end;
CoTaskMemFree(BrowseInfo.pidlRoot);
end;
function WinVistaSelectFolder: String;
var
FileDialog: IFileDialog;
hr: HRESULT;
IResult: IShellItem;
FileName: PWideChar;
Settings: Cardinal;
Title: String;
begin
hr := CoCreateInstance(CLSID_FileOpenDialog,
nil,
CLSCTX_INPROC_SERVER,
IFileDialog,
FileDialog);
if hr = S_OK then
begin
FileDialog.GetOptions(Settings);
Settings := Settings or FOS_PICKFOLDERS or FOS_FORCEFILESYSTEM;
FileDialog.SetOptions(Settings);
FileDialog.SetOkButtonLabel('Select');
Title := 'Select a directory';
FileDialog.SetTitle(PWideChar(Title));
hr := FileDialog.Show(0);
if hr = S_OK then
begin
hr := FileDialog.GetResult(IResult);
if hr = S_OK then
begin
IResult.GetDisplayName(SIGDN_FILESYSPATH, FileName);
Result := FileName;
end;
end;
end;
end;
end.
Since you are using Windows, you can use the code in the question even in a FireMonkey app. I think you need to put the code into a unit which does not use any FMX units, but otherwise it works fine. You'll need to use Vcl.Dialogs in that unit.
I'm developing a Tool to help me find memory leak on my application.
The feature runs well while I'm working with object (TOBJECT), but I'm getting some problems while I'm working with buffer.
In SOME cases, that I could not identify I got some errors in my application, this errors seems to be some bad access to memory. I could not find any possible error on my logic or my code. If some one with more experience with Delphi can help me. Maybe some memory manager particular behavior is causing the problem.
A little more explanations:
Memory Allocation Control
Objective: Count how much memory buffer with determined size is all allocated by the system, exemple:
Buffer Size | Amount of Allocs | Total Memory Used
325 | 35265 | 11461125
23 | 32 | 736
... | ... | ...
How I control the memory allocation and deallocation:
I created an array of integer that goes from 0 to 65365. This array will be used to keep the amount of allocs of the corresponding size.
For example, If I call GetMem for a buffer of 523, the Array[523] will increase + 1.
The GetMem, ReallocMem, AllocMem, the problem is easy to resolve 'cause one of it's parameters is the size of the buffer. So I can use this to increase the position of the array.
The problem cames with the FreeMem, 'cause the only parameter is the pointer of the buffer. I don't know it's size.
- I can't create a list to keep the Pointer and it's size. 'Cause there is SO much allocations, it will be so much expensive to the application keep searching/adding/removing items from this list. And this list must to be protected with critical section etc etc. So no way.
How I'm trying to solve this problem:
Just to remeber I created the array to keep the number off allocations.
Items: 0 65365
|................................|
Addess: $X $(65365x SizeOf(Integer))
When allocators methos are called, for example: GetMem(52);
I changed the behavior of it, I will alloc the requested size (52), but I'll add here a size of an integer;
So I will have:
0 4 56
|.....|...........................|
$x
In the plus space (0..3) I'll set the address of the corresponding space of the array. In this case the address position $array(52). And I add + (SizeOf(Integer)) to the address result of the GetMem, so it will have access just the 52 bytes that were asked for.
When the FreeMem are called. What I do is:
- Get the pointer asked for deallocation.
- Decrease the pointer by the size of the integer
- Check if the address of the current pointer is relative to the Array of control address.
- If it is, I use the the address and decrease 1 from the Array position
- And ask for the FreeMem
In the biggest part of time and systems it's working very well. BUT, In some moments that I really don't know how and were I get some strange errors in system. Erros that I NEVER get if I deactive this implementation.
I'm commenting the code to get easier to be understood, but It's not a hard code so, here it's:
Other thread in: https://forums.embarcadero.com/thread.jspa?threadID=77787
unit uInstancesAnalyser;
{
Functionality: The feature developed in this unit try to watch how the memory are being allocated by your system. The main focus of it is help to find memory leak in the most non intrusive way.
How to Install: Put this unit as the first unit of yout project. If use use a third memory manager put this unit just after the unit of your memory manager.
How to get it's report: It's not the final version of this unit, so the viewer was not developed. By the momento you can call the
method SaveInstancesToFile. It'll create a text file called MemReport in the executable path.
WARNING: If you use the pointer of the VMT destinated to vmtAutoTable, you should not use the directive TRACEINSTANCES.
How it works:
The feature work in two different approaches:
1) Map the memory usage by objects
2) Map the memory usage by buffers (Records, strings and so on)
How are Objects tracked:
The TObject.NewInstance was replaced by a new method (TObjectHack.NNewInstanceTrace).
So when the creation of an object is called it's redirect to the new method. In this new method is increased the counter of the relative class and change the method in the VMT that is responsible to free the object to a new destructor method (vmtFreeInstance). This new destructor call the decrease of the counter and the old destructor.
This way I can know how much of objects of each class are alive in the system.
(More details about how it deep work can be found in the comments on the code)
How are Memory Buffer Traced:
The GetMem, FreeMem, ReallocMem, AllocMem were replaced by new method that have an special behavior to help track the buffers.
As the memory allocation use the same method to every kind of memory request, I'm not able to create a single counter to each count of buffer. So, I calculate them base on it size. First I create a array of integer that start on 0 and goes to 65365.
When the system ask me to give it a buffer of 65 bytes, I increase the position 65 of the array and the buffer is deallocated I call the decrease of the position of the array corresponding to buffer size. If the size requested to the buffer is bigger or equal to 65365, I'll use the position 65365 of the array.
(More details about how it deep work can be found in the comments on the code)
--------------------------------------------------------------------------------------
Develop by Rodrigo Farias Rezino
E-mail: rodrigofrezino#gmail.com
Stackoverflow: http://stackoverflow.com/users/225010/saci
Please, any bug let me know
}
interface
{$DEFINE TRACEBUFFER} {Directive used to track buffer} //Comment to inactive
{$DEFINE TRACEINSTANCES} {Directive used to track objects} //Comment to inactive
//{$DEFINE WATCHTHREADS} // It's not finished
uses
Classes, SyncObjs, uIntegerList;
{You can register possibles names for some Buffers Sizes, it can be useful when you are working with record. Example
TRecordTest = record
Field1: Integer
Field2: string[50]
So, you can call RegisterNamedBuffer(TRecordTest, SizeOf(TRecordTest));
This way, in on the report of buffer/objects will be explicit what possibles named buffer can be that memory in use.}
procedure RegisterNamedBuffer(ABufferName: string; ASize: integer);
{This function return the possible named buffers registered with that size}
function GetBufferName(ASize: integer): string;
{It's a simple output to save the report of memory usage on the disk. It'll create a file called test.txt in the executable directory}
procedure SaveInstancesToFile;
var
{Flag to say if the memory watcher is on or off}
SIsMemoryWatcherActive: Boolean;
implementation
uses
Windows, SysUtils, TypInfo;
const
SIZE_OF_INT = SizeOf(Integer);
SIZE_OF_MAP = 65365;
{$IFDEF WATCHTHREADS}
GAP_SIZE = SIZE_OF_INT * 2;
{$ELSE}
GAP_SIZE = SIZE_OF_INT;
{$ENDIF}
type
TArrayOfMap = array [0..SIZE_OF_MAP] of Integer;
TThreadMemory = array [0..SIZE_OF_MAP] of Integer;
{This class is used to Register}
TNamedBufferList = class(TIntegerList)
public
constructor Create;
function GetBufferName(ASize: integer): string;
procedure RegisterNamedBuffer(ABufferName: string; ASize: integer);
end;
PClassVars = ^TClassVars;
TClassVars = class(TObject)
private
class var ListClassVars: TList;
public
BaseInstanceCount: Integer;
BaseClassName: string;
BaseParentClassName: string;
BaseInstanceSize: Integer;
OldVMTFreeInstance: Pointer;
constructor Create;
class procedure SaveToDisk;
end;
TNamedBuffer = class(TObject)
Names: string;
end;
PJump = ^TJump;
TJump = packed record
OpCode: Byte;
Distance: Pointer;
end;
TObjectHack = class(TObject)
private
FCriticalSection: TCriticalSection;
class procedure SetClassVars(AClassVars: TClassVars); //inline;
class function GetClassVars: TClassVars; inline;
procedure IncCounter; inline;
procedure DecCounter; inline;
procedure CallOldFunction;
public
constructor Create;
destructor Destroy; override;
class function NNewInstance: TObject;
class function NNewInstanceTrace: TObject;
procedure NFreeInstance;
end;
var
SDefaultGetMem: function(Size: Integer): Pointer;
SDefaultFreeMem: function(P: Pointer): Integer;
SDefaultReallocMem: function(P: Pointer; Size: Integer): Pointer;
SDefaultAllocMem: function(Size: Cardinal): Pointer;
SThreadMemory: TThreadMemory;
SMap: TArrayOfMap;
SNamedBufferList: TNamedBufferList;
{$IFDEF WATCHTHREADS}
SMissedMemoryFlow: Integer;
{$ENDIF}
{$REGION 'Util'}
type
TWinVersion = (wvUnknown, wv95, wv98, wv98SE, wvNT, wvME, wv2000, wvXP, wvVista, wv2003, wv7);
function GetWinVersion: TWinVersion;
var
osVerInfo: TOSVersionInfo;
majorVersion, minorVersion: Integer;
begin
Result := wvUnknown;
osVerInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
if GetVersionEx(osVerInfo) then
begin
minorVersion := osVerInfo.dwMinorVersion;
majorVersion := osVerInfo.dwMajorVersion;
case osVerInfo.dwPlatformId of
VER_PLATFORM_WIN32_NT:
begin
if majorVersion <= 4 then
Result := wvNT
else if (majorVersion = 5) and (minorVersion = 0) then
Result := wv2000
else if (majorVersion = 5) and (minorVersion = 1) then
Result := wvXP
else if (majorVersion = 5) and (minorVersion = 2) then
Result := wv2003
else if (majorVersion = 6) then
Result := wvVista
else if (majorVersion = 7) then
Result := wv7;
end;
VER_PLATFORM_WIN32_WINDOWS:
begin
if (majorVersion = 4) and (minorVersion = 0) then
Result := wv95
else if (majorVersion = 4) and (minorVersion = 10) then
begin
if osVerInfo.szCSDVersion[1] = 'A' then
Result := wv98SE
else
Result := wv98;
end
else if (majorVersion = 4) and (minorVersion = 90) then
Result := wvME
else
Result := wvUnknown;
end;
end;
end;
end;
function GetMethodAddress(AStub: Pointer): Pointer;
const
CALL_OPCODE = $E8;
begin
if PBYTE(AStub)^ = CALL_OPCODE then
begin
Inc(Integer(AStub));
Result := Pointer(Integer(AStub) + SizeOf(Pointer) + PInteger(AStub)^);
end
else
Result := nil;
end;
procedure AddressPatch(const ASource, ADestination: Pointer);
const
JMP_OPCODE = $E9;
SIZE = SizeOf(TJump);
var
NewJump: PJump;
OldProtect: Cardinal;
begin
if VirtualProtect(ASource, SIZE, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
NewJump := PJump(ASource);
NewJump.OpCode := JMP_OPCODE;
NewJump.Distance := Pointer(Integer(ADestination) - Integer(ASource) - 5);
FlushInstructionCache(GetCurrentProcess, ASource, SizeOf(TJump));
VirtualProtect(ASource, SIZE, OldProtect, #OldProtect);
end;
end;
function PatchCodeDWORD(ACode: PDWORD; AValue: DWORD): Boolean;
var
LRestoreProtection, LIgnore: DWORD;
begin
Result := False;
if VirtualProtect(ACode, SizeOf(ACode^), PAGE_EXECUTE_READWRITE, LRestoreProtection) then
begin
Result := True;
ACode^ := AValue;
Result := VirtualProtect(ACode, SizeOf(ACode^), LRestoreProtection, LIgnore);
if not Result then
Exit;
Result := FlushInstructionCache(GetCurrentProcess, ACode, SizeOf(ACode^));
end;
end;
{$ENDREGION}
function GetInstanceList: TList;
begin
Result := TClassVars.ListClassVars;
end;
procedure SaveInstancesToFile;
begin
TClassVars.SaveToDisk;
end;
procedure OldNewInstance;
asm
call TObject.NewInstance;
end;
procedure OldAfterConstruction;
asm
call TObject.InitInstance;
end;
{ TObjectHack }
type
TExecute = procedure of object;
procedure TObjectHack.CallOldFunction;
var
Routine: TMethod;
Execute: TExecute;
begin
Routine.Data := Pointer(Self);
Routine.Code := GetClassVars.OldVMTFreeInstance;
Execute := TExecute(Routine);
Execute;
end;
constructor TObjectHack.Create;
begin
end;
procedure TObjectHack.DecCounter;
var
ThreadId: Cardinal;
begin
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] - Self.InstanceSize;
ThreadId := 0;
if (Self.ClassType.InheritsFrom(TThread)) then
ThreadId := TThread(Self).ThreadID;
{$ENDIF}
GetClassVars.BaseInstanceCount := GetClassVars.BaseInstanceCount -1;
CallOldFunction;
{$IFDEF WATCHTHREADS}
if ThreadId <> 0 then
begin
if SThreadMemory[ThreadId] < 0 then
SMissedMemoryFlow := SMissedMemoryFlow + SThreadMemory[ThreadId];
SThreadMemory[ThreadId] := 0;
end;
{$ENDIF}
end;
destructor TObjectHack.Destroy;
begin
inherited;
end;
class function TObjectHack.GetClassVars: TClassVars;
begin
Result := PClassVars(Integer(Self) + vmtAutoTable)^;
end;
function _InitializeHook(AClass: TClass; AOffset: Integer; HookAddress: Pointer): Boolean;
var
lAddress: Pointer;
lProtect: DWord;
begin
lAddress := Pointer(Integer(AClass) + AOffset);
Result := VirtualProtect(lAddress, 4, PAGE_READWRITE, #lProtect);
if not Result then
Exit;
CopyMemory(lAddress, #HookAddress, 4);
Result := VirtualProtect(lAddress, 4, lProtect, #lProtect);
end;
class procedure TObjectHack.SetClassVars(AClassVars: TClassVars);
begin
AClassVars.BaseClassName := Self.ClassName;
AClassVars.BaseInstanceSize := Self.InstanceSize;
AClassVars.OldVMTFreeInstance := PPointer(Integer(TClass(Self)) + vmtFreeInstance)^;
if Self.ClassParent <> nil then
AClassVars.BaseParentClassName := Self.ClassParent.ClassName;
PatchCodeDWORD(PDWORD(Integer(Self) + vmtAutoTable), DWORD(AClassVars));
_InitializeHook(Self, vmtFreeInstance, #TObjectHack.DecCounter);
end;
procedure RegisterClassVarsSupport(const Classes: array of TObjectHack);
var
LClass: TObjectHack;
begin
for LClass in Classes do
if LClass.GetClassVars = nil then
LClass.SetClassVars(TClassVars.Create)
else
raise Exception.CreateFmt('Class %s has automated section or duplicated registration.', [LClass.ClassName]);
end;
procedure TObjectHack.IncCounter;
begin
if GetClassVars = nil then
RegisterClassVarsSupport(Self);
GetClassVars.BaseInstanceCount := GetClassVars.BaseInstanceCount + 1;
end;
{ TClassVars }
constructor TClassVars.Create;
begin
ListClassVars.Add(Self);
end;
class procedure TClassVars.SaveToDisk;
var
LStringList: TStringList;
i: Integer;
begin
LStringList := TStringList.Create;
try
LStringList.Add('CLASS | NUMBER OF INSTANCES');
{$IFDEF TRACEINSTANCES}
for i := 0 to ListClassVars.Count -1 do
if TClassVars(ListClassVars.Items[I]).BaseInstanceCount > 0 then
LStringList.Add(TClassVars(ListClassVars.Items[I]).BaseClassName + '|' + IntToStr(TClassVars(ListClassVars.Items[I]).BaseInstanceCount));
{$ENDIF}
{$IFDEF TRACEBUFFER}
for I := 0 to SIZE_OF_MAP do
if SMap[I] > 0 then
LStringList.Add(Format('Mem. Size: %d | Amount: %d', [I, SMap[I]]));
{$ENDIF}
LStringList.SaveToFile(ExtractFilePath(ParamStr(0)) + 'MemReport.txt');
finally
FreeAndNil(LStringList);
end;
end;
//////////////////////////////////////////////////////////////////////////////////////
/// Memory manager controller
function IsInMap(AValue: Integer): Boolean; inline;
begin
try
Result := (AValue > Integer(#SMap)) and (AValue <= Integer(#SMap[SIZE_OF_MAP]));
except
Result := False;
end;
end;
function MemorySizeOfPos(APos: Integer): Integer; inline;
begin
Result := (APos - Integer(#SMap)) div SIZE_OF_INT;
end;
function NAllocMem(Size: Cardinal): Pointer;
var
pIntValue: ^Integer;
MapSize: Integer;
ThreadId: Cardinal;
begin
if Size > SIZE_OF_MAP then
MapSize := SIZE_OF_MAP
else
MapSize := Size;
Result := SDefaultAllocMem(Size + GAP_SIZE);
pIntValue := Result;
SMap[MapSize] := SMap[MapSize] + 1;
pIntValue^ := Integer(#SMap[MapSize]);
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] + Size;
pIntValue := Pointer(Integer(Result) + SIZE_OF_INT);
pIntValue^ := ThreadId;
{$ENDIF}
Result := Pointer(Integer(Result) + GAP_SIZE);
end;
function NGetMem(Size: Integer): Pointer;
var
LPointer: Pointer;
pIntValue: ^Integer;
MapSize: Integer;
ThreadId: Cardinal;
begin
if Size > SIZE_OF_MAP then
MapSize := SIZE_OF_MAP
else
MapSize := Size;
LPointer := SDefaultGetMem(Size + GAP_SIZE);
pIntValue := LPointer;
SMap[MapSize] := SMap[MapSize] + 1;
pIntValue^ := Integer(#SMap[MapSize]);
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] + Size;
pIntValue := Pointer(Integer(LPointer) + SIZE_OF_INT);
pIntValue^ := ThreadId;
{$ENDIF}
Result := Pointer(Integer(LPointer) + GAP_SIZE);
end;
function NFreeMem(P: Pointer): Integer;
var
pIntValue: ^Integer;
LPointer: Pointer;
ThreadId: Cardinal;
LFreed: Boolean;
begin
LPointer := Pointer(Integer(P) - GAP_SIZE);
pIntValue := LPointer;
if IsInMap(pIntValue^) then
begin
{$IFDEF WATCHTHREADS}
ThreadId := Integer(Pointer(Integer(pIntValue) + SIZE_OF_INT)^);
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] - MemorySizeOfPos(pIntValue^);
{$ENDIF}
Integer(Pointer(pIntValue^)^) := Integer(Pointer(pIntValue^)^) - 1;
Result := SDefaultFreeMem(LPointer);
end
else
Result := SDefaultFreeMem(P);
end;
function NReallocMem(P: Pointer; Size: Integer): Pointer;
var
pIntValue: ^Integer;
LPointer: Pointer;
LSizeMap: Integer;
ThreadId: Cardinal;
begin
LPointer := Pointer(Integer(P) - GAP_SIZE);
pIntValue := LPointer;
if not IsInMap(pIntValue^) then
begin
Result := SDefaultReallocMem(P, Size);
Exit;
end;
if Size > SIZE_OF_MAP then
LSizeMap := SIZE_OF_MAP
else
LSizeMap := Size;
//Uma vez com o valor setado, não pode remover o setor
Integer(Pointer(pIntValue^)^) := Integer(Pointer(pIntValue^)^) - 1;
{$IFDEF WATCHTHREADS}
ThreadId := Integer(Pointer(Integer(pIntValue) + SIZE_OF_INT)^);
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] - MemorySizeOfPos(pIntValue^) + Size;
{$ENDIF}
Result := SDefaultReallocMem(LPointer, Size + GAP_SIZE);
SMap[LSizeMap] := SMap[LSizeMap] + 1;
pIntValue := Result;
pIntValue^ := Integer(#SMap[LSizeMap]);
Result := Pointer(Integer(Result) + GAP_SIZE);
end;
procedure TObjectHack.NFreeInstance;
var
ThreadId: Cardinal;
begin
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] - Self.InstanceSize;
{$ENDIF}
CleanupInstance;
SDefaultFreeMem(Self);
end;
class function TObjectHack.NNewInstance: TObject;
var
ThreadId: Cardinal;
begin
Result := InitInstance(SDefaultGetMem(Self.InstanceSize));
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] + Self.InstanceSize;
{$ENDIF}
end;
class function TObjectHack.NNewInstanceTrace: TObject;
var
ThreadId: Cardinal;
begin
try
Result := InitInstance(SDefaultGetMem(Self.InstanceSize));
if (Result.ClassType = TClassVars) or (Result is EExternal) then
Exit;
TObjectHack(Result).IncCounter;
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] + Self.InstanceSize;
{$ENDIF}
except
raise Exception.Create(Result.ClassName);
end;
end;
procedure InitializeArray;
var
I: Integer;
begin
for I := 0 to SIZE_OF_MAP do
SMap[I] := 0;
end;
type
PLocalTest = ^LocalTest;
LocalTest = record
Size: integer;
Size2: string;
end;
procedure ApplyMemoryManager;
var
LMemoryManager: TMemoryManagerEx;
begin
GetMemoryManager(LMemoryManager);
SDefaultGetMem := LMemoryManager.GetMem;
{$IFNDEF TRACEBUFFER}
Exit;
{$ENDIF}
LMemoryManager.GetMem := NGetMem;
SDefaultFreeMem := LMemoryManager.FreeMem;
LMemoryManager.FreeMem := NFreeMem;
SDefaultReallocMem := LMemoryManager.ReallocMem;
LMemoryManager.ReallocMem := NReallocMem;
SDefaultAllocMem := LMemoryManager.AllocMem;
LMemoryManager.AllocMem := NAllocMem;
SetMemoryManager(LMemoryManager);
end;
procedure TestRecord;
var
LTest: PLocalTest;
begin
LTest := AllocMem(1);
Dispose(LTest);
LTest := AllocMem(SIZE_OF_MAP + 1);
Dispose(LTest);
New(LTest);
ReallocMem(LTest, SIZE_OF_MAP +1);
Dispose(LTest);
end;
procedure TesteObject;
var
LTestObject: TObject;
begin
LTestObject := TObject.Create;
LTestObject.Free;
end;
{ TNamedBuffer }
constructor TNamedBufferList.Create;
begin
inherited Create;
Sorted := True;
end;
function GetBufferName(ASize: integer): string;
begin
Result := SNamedBufferList.GetBufferName(ASize);
end;
procedure RegisterNamedBuffer(ABufferName: string; ASize: integer);
begin
SNamedBufferList.RegisterNamedBuffer(ABufferName, ASize);
end;
function TNamedBufferList.GetBufferName(ASize: integer): string;
var
LIndex: Integer;
begin
Result := 'Unknow';
LIndex := IndexOf(ASize);
if LIndex = -1 then
Exit;
Result := TNamedBuffer(Objects[LIndex]).Names;
end;
procedure TNamedBufferList.RegisterNamedBuffer(ABufferName: string; ASize: integer);
var
LIndex: Integer;
LNamedBuffer: TNamedBuffer;
begin
LIndex := IndexOf(ASize);
if LIndex = -1 then
begin
LNamedBuffer := TNamedBuffer.Create;
LNamedBuffer.Names := 'Possible types: ' + ABufferName;
Self.AddObject(ASize, LNamedBuffer);
end
else
TNamedBuffer(Objects[LIndex]).Names := TNamedBuffer(Objects[LIndex]).Names + ' | ' + ABufferName;
end;
procedure InitializeAnalyser;
var
LCan: Boolean;
begin
SIsMemoryWatcherActive := False;
SNamedBufferList := TNamedBufferList.Create;
case GetWinVersion of
wv98, wvXP, wvVista, wv7: LCan := True;
else LCan := False;
end;
if not LCan then
Exit;
{$IFDEF TRACEINSTANCES}
TClassVars.ListClassVars := TList.Create;
{$ENDIF}
{$IFDEF TRACEBUFFER}
InitializeArray;
{$ENDIF}
ApplyMemoryManager;
/// Buffer wrapper
{$IFDEF TRACEBUFFER}
TestRecord;
{$IFNDEF TRACEINSTANCES}
AddressPatch(GetMethodAddress(#OldNewInstance), #TObjectHack.NNewInstance);
{$ENDIF}
{$ENDIF}
///Class wrapper
{$IFDEF TRACEINSTANCES}
AddressPatch(GetMethodAddress(#OldNewInstance), #TObjectHack.NNewInstanceTrace);
TesteObject;
{$ENDIF}
SIsMemoryWatcherActive := True;
end;
{ TThreadDestroy }
initialization
InitializeAnalyser
end.
I was not cleaning the parity byte on FreeMem.
I continue working on this, if someone would like to take a look or help: http://rfrezinos.wordpress.com/delphi-memory-profiler/
Att.
I added a code that was published 3 years later than original plugin, but it still returns error...
Code is straight forward imho ... but still I most likely miss some aspect ...
See this code:
{
nsScreenshot NSIS Plugin
(c) 2003: Leon Zandman (leon#wirwar.com)
Re-compiled by: Linards Liepins (linards.liepins#gmail.com)
Code by: http://www.delphitricks.com/source-code/forms/make_a_desktop_screenshot.html
(e) 2012.
}
library nsScreenshot;
uses
nsis in './nsis.pas',
Windows,
Jpeg,
graphics,
types,
SysUtils;
const
USER32 = 'user32.dll';
type
HWND = type LongWord;
{$EXTERNALSYM HWND}
HDC = type LongWord;
{$EXTERNALSYM HDC}
BOOL = LongBool;
{$EXTERNALSYM BOOL}
{$EXTERNALSYM GetDesktopWindow}
function GetDesktopWindow: HWND; stdcall; external USER32 name 'GetDesktopWindow';
{$EXTERNALSYM GetWindowDC}
function GetWindowDC(hWnd: HWND): HDC; stdcall; external USER32 name 'GetWindowDC';
{$EXTERNALSYM GetWindowRect}
function GetWindowRect(hWnd: HWND; var lpRect: TRect): BOOL; stdcall; external USER32 name 'GetWindowRect';
{$EXTERNALSYM ReleaseDC}
function ReleaseDC(hWnd: HWND; hDC: HDC): Integer; stdcall; external user32 name 'ReleaseDC';
function GetScreenshot(Filename: string; Hwnd: HWND; var Width: integer; var Height: integer): boolean; forward;
function ScreenShot(Bild: TBitMap; hWnd: HWND): boolean; forward;
function Grab_FullScreen(hwndParent: HWND; string_size: integer; variables: PChar; stacktop: pointer):integer; cdecl;
var
buf: array[0..1024] of char;
W,H: integer;
begin
Result := 0;
// set up global variables
Init(hwndParent,string_size,variables,stacktop);
// Get filename to save to
PopString;//(#buf);
// Get a full-screen screenshot
if GetScreenShot(buf,GetDesktopWindow,W,H) then begin
// Everything went just fine...
// Push image dimensions onto stack
PushString(PChar(IntToStr(H)));
PushString(PChar(IntToStr(W)));
// Push result onto stack
PushString(PChar('ok'));
Result := 1;
end else begin
// Something went wrong...
PushString(PChar('error'));
end;
end;
function Grab(hwndParent: HWND; string_size: integer; variables: PChar; stacktop: pointer):integer; cdecl;
var
buf: array[0..1024] of char;
grabWnd: HWND;
Filename: string;
W,H: integer;
begin
Result := 0;
// set up global variables
Init(hwndParent,string_size,variables,stacktop);
try
// Get filename to save to
PopString;//(#buwf);
Filename := buf;
// Get window handle of window to grab
PopString;//(#buf);
grabWnd := StrToInt(buf);
except
PushString(PChar('error'));
exit;
end;
// Get screenshot of parent windows (NSIS)
if GetScreenShot(Filename,grabWnd,W,H) then begin
// Everything went just fine...
// Push image dimensions onto stack
PushString(PChar(IntToStr(H)));
PushString(PChar(IntToStr(W)));
// Push result onto stack
PushString(PChar('ok'));
Result := 1;
end else begin
// Something went wrong...
PushString(PChar('error'));
end;
end;
function GetScreenshot(Filename: string; Hwnd: HWND; var Width: integer; var Height: integer): boolean;
var
bmp: TBitmap;
begin
Result := false;
// Get screenshot
bmp := TBitmap.Create;
try
try
if ScreenShot(bmp,Hwnd) then begin
Width := bmp.Width;
Height := bmp.Height;
bmp.SaveToFile(Filename);
Result := true;
end;
except
// Catch exception and do nothing (function return value remains 'false')
end;
finally
bmp.Free;
end;
end;
function ScreenShot(Bild: TBitMap; hWnd: HWND): boolean;
var
c: TCanvas;
r, t: TRect;
h: THandle;
begin
Result := false;
c := TCanvas.Create;
c.Handle := GetWindowDC(GetDesktopWindow);
h := hWnd;
if h <> 0 then begin
GetWindowRect(h, t);
try
r := Rect(0, 0, t.Right - t.Left, t.Bottom - t.Top);
Bild.Width := t.Right - t.Left;
Bild.Height := t.Bottom - t.Top;
Bild.Canvas.CopyRect(r, c, t);
finally
ReleaseDC(0, c.Handle);
c.Free;
end;
Result := true;
end;
end;
function GetScreenToFile(FileName: string; Quality: Word; Percent: Word): boolean;
var
Bmp: TBitmap;
Jpg: TJpegImage;
begin
Bmp := TBitmap.Create;
Jpg := TJpegImage.Create;
try
Bmp.Width := GetDeviceCaps(GetDc(0), 8) * Percent div 100;
Bmp.Height := GetDeviceCaps(GetDc(0), 10) * Percent div 100;
SetStretchBltMode(Bmp.Canvas.Handle, HALFTONE);
StretchBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, GetDc(0), 0, 0, GetDeviceCaps(GetDc(0), 8), GetDeviceCaps(GetDc(0), 10), SRCCOPY);
Jpg.Assign(Bmp);
Jpg.CompressionQuality := Quality;
Jpg.SaveToFile(FileName);
finally
Jpg.free;
Bmp.free;
end;
end;
function ScreenToFile(hwndParent: HWND; string_size: integer; variables: PChar; stacktop: pointer):integer; cdecl;
var
buf: array[0..1024] of char;
grabWnd: HWND;
Filename: string;
W,H: integer;
begin
Result := 0;
Init(hwndParent,string_size,variables,stacktop);
try
PopString;
Filename := buf;
PopString;
grabWnd := StrToInt(buf);
except
PushString(PChar('error'));
exit;
end;
if GetScreenToFile(Filename,W,H) then
begin
PushString(PChar('ok'));
Result := 1;
end else
begin
PushString(PChar('error'));
end;
end;
//ScreenToFile('SHOT.JPG', 50, 70);
exports Grab_FullScreen,
Grab,
ScreenToFile;
begin
end.
Search for ScreenToFile.
Thanks for any input,. This plugin is vital for installer documentation generation automatization.
1. NSIS plugin core unit problem:
1.1. About the wrong string:
From your own answer post arised that you are using ANSI version of NSIS. Since you have used in your library code compiled in Delphi XE, where the string, Char and PChar are mapped to the Unicode strings, you were passing between NSIS setup application and your library wrong data.
1.2. Another view on core plugin unit:
I've checked your slightly modified plugin core unit NSIS.pas and there are some issues, that prevents your plugin to work properly. However, as I've seen this unit, the first what came to my mind, was to wrap the standalone procedures and functions into a class. And that's what I've done.
1.3. The NSIS.pas v2.0:
Since you've currently used only 3 functions from the original core unit in your code I've simplified the class for only using those (and one extra for message box showing). So here is the code of the modified plugin core unit. I'm not an expert for data manipulations, so maybe the following code can be simplified, but it works at least in Delphi XE2 and Delphi 2009, where I've tested it. Here is the code:
unit NSIS;
interface
uses
Windows, CommCtrl, SysUtils;
type
PParamStack = ^TParamStack;
TParamStack = record
Next: PParamStack;
Value: PAnsiChar;
end;
TNullsoftInstaller = class
private
FParent: HWND;
FParamSize: Integer;
FParameters: PAnsiChar;
FStackTop: ^PParamStack;
public
procedure Initialize(Parent: HWND; ParamSize: Integer; Parameters: PAnsiChar;
StackTop: Pointer);
procedure PushString(const Value: string = '');
function PopString: string;
function MessageDialog(const Text, Caption: string; Buttons: UINT): Integer;
end;
var
NullsoftInstaller: TNullsoftInstaller;
implementation
procedure TNullsoftInstaller.Initialize(Parent: HWND; ParamSize: Integer;
Parameters: PAnsiChar; StackTop: Pointer);
begin
FParent := Parent;
FParamSize := ParamSize;
FParameters := Parameters;
FStackTop := StackTop;
end;
procedure TNullsoftInstaller.PushString(const Value: string = '');
var
CurrParam: PParamStack;
begin
if Assigned(FStackTop) then
begin
CurrParam := PParamStack(GlobalAlloc(GPTR, SizeOf(TParamStack) + FParamSize));
StrLCopy(#CurrParam.Value, PAnsiChar(AnsiString(Value)), FParamSize);
CurrParam.Next := FStackTop^;
FStackTop^ := CurrParam;
end;
end;
function TNullsoftInstaller.PopString: string;
var
CurrParam: PParamStack;
begin
Result := '';
if Assigned(FStackTop) then
begin
CurrParam := FStackTop^;
Result := String(PAnsiChar(#CurrParam.Value));
FStackTop^ := CurrParam.Next;
GlobalFree(HGLOBAL(CurrParam));
end;
end;
function TNullsoftInstaller.MessageDialog(const Text, Caption: string;
Buttons: UINT): Integer;
begin
Result := MessageBox(FParent, PChar(Text), PChar(Caption), Buttons);
end;
initialization
NullsoftInstaller := TNullsoftInstaller.Create;
finalization
if Assigned(NullsoftInstaller) then
NullsoftInstaller.Free;
end.
1.4. Usage of the modified plugin core unit:
As you can see, there's the NullsoftInstaller global variable declared, which allows you to use the class where I've wrapped the functions you've been using before. The usage of the object instance from this variable is simplified with the initialization and finalization sections where this object instance is being created and assigned to this variable when the library is loaded and released when the library is freed.
So the only thing you need to do in your code is to use this NullsoftInstaller global variable like this way:
uses
NSIS;
function ScreenToFile(Parent: HWND; ParamSize: Integer; Parameters: PAnsiChar;
StackTop: Pointer): Integer; cdecl;
var
InputString: string;
begin
Result := 0;
// this is not necessary, if you keep the NullsoftInstaller object instance
// alive (and there's even no reason to free it, since this will be done in
// the finalization section when the library is unloaded), so the following
// statement has no meaning when you won't free the NullsoftInstaller
if not Assigned(NullsoftInstaller) then
NullsoftInstaller := TNullsoftInstaller.Create;
// this has the same meaning as the Init procedure in the original core unit
NullsoftInstaller.Initialize(Parent, ParamSize, Parameters, StackTop);
// this is the same as in the original, except that returns a native string
InputString := NullsoftInstaller.PopString;
NullsoftInstaller.MessageDialog(InputString, 'PopString Result', 0);
// and finally the PushString method, this is also the same as original and
// as well as the PopString supports native string for your Delphi version
NullsoftInstaller.PushString('ok');
end;
2. Screenshot of the Aero composited window
Here is my attempt of screenshot procedure, the TakeScreenshot in code. It takes an extra parameter DropShadow, which should take screenshot including window drop shadow, when the Aero composition is enabled. However I couldn't find a way how to do it in a different way than placing fake window behind the captured one. It has one big weakness; sometimes happens that the fake window isn't fully displayed when the capture is done, so it takes the screenshot of the current desktop around the captured window instead of the white fake window (not yet displayed) behind. So setting the DropShadow to True is now just in experimental stage.
When the DropShadow is False (screenshots without drop shadow) it works properly. My guess is that you were passing wrong parameters due to Unicode Delphi vs. ANSI NSIS problem described above.
library nsScreenshot;
uses
Windows, SysUtils, Types, Graphics, DwmApi, Forms, JPEG, NSIS;
procedure CalcCloseCrop(Bitmap: TBitmap; const BackColor: TColor;
out CropRect: TRect);
var
X: Integer;
Y: Integer;
Color: TColor;
Pixel: PRGBTriple;
RowClean: Boolean;
LastClean: Boolean;
begin
LastClean := False;
CropRect := Rect(Bitmap.Width, Bitmap.Height, 0, 0);
for Y := 0 to Bitmap.Height-1 do
begin
RowClean := True;
Pixel := Bitmap.ScanLine[Y];
for X := 0 to Bitmap.Width - 1 do
begin
Color := RGB(Pixel.rgbtRed, Pixel.rgbtGreen, Pixel.rgbtBlue);
if Color <> BackColor then
begin
RowClean := False;
if X < CropRect.Left then
CropRect.Left := X;
if X + 1 > CropRect.Right then
CropRect.Right := X + 1;
end;
Inc(Pixel);
end;
if not RowClean then
begin
if not LastClean then
begin
LastClean := True;
CropRect.Top := Y;
end;
if Y + 1 > CropRect.Bottom then
CropRect.Bottom := Y + 1;
end;
end;
with CropRect do
begin
if (Right < Left) or (Right = Left) or (Bottom < Top) or
(Bottom = Top) then
begin
if Left = Bitmap.Width then
Left := 0;
if Top = Bitmap.Height then
Top := 0;
if Right = 0 then
Right := Bitmap.Width;
if Bottom = 0 then
Bottom := Bitmap.Height;
end;
end;
end;
procedure TakeScreenshot(WindowHandle: HWND; const FileName: string;
DropShadow: Boolean);
var
R: TRect;
Form: TForm;
Bitmap: TBitmap;
Target: TBitmap;
DeviceContext: HDC;
DesktopHandle: HWND;
ExtendedFrame: Boolean;
const
CAPTUREBLT = $40000000;
begin
ExtendedFrame := False;
if DwmCompositionEnabled then
begin
DwmGetWindowAttribute(WindowHandle, DWMWA_EXTENDED_FRAME_BOUNDS, #R,
SizeOf(TRect));
if DropShadow then
begin
ExtendedFrame := True;
R.Left := R.Left - 30;
R.Top := R.Top - 30;
R.Right := R.Right + 30;
R.Bottom := R.Bottom + 30;
end;
end
else
GetWindowRect(WindowHandle, R);
SetForegroundWindow(WindowHandle);
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf24bit;
Bitmap.SetSize(R.Right - R.Left, R.Bottom - R.Top);
if ExtendedFrame then
begin
DesktopHandle := GetDesktopWindow;
DeviceContext := GetDC(GetDesktopWindow);
Form := TForm.Create(nil);
try
Form.Color := clWhite;
Form.BorderStyle := bsNone;
Form.AlphaBlend := True;
Form.AlphaBlendValue := 0;
ShowWindow(Form.Handle, SW_SHOWNOACTIVATE);
SetWindowPos(Form.Handle, WindowHandle, R.Left, R.Top,
R.Right - R.Left, R.Bottom - R.Top, SWP_NOACTIVATE);
Form.AlphaBlendValue := 255;
BitBlt(Bitmap.Canvas.Handle, 0, 0, R.Right - R.Left, R.Bottom - R.Top,
DeviceContext, R.Left, R.Top, SRCCOPY or CAPTUREBLT);
finally
Form.Free;
ReleaseDC(DesktopHandle, DeviceContext);
end;
Target := TBitmap.Create;
try
CalcCloseCrop(Bitmap, clWhite, R);
Target.SetSize(R.Right - R.Left, R.Bottom - R.Top);
Target.Canvas.CopyRect(Rect(0, 0, R.Right - R.Left, R.Bottom - R.Top),
Bitmap.Canvas, R);
Target.SaveToFile(FileName);
finally
Target.Free;
end;
end
else
begin
DeviceContext := GetWindowDC(WindowHandle);
try
BitBlt(Bitmap.Canvas.Handle, 0, 0, R.Right - R.Left, R.Bottom - R.Top,
DeviceContext, 0, 0, SRCCOPY or CAPTUREBLT);
finally
ReleaseDC(WindowHandle, DeviceContext);
end;
Bitmap.SaveToFile(FileName);
end;
finally
Bitmap.Free;
end;
end;
function ScreenToFile(Parent: HWND; ParamSize: Integer; Params: PAnsiChar;
StackTop: Pointer): Integer; cdecl;
var
I: Integer;
FileName: string;
DropShadow: Boolean;
Parameters: array[0..1] of string;
begin
Result := 0;
if not Assigned(NullsoftInstaller) then
NullsoftInstaller := TNullsoftInstaller.Create;
NullsoftInstaller.Initialize(Parent, ParamSize, Params, StackTop);
for I := 0 to High(Parameters) do
Parameters[I] := NullsoftInstaller.PopString;
FileName := Parameters[1];
if not DirectoryExists(ExtractFilePath(FileName)) or
not TryStrToBool(Parameters[0], DropShadow) then
begin
NullsoftInstaller.PushString('error');
NullsoftInstaller.PushString('Invalid parameters!');
Exit;
end;
try
TakeScreenshot(Parent, FileName, DropShadow);
NullsoftInstaller.PushString('ok');
Result := 1;
except
on E: Exception do
begin
NullsoftInstaller.PushString('error');
NullsoftInstaller.PushString(E.Message);
NullsoftInstaller.MessageDialog(E.Message, 'Error', 0);
end;
end;
end;
exports
ScreenToFile;
begin
end.
After some search I found the following code working from the following SO question:
How to take a screenshot of the Active Window in Delphi?
All other options in the inclusin with NSIS caused crash in BitBtl function, probobly because of Aero and its related DWM fog ...
Also, there is suggestion to use this function. Not jet tested...
http://msdn.microsoft.com/en-us/library/dd162869.aspx
Still, there few problems:
Glass frame is drawn as transparent one
File name from NSIS is converted to somewhat corrupted widestring ...
Files can be drawn just by dialog background color, if you change pages ( using nsdialogs and MUI2 ) ...
GetDesktopWindow should probably be GetDesktopWindow() but often you can (and should) use NULL and not GetDesktopWindow(). Also, one function uses GetDC and the other GetWindowDC...
I asked a question about this some years back when Vista was first released, but never resolved the problem and shelved it as something to consider later.
I have a splash screen that I went to great effort to make look great. It's a 32bpp alpha-blended PNG. I have some code (which I can dig up if required!) that works great under Windows XP or under Vista+ when desktop composition is turned off. However, under Vista+ all the transparent parts are black, destroying everything that looks great about it!
So, my question is this: as anyone been able to display a 32bpp alpha-blended PNG as a splash screen in a way that works both with and without desktop composition activated? I'm not adverse to using third-party components if required, free or otherwise.
Ideally, this would work in Delphi 7.
Update: Besides the answers below, which work very well, I found that the TMS TAdvSmoothSplashScreen component also handles this task very well, if somewhat more complex.
Tim, I just tried this on Vista/D2007 with 'Windows Classic' theme selected:
Alpha Blended Splash Screen in Delphi - Part 2
http://melander.dk/articles/alphasplash2/2/
no black background that I could see... it still looks great.
The article Bob S links to gives the correct answer. Since that article contains quite a bit extra information that you actually need, here is the form/unit I create through it (Note that you'll need the GraphicEx library from here:
unit Splash2Form;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, GraphicEx;
type
TSplash2 = class(TForm)
private
{ Private declarations }
procedure PreMultiplyBitmap(Bitmap: TBitmap);
public
constructor Create(Owner: TComponent);override;
{ Public declarations }
procedure CreateParams(var Params: TCreateParams);override;
procedure Execute;
end;
var
Splash2: TSplash2;
implementation
{$R *.dfm}
{ TSplash2 }
constructor TSplash2.Create(Owner: TComponent);
begin
inherited;
Brush.Style := bsClear;
end;
procedure TSplash2.CreateParams(var Params: TCreateParams);
begin
inherited;
end;
procedure TSplash2.Execute;
var exStyle: DWORD;
BitmapPos: TPoint;
BitmapSize: TSize;
BlendFunction: TBlendFunction;
PNG: TPNGGraphic;
Stream: TResourceStream;
begin
// Enable window layering
exStyle := GetWindowLongA(Handle, GWL_EXSTYLE);
if (exStyle and WS_EX_LAYERED = 0) then
SetWindowLong(Handle, GWL_EXSTYLE, exStyle or WS_EX_LAYERED);
PNG := TPNGGraphic.Create;
try
Stream := TResourceStream.Create(HInstance, 'SPLASH', RT_RCDATA);
try
PNG.LoadFromStream(Stream);
finally
Stream.Free;
end;
PreMultiplyBitmap(PNG);
ClientWidth := PNG.Width;
ClientHeight := PNG.Height;
BitmapPos := Point(0, 0);
BitmapSize.cx := ClientWidth;
BitmapSize.cy := ClientHeight;
// Setup alpha blending parameters
BlendFunction.BlendOp := AC_SRC_OVER;
BlendFunction.BlendFlags := 0;
BlendFunction.SourceConstantAlpha := 255;
BlendFunction.AlphaFormat := AC_SRC_ALPHA;
// ... and action!
UpdateLayeredWindow(Handle, 0, nil, #BitmapSize, PNG.Canvas.Handle,
#BitmapPos, 0, #BlendFunction, ULW_ALPHA);
Show;
finally
PNG.Free;
end;
end;
procedure TSplash2.PreMultiplyBitmap(Bitmap: TBitmap);
var
Row, Col: integer;
p: PRGBQuad;
PreMult: array[byte, byte] of byte;
begin
// precalculate all possible values of a*b
for Row := 0 to 255 do
for Col := Row to 255 do
begin
PreMult[Row, Col] := Row*Col div 255;
if (Row <> Col) then
PreMult[Col, Row] := PreMult[Row, Col]; // a*b = b*a
end;
for Row := 0 to Bitmap.Height-1 do
begin
Col := Bitmap.Width;
p := Bitmap.ScanLine[Row];
while (Col > 0) do
begin
p.rgbBlue := PreMult[p.rgbReserved, p.rgbBlue];
p.rgbGreen := PreMult[p.rgbReserved, p.rgbGreen];
p.rgbRed := PreMult[p.rgbReserved, p.rgbRed];
inc(p);
dec(Col);
end;
end;
end;
end.