Based on my last question, I have a fully functional application that can execute an external process.
But there is a problem. When, for example, Internet Explorer is started, the browser window is not opened maximized.
How to make the browser window (or any other window) start in maximized mode?
Here is my code:
Form:
type
PEnumInfo = ^TEnumInfo;
TEnumInfo = record ProcessID: DWORD; HWND: THandle; end;
type
TForm1 = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
myPID: DWORD = 0;
implementation
uses
UTaskBarList;
{$R *.dfm}
function EnumWindowsProc(Wnd: DWORD; var EI: TEnumInfo): Bool; stdcall;
var
PID: DWORD;
begin
GetWindowThreadProcessID(Wnd, #PID);
Result := (PID <> EI.ProcessID) or (not IsWindowVisible(WND)) or (not IsWindowEnabled(WND));
if not Result then EI.HWND := WND;
end;
function FindMainWindow(PID: DWORD): DWORD;
var
EI: TEnumInfo;
begin
EI.ProcessID := PID;
EI.HWND := 0;
EnumWindows(#EnumWindowsProc, Integer(#EI));
Result := EI.HWND;
end;
procedure dgCreateProcess(const FileName: string);
var ProcInfo: TProcessInformation;
StartInfo: TStartupInfo;
begin
FillMemory(#StartInfo, sizeof(StartInfo), 0);
StartInfo.cb := sizeof(StartInfo);
// StartInfo.dwX := Screen.DesktopRect.BottomRight.X;
// StartInfo.dwY := Screen.DesktopRect.BottomRight.Y;
CreateProcess(
PChar(FileName),
nil,
nil, Nil, False,
NORMAL_PRIORITY_CLASS,
nil, nil,
StartInfo,
ProcInfo);
myPID := ProcInfo.dwProcessId;
CloseHandle(ProcInfo.hProcess);
CloseHandle(ProcInfo.hThread);
end;
procedure TForm1.btn1Click(Sender: TObject);
var
hWindow : DWORD;
szRect : TRect;
posX, posY, windW, windH: Integer;
begin
dgCreateProcess('C:\Program Files\Internet Explorer\iexplore.exe');
repeat
hWindow := FindMainWindow(myPID);//FindWindow('IEFrame', nil);
if hWindow > 0 then
begin
GetWindowRect(hWindow,szRect);
windW := szRect.Width;
windH := szRect.Height;
posX := Screen.DesktopRect.BottomRight.X;
posY := Screen.DesktopRect.BottomRight.Y;
MoveWindow(hWindow, posX, posY, windW, windH,True);
TTaskbarList.Remove(hWindow);
end;
until (IsWindowVisible(hWindow));
ShowMessage('outside of loop');
end;
end.
UTaskBarList:
unit UTaskBarList;
interface
uses ComObj, ShlObj;
type
ITaskbarList = interface
[SID_ITaskbarList]
function HrInit: HResult; stdcall;
function AddTab(hwnd: Cardinal): HResult; stdcall;
function DeleteTab(hwnd: Cardinal): HResult; stdcall;
function ActivateTab(hwnd: Cardinal): HResult; stdcall;
function SetActiveAlt(hwnd: Cardinal): HResult; stdcall;
end;
TTaskbarList = class
private
xTaskbarList: ITaskbarList;
public
constructor Create;
procedure Activate(hwnd: THandle);
procedure Add(hwnd: THandle);
procedure Delete(hwnd: THandle);
class procedure Insert(hwnd: THandle);
class procedure Remove(hwnd: THandle);
end;
implementation
constructor TTaskbarList.Create;
begin
inherited Create;
xTaskbarList := CreateComObject(CLSID_TaskbarList) as ITaskbarList;
xTaskbarList.HrInit;
end;
procedure TTaskbarList.Activate(hwnd: THandle);
begin
xTaskbarList.ActivateTab(hwnd);
end;
procedure TTaskbarList.Add(hwnd: THandle);
begin
xTaskbarList.AddTab(hwnd);
end;
procedure TTaskbarList.Delete(hwnd: THandle);
begin
xTaskbarList.DeleteTab(hwnd);
end;
class procedure TTaskbarList.Insert(hwnd: THandle);
begin
with TTaskbarList.Create do
begin
Add(hwnd);
Free;
end;
end;
class procedure TTaskbarList.Remove(hwnd: THandle);
begin
with TTaskbarList.Create do
begin
Delete(hwnd);
Free;
end;
end;
end.
The usual way to maximize a window in Windows is to call ShowWindow from Win32 API, passing the handle to the window and SW_MAXIMIZE as arguments, after the process has been started.
But when using CreateProcess to start a new process, you can instruct it to call ShowWindow for you, by setting the wShowWindow field of TStartupInfo to SW_MAXIMIZE.
The value you set in wShowWindow is only taken in account if you also set the STARTF_USESHOWWINDOW flag in the dwFlags field. The dwFlags bitfield determines whether certain members of the TStartupInfo record are used when the process creates a window.
Actually, ShowWindow is called automatically when a GUI process is started. By setting the wShowWindow field of TStartupInfo you are just telling it which value to use as argument for nCmdShow parameter on the first call to ShowWindow.
Add the following lines after StartInfo.cb := sizeof(StartInfo); in your code:
StartInfo.dwFlags := STARTF_USESHOWWINDOW;
StartInfo.wShowWindow := SW_MAXIMIZE;
All this is explained in documentation of STARTUPINFO structure:
wShowWindow
If dwFlags specifies STARTF_USESHOWWINDOW, this member can be any of the values that can be specified in the nCmdShow parameter for the
ShowWindow function, except for SW_SHOWDEFAULT. Otherwise, this member
is ignored.
For GUI processes, the first time ShowWindow is called, its nCmdShow parameter is ignored wShowWindow specifies the default value.
In subsequent calls to ShowWindow, the wShowWindow member is used if
the nCmdShow parameter of ShowWindow is set to SW_SHOWDEFAULT.
Unfortunately this does not work universally for all applications. You have to test it individually with every process that you intend to start with CreateProcess. For some applications setting SW_MAXIMIZED on the first call to ShowWindow might not be enough.
Related
Trying to setup a SetWindowsHookEx(WH_KEYBOARD) from a console app. I'm doing this inside thread, because I tried to to use the TThread.WaitFor method to keep the application openned, while the thread is running.
Important code parts:
type
THookKeyboard = procedure; stdcall;
KeyloggerThread = class(TThread)
private
const
MESSAGE_CODE = WM_USER + $1000;
var
HookOn, HookOff: THookKeyboard;
MsgReceptor: ^Integer;
MemFile: THandle;
function InstallKeyLogger(const TempDir: String): bool;
procedure HookMessage(var MessageHandler: TMessage); message MESSAGE_CODE;
protected
constructor Create;
procedure Execute; override;
end;
var
KeylogThreadCtrl: KeyloggerThread;
function KeyloggerThread.InstallKeyLogger(const TempDir: String): bool;
var
DLLHandle: THandle;
begin
Result:= false;
if FileExists(TempDir + DLLName) = true then
begin
DLLHandle:= LoadLibrary(PChar(TempDir + DLLName));
if DLLHandle <> 0 then
begin
#HookOn:= GetProcAddress(DLLHandle, 'HookOn');
#HookOff:= GetProcAddress(DLLHandle, 'HookOff');
end;
if assigned(HookOn) and assigned(HookOff) then
begin
MemFile:= CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0,SizeOf(Integer), 'Win32KLCom');
if MemFile <> 0 then
begin
MessageBox(0, 'starting keylogger', 'hook', MB_OK);
MsgReceptor:= MapViewOfFile(MemFile,FILE_MAP_WRITE,0,0,0);
HookOn;
Result:= true;
end;
end;
end;
end;
procedure KeyloggerThread.HookMessage(var MessageHandler: TMessage);
begin
MessageBox(0, 'pressed something!', 'hook', MB_OK);
end;
constructor KeyloggerThread.Create;
begin
inherited Create(false);
end;
procedure KeyloggerThread.Execute;
begin
while not Terminated do
begin
if not assigned(HookOn) then
if InstallKeyLogger(ExtractFilePath(ParamStr(0))) = false then
Terminate;
end;
end;
begin
if ParamStr(1) = '-runkeylog' then
begin
MessageBox(0, 'going to install keylogger', 'hook', MB_OK);
KeylogThreadCtrl:= KeyloggerThread.Create;
KeylogThreadCtrl.WaitFor;
end
end;
I know the InstallKeyLogger function is going fine, because I get the messagebox 'starting keylogger'.
Once I press any key, windows start freezing and I need to finish the application. The DLL code is:
library KeyboardDLL;
uses
Windows,
Messages;
{$R *.res}
const
MESSAGE_CODE = WM_USER + $1000;
var
KeyboardHook: HHook;
MemFile: THandle;
MsgReceptor: ^Integer;
function HookCallBack( Code : Integer;
wParam : WPARAM;
lParam : LPARAM
) : LRESULT; stdcall;
begin
if code=HC_ACTION then
begin
MemFile:= OpenFileMapping(FILE_MAP_WRITE,False, 'Win32KLCom');
if MemFile<>0 then
begin
MsgReceptor:= MapViewOfFile(MemFile,FILE_MAP_WRITE,0,0,0);
PostMessage(MsgReceptor^,MESSAGE_CODE,wParam,lParam);
end;
end;
Result:= CallNextHookEx(KeyboardHook, Code, wParam, lParam)
end;
procedure HookOn; stdcall;
begin
KeyboardHook:= SetWindowsHookEx(WH_KEYBOARD, #HookCallBack, HInstance , 0);
end;
procedure HookOff; stdcall;
begin
UnmapViewOfFile(MsgReceptor);
CloseHandle(MemFile);
UnhookWindowsHookEx(KeyboardHook);
end;
exports
HookOn,
HookOff;
begin
end.
It looks like you ported your hosting code from a VCL application, because you have some assumptions that don't hold for stand-alone threads, like the one you have there:
procedure HookMessage(var MessageHandler: TMessage); message MESSAGE_CODE;
Message procedures like this one only work in the context of a VCL form or control.
You can only post messages (use PostMessage) to window handles, not memory mapped files (as you attempt with the MsgReceptor pointer).
If you want your thread to be able to process messages, you must create a window handle and the thread must have a message loop (GetMessage/DispatchMessage, or similar).
In Delphi XE can I allow my form to accept file 'drag and drop' but without having to handle bare windows messages?
You don't need to handle messages to implement this. You just need to implement IDropTarget and call RegisterDragDrop/RevokeDragDrop. It's really very very simple. You can actually implement IDropTarget in your form code but I prefer to do it in a helper class that looks like this:
uses
Winapi.Windows,
Winapi.ActiveX,
Winapi.ShellAPI,
System.StrUtils,
Vcl.Forms;
type
IDragDrop = interface
function DropAllowed(const FileNames: array of string): Boolean;
procedure Drop(const FileNames: array of string);
end;
TDropTarget = class(TObject, IInterface, IDropTarget)
private
// IInterface
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
private
// IDropTarget
FHandle: HWND;
FDragDrop: IDragDrop;
FDropAllowed: Boolean;
procedure GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
procedure SetEffect(var dwEffect: Integer);
function DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; stdcall;
function DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
function DragLeave: HResult; stdcall;
function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
public
constructor Create(AHandle: HWND; const ADragDrop: IDragDrop);
destructor Destroy; override;
end;
{ TDropTarget }
constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
begin
inherited Create;
FHandle := AHandle;
FDragDrop := ADragDrop;
RegisterDragDrop(FHandle, Self)
end;
destructor TDropTarget.Destroy;
begin
RevokeDragDrop(FHandle);
inherited;
end;
function TDropTarget.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then begin
Result := S_OK;
end else begin
Result := E_NOINTERFACE;
end;
end;
function TDropTarget._AddRef: Integer;
begin
Result := -1;
end;
function TDropTarget._Release: Integer;
begin
Result := -1;
end;
procedure TDropTarget.GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
var
i: Integer;
formatetcIn: TFormatEtc;
medium: TStgMedium;
dropHandle: HDROP;
begin
FileNames := nil;
formatetcIn.cfFormat := CF_HDROP;
formatetcIn.ptd := nil;
formatetcIn.dwAspect := DVASPECT_CONTENT;
formatetcIn.lindex := -1;
formatetcIn.tymed := TYMED_HGLOBAL;
if dataObj.GetData(formatetcIn, medium)=S_OK then begin
(* This cast needed because HDROP is incorrectly declared as Longint in ShellAPI.pas. It should be declared as THandle
which is an unsigned integer. Without this fix the routine fails in top-down memory allocation scenarios. *)
dropHandle := HDROP(medium.hGlobal);
SetLength(FileNames, DragQueryFile(dropHandle, $FFFFFFFF, nil, 0));
for i := 0 to high(FileNames) do begin
SetLength(FileNames[i], DragQueryFile(dropHandle, i, nil, 0));
DragQueryFile(dropHandle, i, #FileNames[i][1], Length(FileNames[i])+1);
end;
end;
end;
procedure TDropTarget.SetEffect(var dwEffect: Integer);
begin
if FDropAllowed then begin
dwEffect := DROPEFFECT_COPY;
end else begin
dwEffect := DROPEFFECT_NONE;
end;
end;
function TDropTarget.DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
var
FileNames: TArray<string>;
begin
Result := S_OK;
Try
GetFileNames(dataObj, FileNames);
FDropAllowed := (Length(FileNames)>0) and FDragDrop.DropAllowed(FileNames);
SetEffect(dwEffect);
Except
Result := E_UNEXPECTED;
End;
end;
function TDropTarget.DragLeave: HResult;
begin
Result := S_OK;
end;
function TDropTarget.DragOver(grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
Result := S_OK;
Try
SetEffect(dwEffect);
Except
Result := E_UNEXPECTED;
End;
end;
function TDropTarget.Drop(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
var
FileNames: TArray<string>;
begin
Result := S_OK;
Try
GetFileNames(dataObj, FileNames);
if Length(FileNames)>0 then begin
FDragDrop.Drop(FileNames);
end;
Except
Application.HandleException(Self);
End;
end;
The idea here is to wrap up the complexity of the Windows IDropTarget in TDropTarget. All you need to do is to implement IDragDrop which is much simpler. Anyway, I think this should get you going.
Create the drop target object from your control's CreateWnd. Destroy it in the DestroyWnd method. That point is important because VCL window re-creation means that a control can have its window handle destroyed and re-created during its lifetime.
Note that reference counting on TDropTarget is suppressed. That is because when RegisterDragDrop is called it increments the reference count. This creates a circular reference and this code to suppress reference counting breaks that. This means that you would use this class through a class variable rather than an interface variable, in order to avoid leaking.
The usage would look something like this:
type
TMainForm = class(TForm, IDragDrop)
....
private
FDropTarget: TDropTarget;
// implement IDragDrop
function DropAllowed(const FileNames: array of string): Boolean;
procedure Drop(const FileNames: array of string);
protected
procedure CreateWindowHandle; override;
procedure DestroyWindowHandle; override;
end;
....
procedure TMainForm.CreateWindowHandle;
begin
inherited;
FDropTarget := TDropTarget.Create(WindowHandle, Self);
end;
procedure TMainForm.DestroyWindowHandle;
begin
FreeAndNil(FDropTarget);
inherited;
end;
function TMainForm.DropAllowed(const FileNames: array of string): Boolean;
begin
Result := True;
end;
procedure TMainForm.Drop(const FileNames: array of string);
begin
; // do something with the file names
end;
Here I am using a form as the drop target. But you could use any other windowed control in a similar fashion.
If you don't like pure WinAPI, then you can use components. Drag and Drop Component Suite is free with sources.
No, unless you are about to peruse some custom TForm descendant which have this functionality built-in already.
I used David Heffernan's solution as base for my test application and got 'Invalid pointer operation' on application close.
The solution for that problem was to change the TDropTarget.Create by adding '_Release;'
constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
begin
inherited Create;
FHandle := AHandle;
FDragDrop := ADragDrop;
RegisterDragDrop(FHandle, Self);
_Release;
end;
A discussion about this problem you can see on Embarcadero forum.
You have to either write code yourself, or install a 3rd party product like DropMaster, which lets you do drag and drop in much older Delphi versions as well.
--jeroen
I have the following procedure:
procedure MyMainThread.MapProc;
var
Handle: THandle;
PID: dword;
Struct: TProcessEntry32;
Processes: TStringList;
begin
Handle:= CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Struct.dwSize:=Sizeof(TProcessEntry32);
Process32First(Handle, Struct);
Processes:= TStringList.Create;
repeat
Processes.Add(Struct.szExeFile);
Processes.SaveToFile('C:\Log.txt');
PID:= Struct.th32ProcessID;
PIDHandle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or
PROCESS_VM_READ, false, PID);
CloseHandle(PIDHandle);
until (not Process32Next(Handle,Struct));
Processes.Free;
end;
As you can see, I save the running processes inside C:\Log.txt, and this works nice when inside an .exe file. Now I'm trying to implement this inside a .DLL file, and the concept is: The DLL will be loaded, and it will have an EntryPoint calling a Thread.Create... This Thread will use a SetTimer to run the procedure MapProc every 10 seconds to save the running processes in C:\Log.txt. The code is:
library Project1;
uses
Windows,
SysUtils,
Classes,
Registry,
EncdDecd,
TLHelp32,
IdHTTP;
{$R *.res}
type
MyMainThread = Class(TThread)
var
DestDir, ContactHost: String;
Sent: TStringList;
PIDHandle: THandle; //need to be public because we use in MapProc / CatchYa
private
procedure MapProc;
procedure MapMemory(ProcessName: string);
procedure CreateMessagePump;
protected
constructor Create;
procedure Execute; override;
end;
constructor MyMainThread.Create;
begin
inherited Create(false);
FreeOnTerminate:= true;
Priority:= tpNormal;
end;
procedure MyMainThread.Execute;
begin
while not Terminated do
begin
SetTimer(0, 0, 10000, #MyMainThread.MapProc); //setting timer 10 seconds calling MapProc
CreateMessagePump; //we are inside DLL so I think we need Message Pump to timer work
Terminate;
end;
end;
procedure MyMainThread.MapProc;
var
Handle: THandle;
PID: dword;
Struct: TProcessEntry32;
Processes: TStringList;
begin
Handle:= CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Struct.dwSize:=Sizeof(TProcessEntry32);
Process32First(Handle, Struct);
Processes:= TStringList.Create;
repeat
Processes.Add(Struct.szExeFile);
Processes.SaveToFile('C:\Log.txt');
PID:= Struct.th32ProcessID;
PIDHandle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or
PROCESS_VM_READ, false, PID);
if POS(Struct.szExeFile, ExeName) = 0 then
MapMemory(Struct.szExeFile); //procedure called for verification purposes, but it's not even getting called
CloseHandle(PIDHandle);
until (not Process32Next(Handle,Struct));
Processes.Free;
end;
procedure MyMainThread.CreateMessagePump;
var
AppMsg: TMsg;
begin
while GetMessage(AppMsg, 0, 0, 0) do
begin
TranslateMessage(AppMsg);
DispatchMessage(AppMsg);
end;
//if needed to quit this procedure use PostQuitMessage(0);
end;
procedure EntryPoint(Reason: integer);
begin
if Reason = DLL_PROCESS_ATTACH then
begin
MyMainThread.Create;
end
else
if Reason = DLL_PROCESS_DETACH then
begin
MessageBox(0, 'DLL De-Injected', 'DLL De-Injected', 0);
end;
end;
begin
DLLProc:= #EntryPoint;
EntryPoint(DLL_PROCESS_ATTACH);
end.
But when running this, I get in the Log.txt file only the line: [System Process]
The exe hosting DLL is:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
HD: THandle;
begin
HD:= LoadLibrary('C:\Project1.dll');
end;
end.
The reason that your code fails is that you're not using a proper callback for the SetTimer function. As per the documentation that should have a signature like
procedure (hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD); stdcall;
Your incompatible callback - which is a class method - causes the code to think the Self lives at a completely arbitrary memory address, as class methods has an implicit Self parameter but winapi has no knowledge of that. Now when the code tries to write to an invalid address - 'PIDHandle', assuming there should be a class field, an AV is raised and since the exception is not handled the rest of the code is not executed - also as explained in David's answer.
Your solution is to use a proper callback. To access class members you can use a global variable. Not using a global variable would require some hacky code (google for MethodToProcedure f.i.)
A sample could be like:
threadvar
MyThread: MyMainThread;
procedure TimerProc(hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD);
stdcall;
var
Handle: THandle;
PID: dword;
Struct: TProcessEntry32;
Processes: TStringList;
begin
Handle:= CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Struct.dwSize:=Sizeof(TProcessEntry32);
Process32First(Handle, Struct);
Processes:= TStringList.Create;
repeat
Processes.Add(Struct.szExeFile);
Processes.SaveToFile('C:\Temp\Log3.txt');
PID:= Struct.th32ProcessID;
MyThread.PIDHandle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or
PROCESS_VM_READ, false, PID);
if POS(Struct.szExeFile, ExeName) = 0 then
MyThread.MapMemory(Struct.szExeFile);
CloseHandle(MyThread.PIDHandle);
until (not Process32Next(Handle,Struct));
Processes.Free;
end;
procedure MyMainThread.Execute;
begin
while not Terminated do
begin
MyThread := Self;
SetTimer(0, 0, 10000, #TimerProc);
CreateMessagePump;
Terminate;
end;
end;
To take David's advice, not to get beaten by the '#' operator, we should first redeclare the SetTimer function to use the callback correctly. That would look something like:
threadvar
MyThread: MyMainThread;
procedure TimerProc(hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD);
stdcall;
var
..
begin
..
end;
type
TFnTimerProc = procedure (hwnd: HWND; uMsg: UINT; idEvent: UIntPtr;
dwTime: DWORD); stdcall;
function SetTimer(hWnd: HWND; nIDEvent: UIntPtr; uElapse: UINT;
lpTimerFunc: TFNTimerProc): UINT; stdcall; external user32;
procedure MyMainThread.Execute;
begin
MyThread := Self;
SetTimer(0, 0, 10000, TimerProc);
CreateMessagePump;
end;
Here's a version that works as you'd expect. This proves that process enumeration using toolhelp32 works perfectly well from a DLL.
Library
library ProcessEnumLib;
uses
SysUtils, Classes, Windows, TlHelp32;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
procedure TMyThread.Execute;
var
Handle: THandle;
PID: dword;
ProcessEntry: TProcessEntry32;
Processes: TStringList;
begin
Handle := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Win32Check(Handle<>0);
try
ProcessEntry.dwSize := Sizeof(TProcessEntry32);
Win32Check(Process32First(Handle, ProcessEntry));
Processes := TStringList.Create;
try
repeat
Processes.Add(ProcessEntry.szExeFile);
until not Process32Next(Handle, ProcessEntry);
Processes.SaveToFile('C:\Desktop\Log.txt');
finally
Processes.Free;
end;
finally
CloseHandle(Handle);
end;
end;
begin
TMyThread.Create;
end.
Host
program ProcessEnumHost;
{$APPTYPE CONSOLE}
uses
Windows;
begin
LoadLibrary('ProcessEnumLib.dll');
Sleep(1000);
end.
Your version is failing because the call to OpenProcess is raising an access violation which is killing the thread. Right now, I'm not sure why that is so.
I suggest that you simplify grossly. You don't need a message loop, and you don't need a timer. You can use Sleep in your thread to pause between process maps. Something like this:
library ProcessEnumLib;
uses
SysUtils, Classes, Windows, TlHelp32;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
procedure TMyThread.Execute;
var
Handle, ProcessHandle: THandle;
ProcessEntry: TProcessEntry32;
Processes: TStringList;
begin
while True do
begin
Handle := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Win32Check(Handle<>0);
try
ProcessEntry.dwSize := Sizeof(TProcessEntry32);
Win32Check(Process32First(Handle, ProcessEntry));
Processes := TStringList.Create;
try
repeat
Processes.Add(ProcessEntry.szExeFile);
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or PROCESS_VM_READ, false, ProcessEntry.th32ProcessID);
CloseHandle(ProcessHandle);
until not Process32Next(Handle, ProcessEntry);
Processes.SaveToFile('C:\Desktop\Log.txt');
finally
Processes.Free;
end;
finally
CloseHandle(Handle);
end;
Sleep(10000);//10s sleep
end;
end;
begin
TMyThread.Create;
end.
I've no idea why, but this variant avoids the AV when calling OpenProcess. I'd love to know why. But it's the right way for you to do what you want, and it side-steps the problem.
This article
http://msdn.microsoft.com/en-gb/library/bb776867.aspx
describes preview handlers in Windows as
Preview handlers are called when an
item is selected to show a
lightweight, rich, read-only preview
of the file's contents in the view's
reading pane. This is done without
launching the file's associated
application.
and ...
A preview handler is a hosted
application. Hosts include the
Microsoft Windows Explorer in Windows
Vista or Microsoft Outlook 2007.
Is there some Delphi VCL code which can be used as a startingpoint for such a handler?
#Mjn, right know I'm writing an article for my blog to implement Preview Handlers from Delphi, but due to lack of time, I do not know when this is complete, as others users mention by the moment no exist a VCL component in Delphi to implement preview handlers, in the past I implemented a couple of preview handlers for a customer but using Delphi-Prism and C#.
As starting point here I leave some tips.
You must use the IPreviewHandler, InitializeWithFile, InitializeWithStream, IPreviewHandlerFrame, IPreviewHandlerVisuals interfaces.
This is the Delphi translation of the headers of these interfaces
uses
Windows, ActiveX, AxCtrls, ShlObj, ComObj;
type
IIPreviewHandler = interface(IUnknown)
['{8895b1c6-b41f-4c1c-a562-0d564250836f}']
function SetWindow(hwnd: HWND; var RectangleRef: TRect): HRESULT; stdcall;
function SetRect(var RectangleRef: TRect): HRESULT; stdcall;
function DoPreview(): HRESULT; stdcall;
function Unload(): HRESULT; stdcall;
function SetFocus(): HRESULT; stdcall;
function QueryFocus(phwnd: HWND): HRESULT; stdcall;
function TranslateAccelerator(PointerToWindowMessage: MSG): HRESULT; stdcall;
end;
IInitializeWithFile = interface(IUnknown)
['{b7d14566-0509-4cce-a71f-0a554233bd9b}']
function Initialize(pszFilePath: LPWSTR; grfMode: DWORD):HRESULT;stdcall;
end;
IInitializeWithStream = interface(IUnknown)
['{b824b49d-22ac-4161-ac8a-9916e8fa3f7f}']
function Initialize(pstream: IStream; grfMode: DWORD): HRESULT; stdcall;
end;
IIPreviewHandlerFrame = interface(IUnknown)
['{fec87aaf-35f9-447a-adb7-20234491401a}']
function GetWindowContext(pinfo: HWND): HRESULT; stdcall;
function TranslateAccelerator(PointerToWindowMessage: MSG): HRESULT; stdcall;
end;
IIPreviewHandlerVisuals = interface(IUnknown)
['{8327b13c-b63f-4b24-9b8a-d010dcc3f599}']
function SetBackgroundColor(color: COLORREF ): HRESULT; stdcall;
function SetFont(plf:LOGFONTW): HRESULT; stdcall;
function SetTextColor(color: COLORREF): HRESULT; stdcall;
end;
You must create a com dll with a class which descend from these interfaces IIPreviewHandler, IIPreviewHandlerVisuals, IOleWindow, IObjectWithSite to manage the visualization and a second class to load the files to show. this class must descend from IPreviewHandler, IInitializeWithStream.
something like this
TMyPreviewHandler = class(IIPreviewHandler, IIPreviewHandlerVisuals, IOleWindow, IObjectWithSite)
TMyStream = class(IIPreviewHandler, IInitializeWithStream, IStream)
Now you must create your own implementation of the methods for the parent interfaces.
this is the list of the methods which you need implement.
IPreviewHandler -> DoPreview, SetWindow, SetRect, Unload, SetFocus, TranslateAccelerator, QueryFocus.
IObjectWithSite -> GetSite, SetSite.
IOleWindow -> GetWindow
IPreviewHandlerVisuals - > SetBackgroundColor, SetFont, SetColor
InitializeWithStream -> Initialize
finally you must register your COM in the system as well as the file extensions which will use you PrevieHandler class.
Check this project as a starting point Windows Preview Handler Pack (is written in C#) and this article View Data Your Way With Our Managed Preview Handler Framework
I have made this unit to handle all the preview handler stuff:
unit PreviewHandler;
{$WARN SYMBOL_PLATFORM OFF}
{.$DEFINE USE_CODESITE}
interface
uses
Classes, Controls, ComObj;
type
TPreviewHandler = class abstract
public
{ Create all controls needed for the preview and connect them to the
parent given. The parent follows the size, color and font of the preview
pane. The parent will stay valid until this instance is destroyed, so if
you make the parent also the owner of the controls you don't need to free
them in Destroy. }
constructor Create(AParent: TWinControl); virtual;
class function GetComClass: TComClass; virtual; abstract;
class procedure Register(const AClassID: TGUID; const AName, ADescription, AFileExtension: string);
{$REGION 'Clear Content'}
/// <summary>Clear Content</summary>
/// <remarks>This method is called when the preview should be cleared because
/// either another item was selected or the PreviewHandler will be
/// closed.</remarks>
{$ENDREGION}
procedure Unload; virtual;
end;
TStreamPreviewHandler = class abstract(TPreviewHandler)
public
{$REGION 'Render the preview from the stream data'}
/// <summary>Render the preview from the stream data</summary>
/// <remarks>Here you should render the data from the stream in whatever
/// fashion you want.</remarks>
{$ENDREGION}
procedure DoPreview(Stream: TStream); virtual; abstract;
class function GetComClass: TComClass; override; final;
end;
TFilePreviewHandler = class abstract(TPreviewHandler)
public
{$REGION 'Render the preview from the file path'}
/// <summary>Render the preview from the file path</summary>
/// <remarks>Here you should render the data from the file path in whatever
/// fashion you want.</remarks>
{$ENDREGION}
procedure DoPreview(const FilePath: String); virtual; abstract;
class function GetComClass: TComClass; override; final;
end;
implementation
uses
{$IFDEF USE_CODESITE}
CodeSiteLogging,
{$ENDIF}
Windows, ActiveX, ComServ, ShlObj, PropSys, Types, SysUtils, Graphics, ExtCtrls;
type
TPreviewHandlerClass = class of TPreviewHandler;
TComPreviewHandler = class(TComObject, IPreviewHandler, IPreviewHandlerVisuals, IObjectWithSite, IOleWindow)
strict private
function IPreviewHandler.DoPreview = IPreviewHandler_DoPreview;
function ContextSensitiveHelp(fEnterMode: LongBool): HRESULT; stdcall;
function GetSite(const riid: TGUID; out site: IInterface): HRESULT; stdcall;
function GetWindow(out wnd: HWND): HRESULT; stdcall;
function IPreviewHandler_DoPreview: HRESULT; stdcall;
function QueryFocus(var phwnd: HWND): HRESULT; stdcall;
function SetBackgroundColor(color: Cardinal): HRESULT; stdcall;
function SetFocus: HRESULT; stdcall;
function SetFont(const plf: tagLOGFONTW): HRESULT; stdcall;
function SetRect(var prc: TRect): HRESULT; stdcall;
function SetSite(const pUnkSite: IInterface): HRESULT; stdcall;
function SetTextColor(color: Cardinal): HRESULT; stdcall;
function SetWindow(hwnd: HWND; var prc: TRect): HRESULT; stdcall;
function TranslateAccelerator(var pmsg: tagMSG): HRESULT; stdcall;
function Unload: HRESULT; stdcall;
private
FBackgroundColor: Cardinal;
FBounds: TRect;
FContainer: TWinControl;
FLogFont: tagLOGFONTW;
FParentWindow: HWND;
FPreviewHandler: TPreviewHandler;
FPreviewHandlerClass: TPreviewHandlerClass;
FPreviewHandlerFrame: IPreviewHandlerFrame;
FSite: IInterface;
FTextColor: Cardinal;
protected
procedure CheckContainer;
procedure CheckPreviewHandler;
procedure InternalUnload; virtual; abstract;
procedure InternalDoPreview; virtual; abstract;
property Container: TWinControl read FContainer;
property PreviewHandler: TPreviewHandler read FPreviewHandler;
public
destructor Destroy; override;
property PreviewHandlerClass: TPreviewHandlerClass read FPreviewHandlerClass write FPreviewHandlerClass;
end;
TComStreamPreviewHandler = class(TComPreviewHandler, IInitializeWithStream)
strict private
function IInitializeWithStream.Initialize = IInitializeWithStream_Initialize;
function IInitializeWithStream_Initialize(const pstream: IStream; grfMode: Cardinal): HRESULT; stdcall;
private
FIStream: IStream;
FMode: Cardinal;
function GetPreviewHandler: TStreamPreviewHandler;
protected
procedure InternalUnload; override;
procedure InternalDoPreview; override;
property PreviewHandler: TStreamPreviewHandler read GetPreviewHandler;
end;
TComFilePreviewHandler = class(TComPreviewHandler, IInitializeWithFile)
strict private
function IInitializeWithFile.Initialize = IInitializeWithFile_Initialize;
function IInitializeWithFile_Initialize(pszFilePath: LPCWSTR; grfMode: DWORD): HRESULT; stdcall;
private
FFilePath: string;
FMode: DWORD;
function GetPreviewHandler: TFilePreviewHandler;
protected
procedure InternalDoPreview; override;
procedure InternalUnload; override;
property PreviewHandler: TFilePreviewHandler read GetPreviewHandler;
end;
TComPreviewHandlerFactory = class(TComObjectFactory)
private
FFileExtension: string;
FPreviewHandlerClass: TPreviewHandlerClass;
class procedure DeleteRegValue(const Key, ValueName: string; RootKey: DWord);
class function IsRunningOnWOW64: Boolean;
protected
property FileExtension: string read FFileExtension;
public
constructor Create(APreviewHandlerClass: TPreviewHandlerClass; const AClassID: TGUID; const AName, ADescription, AFileExtension: string);
function CreateComObject(const Controller: IUnknown): TComObject; override;
procedure UpdateRegistry(Register: Boolean); override;
property PreviewHandlerClass: TPreviewHandlerClass read FPreviewHandlerClass;
end;
TWinControlHelper = class helper for TWinControl
public
procedure SetFocusTabFirst;
procedure SetFocusTabLast;
procedure SetBackgroundColor(AColor: Cardinal);
procedure SetBoundsRect(const ARect: TRect);
procedure SetTextColor(AColor: Cardinal);
procedure SetTextFont(const Source: tagLOGFONTW);
end;
TIStreamAdapter = class(TStream)
private
FTarget: IStream;
protected
function GetSize: Int64; override;
procedure SetSize(NewSize: Longint); override;
procedure SetSize(const NewSize: Int64); override;
public
constructor Create(ATarget: IStream);
function Read(var Buffer; Count: Longint): Longint; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override;
function Write(const Buffer; Count: Longint): Longint; override;
property Target: IStream read FTarget;
end;
procedure TWinControlHelper.SetFocusTabFirst;
begin
SelectNext(nil, true, true);
end;
procedure TWinControlHelper.SetFocusTabLast;
begin
SelectNext(nil, false, true);
end;
procedure TWinControlHelper.SetBackgroundColor(AColor: Cardinal);
begin
Color := AColor;
end;
procedure TWinControlHelper.SetBoundsRect(const ARect: TRect);
begin
SetBounds(ARect.Left, ARect.Top, ARect.Right - ARect.Left, ARect.Bottom - ARect.Top);
end;
procedure TWinControlHelper.SetTextColor(AColor: Cardinal);
begin
Font.Color := AColor;
end;
procedure TWinControlHelper.SetTextFont(const Source: tagLOGFONTW);
var
fontStyle: TFontStyles;
begin
Font.Height := Source.lfHeight;
fontStyle := Font.Style;
if Source.lfWeight >= FW_BOLD then
Include(fontStyle, fsBold);
if Source.lfItalic = 1 then
Include(fontStyle, fsItalic);
if Source.lfUnderline = 1 then
Include(fontStyle, fsUnderline);
if Source.lfStrikeOut = 1 then
Include(fontStyle, fsStrikeOut);
Font.Style := fontStyle;
Font.Charset := TFontCharset(Source.lfCharSet);
Font.Name := Source.lfFaceName;
case Source.lfPitchAndFamily and $F of
VARIABLE_PITCH: Font.Pitch := fpVariable;
FIXED_PITCH: Font.Pitch := fpFixed;
else
Font.Pitch := fpDefault;
end;
Font.Orientation := Source.lfOrientation;
end;
constructor TComPreviewHandlerFactory.Create(APreviewHandlerClass: TPreviewHandlerClass; const AClassID: TGUID; const
AName, ADescription, AFileExtension: string);
begin
inherited Create(ComServ.ComServer, APreviewHandlerClass.GetComClass, AClassID, AName, ADescription, ciMultiInstance, tmApartment);
FPreviewHandlerClass := APreviewHandlerClass;
FFileExtension := AFileExtension;
end;
function TComPreviewHandlerFactory.CreateComObject(const Controller: IUnknown): TComObject;
begin
result := inherited CreateComObject(Controller);
TComPreviewHandler(result).PreviewHandlerClass := PreviewHandlerClass;
end;
class procedure TComPreviewHandlerFactory.DeleteRegValue(const Key, ValueName: string; RootKey: DWord);
var
RegKey: HKEY;
begin
if RegOpenKeyEx(RootKey, PChar(Key), 0, KEY_ALL_ACCESS, regKey) = ERROR_SUCCESS then begin
try
RegDeleteValue(regKey, PChar(ValueName));
finally
RegCloseKey(regKey)
end;
end;
end;
class function TComPreviewHandlerFactory.IsRunningOnWOW64: Boolean;
{ code taken from www.delphidabbler.com "IsWow64" }
type
// Type of IsWow64Process API fn
TIsWow64Process = function(Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;
var
IsWow64Result: Windows.BOOL; // Result from IsWow64Process
IsWow64Process: TIsWow64Process; // IsWow64Process fn reference
begin
{$IF defined(CPUX64)}
// compiled for 64-bit: can't be running on Wow64
result := false;
{$ELSE}
// Try to load required function from kernel32
IsWow64Process := Windows.GetProcAddress(Windows.GetModuleHandle('kernel32'), 'IsWow64Process');
if Assigned(IsWow64Process) then begin
// Function is implemented: call it
if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then
raise SysUtils.Exception.Create('IsWindows64: bad process handle');
// Return result of function
Result := IsWow64Result;
end
else
// Function not implemented: can't be running on Wow64
Result := False;
{$IFEND}
end;
procedure TComPreviewHandlerFactory.UpdateRegistry(Register: Boolean);
var
plainFileName: string;
sAppID, sClassID, ProgID, ServerKeyName, RegPrefix: string;
RootKey: HKEY;
RootKey2: HKEY;
begin
if Instancing = ciInternal then
Exit;
ComServer.GetRegRootAndPrefix(RootKey, RegPrefix);
if ComServer.PerUserRegistration then
RootKey2 := HKEY_CURRENT_USER
else
RootKey2 := HKEY_LOCAL_MACHINE;
sClassID := GUIDToString(ClassID);
ProgID := GetProgID;
ServerKeyName := RegPrefix + 'CLSID\' + sClassID + '\' + ComServer.ServerKey;
if IsRunningOnWOW64 then
sAppID := '{534A1E02-D58F-44f0-B58B-36CBED287C7C}' // for Win32 shell extension running on Win64
else
sAppID := '{6d2b5079-2f0b-48dd-ab7f-97cec514d30b}';
if Register then begin
inherited;
plainFileName := ExtractFileName(ComServer.ServerFileName);
CreateRegKey(RegPrefix + 'CLSID\' + sClassID, 'AppID', sAppID, RootKey);
if ProgID <> '' then begin
CreateRegKey(ServerKeyName, 'ProgID', ProgID, RootKey);
CreateRegKey(ServerKeyName, 'VersionIndependentProgID', ProgID, RootKey);
CreateRegKey(RegPrefix + ProgID + '\shellex\' + SID_IPreviewHandler, '', sClassID, RootKey);
CreateRegKey(RegPrefix + FileExtension, '', ProgID, RootKey);
CreateRegKey('SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers', sClassID, Description, RootKey2);
end;
end
else begin
if ProgID <> '' then begin
DeleteRegValue('SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers', sClassID, RootKey2);
DeleteRegKey(RegPrefix + FileExtension, RootKey);
DeleteRegKey(RegPrefix + ProgID + '\shellex', RootKey);
end;
inherited;
end;
end;
destructor TComPreviewHandler.Destroy;
begin
FPreviewHandler.Free;
FContainer.Free;
inherited Destroy;
end;
procedure TComPreviewHandler.CheckContainer;
begin
if FContainer = nil then begin
{ I sprang for a TPanel here, because it makes things so much simpler. }
FContainer := TPanel.Create(nil);
TPanel(FContainer).BevelOuter := bvNone;
FContainer.SetBackgroundColor(FBackgroundColor);
FContainer.SetTextFont(FLogFont);
FContainer.SetTextColor(FTextColor);
FContainer.SetBoundsRect(FBounds);
FContainer.ParentWindow := FParentWindow;
end;
end;
procedure TComPreviewHandler.CheckPreviewHandler;
begin
if FPreviewHandler = nil then begin
CheckContainer;
FPreviewHandler := PreviewHandlerClass.Create(Container);
end;
end;
function TComPreviewHandler.ContextSensitiveHelp(fEnterMode: LongBool): HRESULT;
begin
result := E_NOTIMPL;
end;
function TComPreviewHandler.GetSite(const riid: TGUID; out site: IInterface): HRESULT;
begin
site := nil;
if FSite = nil then
result := E_FAIL
else if Supports(FSite, riid, site) then
result := S_OK
else
result := E_NOINTERFACE;
end;
function TComPreviewHandler.GetWindow(out wnd: HWND): HRESULT;
begin
if Container = nil then begin
result := E_FAIL;
end
else begin
wnd := Container.Handle;
result := S_OK;
end;
end;
function TComPreviewHandler.IPreviewHandler_DoPreview: HRESULT;
begin
try
CheckPreviewHandler;
InternalDoPreview;
except
on E: Exception do begin
{$IFDEF USE_CODESITE}
CodeSite.SendException(E);
{$ENDIF}
end;
end;
result := S_OK;
end;
function TComPreviewHandler.QueryFocus(var phwnd: HWND): HRESULT;
begin
phwnd := GetFocus;
result := S_OK;
end;
function TComPreviewHandler.SetBackgroundColor(color: Cardinal): HRESULT;
begin
FBackgroundColor := color;
if Container <> nil then
Container.SetBackgroundColor(FBackgroundColor);
result := S_OK;
end;
function TComPreviewHandler.SetFocus: HRESULT;
begin
if Container <> nil then begin
if GetKeyState(VK_SHIFT) < 0 then
Container.SetFocusTabLast
else
Container.SetFocusTabFirst;
end;
result := S_OK;
end;
function TComPreviewHandler.SetFont(const plf: tagLOGFONTW): HRESULT;
begin
FLogFont := plf;
if Container <> nil then
Container.SetTextFont(FLogFont);
result := S_OK;
end;
function TComPreviewHandler.SetRect(var prc: TRect): HRESULT;
begin
FBounds := prc;
if Container <> nil then
Container.SetBoundsRect(FBounds);
result := S_OK;
end;
function TComPreviewHandler.SetSite(const pUnkSite: IInterface): HRESULT;
begin
FSite := PUnkSite;
FPreviewHandlerFrame := FSite as IPreviewHandlerFrame;
result := S_OK;
end;
function TComPreviewHandler.SetTextColor(color: Cardinal): HRESULT;
begin
FTextColor := color;
if Container <> nil then
Container.SetTextColor(FTextColor);
result := S_OK;
end;
function TComPreviewHandler.SetWindow(hwnd: HWND; var prc: TRect): HRESULT;
begin
FParentWindow := hwnd;
FBounds := prc;
if Container <> nil then begin
Container.ParentWindow := FParentWindow;
Container.SetBoundsRect(FBounds);
end;
result := S_OK;
end;
function TComPreviewHandler.TranslateAccelerator(var pmsg: tagMSG): HRESULT;
begin
if FPreviewHandlerFrame = nil then
result := S_FALSE
else
result := FPreviewHandlerFrame.TranslateAccelerator(pmsg);
end;
function TComPreviewHandler.Unload: HRESULT;
begin
if PreviewHandler <> nil then
PreviewHandler.Unload;
InternalUnload;
result := S_OK;
end;
constructor TPreviewHandler.Create(AParent: TWinControl);
begin
inherited Create;
end;
class procedure TPreviewHandler.Register(const AClassID: TGUID; const AName, ADescription, AFileExtension: string);
begin
TComPreviewHandlerFactory.Create(Self, AClassID, AName, ADescription, AFileExtension);
end;
procedure TPreviewHandler.Unload;
begin
end;
constructor TIStreamAdapter.Create(ATarget: IStream);
begin
inherited Create;
FTarget := ATarget;
end;
function TIStreamAdapter.GetSize: Int64;
var
statStg: TStatStg;
begin
if Target.Stat(statStg, STATFLAG_NONAME) = S_OK then
result := statStg.cbSize
else
result := -1;
end;
function TIStreamAdapter.Read(var Buffer; Count: Longint): Longint;
begin
Target.Read(#Buffer, Count, #result);
end;
function TIStreamAdapter.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
Target.Seek(Offset, Ord(Origin), result);
end;
procedure TIStreamAdapter.SetSize(const NewSize: Int64);
begin
raise ENotImplemented.Create('SetSize not implemented');
// Target.SetSize(NewSize);
end;
procedure TIStreamAdapter.SetSize(NewSize: Longint);
begin
SetSize(Int64(NewSize));
end;
function TIStreamAdapter.Write(const Buffer; Count: Longint): Longint;
begin
raise ENotImplemented.Create('Write not implemented');
// Target.Write(#Buffer, Count, #result);
end;
function TComStreamPreviewHandler.GetPreviewHandler: TStreamPreviewHandler;
begin
Result := inherited PreviewHandler as TStreamPreviewHandler;
end;
function TComStreamPreviewHandler.IInitializeWithStream_Initialize(const pstream: IStream; grfMode: Cardinal): HRESULT;
begin
FIStream := pStream;
FMode := grfMode;
result := S_OK;
end;
procedure TComStreamPreviewHandler.InternalUnload;
begin
FIStream := nil;
end;
procedure TComStreamPreviewHandler.InternalDoPreview;
var
stream: TIStreamAdapter;
begin
stream := TIStreamAdapter.Create(FIStream);
try
PreviewHandler.DoPreview(stream);
finally
stream.Free;
end;
end;
function TComFilePreviewHandler.GetPreviewHandler: TFilePreviewHandler;
begin
Result := inherited PreviewHandler as TFilePreviewHandler;
end;
function TComFilePreviewHandler.IInitializeWithFile_Initialize(pszFilePath: LPCWSTR; grfMode: DWORD): HRESULT;
begin
FFilePath := pszFilePath;
FMode := grfMode;
result := S_OK;
end;
procedure TComFilePreviewHandler.InternalDoPreview;
begin
PreviewHandler.DoPreview(FFilePath);
end;
procedure TComFilePreviewHandler.InternalUnload;
begin
FFilePath := '';
end;
class function TFilePreviewHandler.GetComClass: TComClass;
begin
result := TComFilePreviewHandler;
end;
class function TStreamPreviewHandler.GetComClass: TComClass;
begin
result := TComStreamPreviewHandler;
end;
initialization
{$IFDEF USE_CODESITE}
CodeSiteManager.ConnectUsingTcp;
{$ENDIF}
end.
A sample preview handler based on this unit is shown here:
unit MyPreviewHandler;
interface
uses
PreviewHandler, Classes, Controls, StdCtrls;
const
{$REGION 'Unique ClassID of your PreviewHandler'}
/// <summary>Unique ClassID of your PreviewHandler</summary>
/// <remarks>Don't forget to create a new one. Best use Ctrl-G.</remarks>
{$ENDREGION}
CLASS_MyPreviewHandler: TGUID = '{64644512-C345-469F-B5FB-EB351E20129D}';
type
{$REGION 'Sample PreviewHandler'}
/// <summary>Sample PreviewHandler</summary>
/// <remarks>A sample PreviewHandler. You only have to derive from
/// TFilePreviewHandler or TStreamPreviewHandler and override some methods.</remarks>
{$ENDREGION}
TMyPreviewHandler = class(TFilePreviewHandler)
private
FTextLabel: TLabel;
protected
public
constructor Create(AParent: TWinControl); override;
procedure Unload; override;
procedure DoPreview(const FilePath: string); override;
property TextLabel: TLabel read FTextLabel;
end;
implementation
uses
SysUtils;
constructor TMyPreviewHandler.Create(AParent: TWinControl);
begin
inherited;
FTextLabel := TLabel.Create(AParent);
FTextLabel.Parent := AParent;
FTextLabel.AutoSize := false;
FTextLabel.Align := alClient;
FTextLabel.Alignment := taCenter;
FTextLabel.Layout := tlCenter;
FTextLabel.WordWrap := true;
end;
procedure TMyPreviewHandler.DoPreview(const FilePath: string);
begin
TextLabel.Caption := GetPackageDescription(PWideChar(FilePath));
end;
procedure TMyPreviewHandler.Unload;
begin
TextLabel.Caption := '';
inherited;
end;
initialization
{ Register your PreviewHandler with the ClassID, name, descripton and file extension }
TMyPreviewHandler.Register(CLASS_MyPreviewHandler, 'bplfile', 'BPL Binary Preview Handler', '.bpl');
end.
library MyPreviewHandlerLib;
uses
ComServ,
PreviewHandler in 'PreviewHandler.pas' {PreviewHandler: CoClass},
MyPreviewHandler in 'MyPreviewHandler.pas';
exports
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer,
DllInstall;
{$R *.RES}
begin
end.
You may be interested in this article in my blog describing some more details on that framework.
I have never seen such a thing, but since the whole thing is build in COM, you would start by importing the type library, and implementing the required interfaces, including IPreviewHandlerFrame. [Sorry, not very helpful. But this is a pretty obscure area, so I'm not surprised that Delphi hasn't got a prebuilt component set for this.]
I can't find any references to using IPreviewHandlerFrame in Delphi, but did manage to come up with a C# example here - maybe it'll give you a starting point.
I think you have to write a COM-Server yourself, which provides the described IPreviwHandler-Interfacees. (There is no type library to import...) I am very interested in such a code as well and I am searching for quite a while now. I am not very experienced with COM-Server-writing... If you find something, let me know please! As I will share my code also, if I get some...
Andreas
I am using Delphi 2007. I can successfully Post data to a web site using WebBrowser.Navigate, but afterwards, when that site returns a PDF, while it appears on the screen of the Browser, I cannot figure out how to acquire the PDF programmatically. I can see some text and HTML using Document.Body.InnerHTML, but not the PDF. Can someone demonstrate how to acquire the PDF which appears after the POST?
Thank yoU!
To get the text out of a PDF in the web browser, I found a solution using an open source unit called PushKeys to send keys to the web browser to select all the text (Control+A), copy it to the clipboard (Control+C) and then paste it to a TMemo or other control using PasteFromClipBoard. Tested in D2007.
WebBrowser.SetFocus; // set the focus to the TWebBrowser control
Sleep(1000); // 1 second delay to be sure webbrowser actually has focus
Application.ProcessMessages;
PushKeys('^a'); //send ctrl-a to select all text
Application.ProcessMessages;
WebBrowser.SetFocus;
PushKeys('^c'); //send ctrl-c to copy the text to clipboard
Sleep(1000); // 1 second delay to make sure clipboard finishes processing
Application.ProcessMessages;
Memo1.PasteFromClipBoard; // Paste the clipboard to a memo field.
// You could also use the clipbrd unit to handle the data.
//for Multi-page PDF's, you can send a PageDn key to get to the next page:
PushFnKey('PAGEDOWN');
You could use an IE4+ option for capturing all internet traffic using your own protocol. You can even hook the protocol http (IIRC) and when you need to load the data use the WIndows functions and/or Indy components.
This is a unit to do so:
{
This component allows you to dynamically create your own internet protocols for
Microsoft Internet Explorer 4+. Simply place the component on your form, set the protocol
property to something useful and set the Active property.
For example, when the Protocol is set to 'private', you can trap requests to
'private:anythingyoulike'.
}
unit UnitInternetProtocol;
// Developed by: R.A. Hornstra
// (C) 2001 ContinuIT BV
interface
uses
SysUtils, Windows, Classes, Messages;
type
TInternetProtocol = class;
{
When a request is made, the data must be returned in a TStream descendant.
The request is present in Request. The result should be saved in Stream.
When no data can be linked to the request, leave Stream equal to nil.
See #link(TInternetProtocol.OnRequestStream) and #link(TInternetProtocol.OnReleaseStream).
}
TProtocolRequest = procedure(Sender: TInternetProtocol; const Request: string;
var Stream: TStream) of object;
{
When a request is done by the Microsoft Internet Explorer it is done via an URL.
This URL starts with a protocol, than a colon and than a protocol specific resource identifier.
New protocols can be added dynamically and privately for each session.
This component will register / deregister new protocols to the Microsoft Internet Explorer.
You should set the name of the protocol with #link(Protocol), activate / deactivate the
protocol with #link(Active). The implementation of the protocol can be done with the
events #link(OnRequestStream) and #link(OnReleaseStream).
}
TInternetProtocol = class(TComponent)
private
FHandle: HWnd;
FActive: Boolean;
FProtocol: string;
FRequest: TProtocolRequest;
FRelease: TProtocolRequest;
procedure SetActive(const Value: Boolean);
procedure SetProtocol(const Value: string);
protected
procedure Loaded; override;
procedure Activate;
procedure Deactivate;
procedure WndProc(var Message: TMessage);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{
Setting this property will activate or deactivate the internet
}
property Active: Boolean read FActive write SetActive;
{
The protocol name must be specified. default, this is 'private'.
You should fill it here without the trailing colon (that's part of the URL notation).
Protocol names should be valid identifiers.
}
property Protocol: string read FProtocol write SetProtocol;
{
When a request is made on the selected protocol, this event is fired.
It should return a TStream, based upon the given Request.
The default behaviour of TInternetProtocol is freeing the stream.
To override or monitor this behaviour, use #link(OnRequestStream).
}
property OnRequestStream: TProtocolRequest read FRequest write FRequest;
{
When a stream is about to be released by TInternetProtocol, you can override the
default behaviour. By Setting the Stream variable to nil in the OnReleaseStream handler,
the stream will not be released by TInternetProtocol.
This is handy when you're implementing a caching system, or for some reason need control on
the creation and deletion to the streams.
The default behaviour of TInternetProtocol is freeing the stream.
}
property OnReleaseStream: TProtocolRequest read FRelease write FRelease;
end;
{
All exceptions raised by #link(TInternetProtocol) are of type EInternetException.
}
EInternetException = class(Exception);
procedure Register;
implementation
uses
ComObj, ActiveX, UrlMon, Forms;
resourcestring
strNotAValidProtocol = 'The Internet Protocol selected is not a valid protocol identifier';
// todo: move registration to separate file
procedure Register;
begin
Classes.RegisterComponents('Internet',[TInternetProtocol]);
end;
// forward declarations
procedure RegisterProtocol(Protocol: string; Handler: TInternetProtocol); forward;
procedure UnregisterProtocol(Protocol: string); forward;
const
IID_TInternetProtocolHandler: TGUID = '{B74826E0-1107-11D5-B166-0010D7090486}';
WM_STREAMNEEDED = WM_USER;
{ TInternetProtocol }
constructor TInternetProtocol.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FActive := False;
FProtocol := 'private';
FRequest := nil;
FRelease := nil;
FHandle := Forms.AllocateHWnd(WndProc);
end;
destructor TInternetProtocol.Destroy;
begin
Active := False;
Forms.DeallocateHWnd(FHandle);
inherited Destroy;
end;
procedure TInternetProtocol.Loaded;
begin
inherited Loaded;
if FActive then Activate;
end;
procedure TInternetProtocol.SetActive(const Value: Boolean);
begin
if Value = FActive then Exit;
if Value then begin
if not (csLoading in ComponentState) then Activate;
end else begin
Deactivate;
end;
FActive := Value;
end;
procedure TInternetProtocol.Activate;
begin
if csDesigning in ComponentState then Exit;
RegisterProtocol(FProtocol,Self);
end;
procedure TInternetProtocol.Deactivate;
begin
if csDesigning in ComponentState then Exit;
UnregisterProtocol(FProtocol);
end;
procedure TInternetProtocol.SetProtocol(const Value: string);
var AActive: Boolean;
begin
if not SysUtils.IsValidIdent(Value) then raise EInternetException.Create(strNotAValidProtocol);
AActive := FActive;
try
Active := False;
FProtocol := Value;
finally
Active := AActive;
end;
end;
procedure TInternetProtocol.WndProc(var Message: TMessage);
var
Msg: packed record
Msg: Longword;
Request: PChar;
Stream: ^TStream;
end;
begin
if Message.Msg = WM_STREAMNEEDED then begin
System.Move(Message,Msg,SizeOf(Msg));
if Assigned(FRequest) then FRequest(Self,string(Msg.Request),Msg.Stream^);
end else Message.Result := Windows.DefWindowProc(FHandle,Message.Msg,Message.WParam,Message.LParam);
end;
var
Session: IInternetSession; // The current Internet Session
Factory: IClassFactory; // Factory of our IInternetProtocol implementation
Lock: TRTLCriticalSection; // The lock for thread safety
List: TStrings; // The list of active protocol handlers
type
TInternetProtocolHandler = class(TInterfacedObject, IInternetProtocol)
private
ProtSink: IInternetProtocolSink; // Protocol Sink that needs the data
Stream: TStream; // Stream containing the data
StreamPosition: Integer; // Current Position in the stream
StreamSize: Integer; // Current size of the stream
LockCount: Integer; // Lock count for releasing data
procedure ReleaseStream;
public
{ IInternetProtocol }
function Start(szUrl: PWideChar; OIProtSink: IInternetProtocolSink;
OIBindInfo: IInternetBindInfo; grfPI, dwReserved: DWORD): HResult; stdcall;
function Continue(const ProtocolData: TProtocolData): HResult; stdcall;
function Abort(hrReason: HResult; dwOptions: DWORD): HResult; stdcall;
function Terminate(dwOptions: DWORD): HResult; stdcall;
function Suspend: HResult; stdcall;
function Resume: HResult; stdcall;
function Read(pv: Pointer; cb: ULONG; out cbRead: ULONG): HResult; stdcall;
function Seek(dlibMove: LARGE_INTEGER; dwOrigin: DWORD;
out libNewPosition: ULARGE_INTEGER): HResult; stdcall;
function LockRequest(dwOptions: DWORD): HResult; stdcall;
function UnlockRequest: HResult; stdcall;
end;
TInternetProtocolHandlerFactory = class(TInterfacedObject, IClassFactory)
public
{ IClassFactory }
function CreateInstance(const unkOuter: IUnknown; const iid: TIID; out obj): HResult; stdcall;
function LockServer(fLock: BOOL): HResult; stdcall;
end;
procedure RegisterProtocol(Protocol: string; Handler: TInternetProtocol);
var
i: Integer;
Proto: WideString;
begin
Windows.EnterCriticalSection(Lock);
try
// if we have a previous handler, delete that from the list.
i := List.IndexOf(Protocol);
if i >=0 then TInternetProtocol(List.Objects[i]).Active := False;
// If this is the first time, create the Factory and get the Internet Session object
if List.Count = 0 then begin
Factory := TInternetProtocolHandlerFactory.Create;
CoInternetGetSession(0, Session, 0);
end;
// Append ourselves to the list
List.AddObject(Protocol,Handler);
// Register the protocol with the Internet session
Proto := Protocol;
Session.RegisterNameSpace(Factory, IInternetProtocol{ IID_TInternetProtocolHandler}, PWideChar(Proto), 0, nil, 0);
finally
Windows.LeaveCriticalSection(Lock);
end;
end;
procedure UnregisterProtocol(Protocol: string);
var i: Integer;
Proto: WideString;
begin
Windows.EnterCriticalSection(Lock);
try
i := List.IndexOf(Protocol);
if i < 0 then Exit; // oops, protocol was somehow already freed... this should not happen
// unregister our namespace handler
Proto := Protocol; // to widestring
Session.UnregisterNameSpace(Factory, PWideChar(Proto));
// and free from list
List.Delete(i);
// see if we need to cleanup?
if List.Count = 0 then begin
// release the COM server
Session := nil;
Factory := nil;
end;
finally
Windows.LeaveCriticalSection(Lock);
end;
end;
{ TInternetProtocolHandler }
function TInternetProtocolHandler.Abort(hrReason: HResult; dwOptions: DWORD): HResult;
begin
Result := E_NOTIMPL;
end;
function TInternetProtocolHandler.Continue(const ProtocolData: TProtocolData): HResult;
begin
Result := S_OK;
end;
function TInternetProtocolHandler.LockRequest(dwOptions: DWORD): HResult;
begin
Inc(LockCount);
Result := S_OK;
end;
function TInternetProtocolHandler.Read(pv: Pointer; cb: ULONG; out cbRead: ULONG): HResult;
const Results: array [Boolean] of Longword = ( E_PENDING, S_FALSE );
begin
if Assigned(Stream) then cbRead := Stream.Read(pv^,cb) else cbRead := 0;
Inc(StreamPosition, cbread);
Result := Results[StreamPosition = StreamSize];
end;
procedure TInternetProtocolHandler.ReleaseStream;
begin
// see if we can release the Stream...
if Assigned(Stream) then FreeAndNil(Stream);
Protsink := nil;
end;
function TInternetProtocolHandler.Resume: HResult;
begin
Result := E_NOTIMPL;
end;
function TInternetProtocolHandler.Seek(dlibMove: LARGE_INTEGER;
dwOrigin: DWORD; out libNewPosition: ULARGE_INTEGER): HResult;
begin
Result := E_NOTIMPL;
end;
function TInternetProtocolHandler.Start(szUrl: PWideChar; OIProtSink: IInternetProtocolSink;
OIBindInfo: IInternetBindInfo; grfPI,dwReserved: DWORD): HResult;
var URL, Proto: string;
i: Integer;
Handler: TInternetProtocol;
begin
// Sanity check.
Assert(Assigned(OIProtSink));
Assert(Assigned(szUrl));
Assert(Assigned(OIBindInfo));
URL := szUrl;
Stream := nil; // just to make sure...
// Clip the protocol name from the URL & change the URL to the proto specific part
i := Pos(':',URL);
if i > 0 then begin
Proto := Copy(URL,1,i-1);
URL := Copy(URL,i+1,MaxInt);
end;
Windows.EnterCriticalSection(Lock);
try
i := List.IndexOf(Proto);
if i >= 0 then begin
// we've found our protocol
Handler := TInternetProtocol(List.Objects[i]);
// And query. Use a Windows message for thread synchronization
Windows.SendMessage(Handler.FHandle,WM_STREAMNEEDED,WParam(PChar(URL)),LParam(#Stream));
end;
finally
Windows.LeaveCriticalSection(Lock);
end;
if not Assigned(Stream) then begin
Result := INET_E_USE_DEFAULT_PROTOCOLHANDLER;
Exit;
end;
// Setup all data
StreamSize := Stream.Size;
Stream.Position := 0;
StreamPosition := 0;
LockCount := 1;
// Get the protocol sink & start the 'downloading' process
ProtSink := OIProtSink;
ProtSink.ReportData(BSCF_FIRSTDATANOTIFICATION or BSCF_LASTDATANOTIFICATION or
BSCF_DATAFULLYAVAILABLE, StreamSize, StreamSize);
ProtSink.ReportResult(S_OK, S_OK, nil);
Result := S_OK;
end;
function TInternetProtocolHandler.Suspend: HResult;
begin
Result := E_NOTIMPL;
end;
function TInternetProtocolHandler.Terminate(dwOptions: DWORD): HResult;
begin
Dec(LockCount);
if LockCount = 0 then ReleaseStream;
Result := S_OK;
end;
function TInternetProtocolHandler.UnlockRequest: HResult;
begin
Dec(LockCount);
if LockCount = 0 then ReleaseStream;
Result := S_OK;
end;
{ TInternetProtocolHandlerFactory }
function TInternetProtocolHandlerFactory.CreateInstance(const unkOuter: IInterface;
const iid: TIID; out obj): HResult;
begin
if IsEqualGUID(iid, IInternetProtocol) then begin
IInternetProtocol(obj) := TInternetProtocolHandler.Create as IInternetProtocol;
Result := S_OK;
end else if IsEqualGUID(iid, IInterface) then begin
IInterface(obj) := TInternetProtocolHandler.Create as IInterface;
Result := S_OK;
end else begin
Result := E_NOINTERFACE;
end;
end;
function TInternetProtocolHandlerFactory.LockServer(fLock: BOOL): HResult;
begin
if fLock then _AddRef else _Release;
Result := S_OK;
end;
initialization
begin
// Get a critical section for thread synchro
Windows.InitializeCriticalSection(Lock);
// The list of protocol handlers
List := TStringList.Create;
end;
finalization
begin
// deactivate all handlers (should only happen when memory leaks are present...)
while List.Count > 0 do TInternetProtocol(List.Objects[0]).Active := False;
List.Free;
// and delete the critical section
Windows.DeleteCriticalSection(Lock);
end;
end.