2011. január 31., hétfő
Impersonating a User on Windows NT is a three step process
Problem/Question/Abstract:
How do I simulate the unix SU command under windows NT. In other words I want to run an app under a different user...
Answer:
This code was not written by me.
The original copyright information is still intact.
{*
SU.DPR for Delphi32 Pascal
by Fred - APIKing - de Jong, Heerlen, Netherlands 1997
home: frejon@worldonline.nl, office: fjng@cbs.nl
su.cpp
UNIX-like Substitute User for Windows NT
Usage:
su [NewDomain\][NewUser] [command-line]
where:
NewDomain\ is desired domain logon (\\ is ok also)
NewUser is the name of the user to be impersonated. Default is Administrator.
command-line is the command to be executed, with parameters. Default is CMD (Console)
Authors:
David Wihl (wihl@shore.net)
Steffen Krause (skrause@informatik.hu-berlin.de)
Revision History:
xx-JUL-1995.
- Removed restriction on command line (User can now specify anything)
- Added NewDomain logon on command line
- Added Unicode support but found bug in LogonUserW
03-JUL-1995. Initial public release
Design:
Impersonating a User on Windows NT is a three step process:
1- Logon the User to create a Security identifier
2- Enabling access to the Windows Station so the newly logged on NewUser
can interact. This is necessary even if the Administrator is logging on.
3- Creating a process using the Security identifier
Different privileges are required for steps (1) and (3). Logging on a User
(LogonUser()) requires the SeTcbPrivilege. Creating a process as another User
CreateProcessAsUser()) requires SeAssignPrimary and SeIncreaseQuota privileges.
To grant these privileges, see the Installation Section.
These two Security API calls were only stablized in NT 3.51, build 1057. SU will
not work with earlier versions.
In NT, there is no direct equivalent of UNIX's rwsr-xr-x file permission.
Restrictions and Limitations:
- There is no logging of failed or successful usage. A future may incorporate
writing to the Event Log.
Installation:
The easiest way to selectively grant the three privileges required to use this
program is:
1- Start the User Manager (MUSRMGR)
2- Create a new group (e.g. "SU Users")
3- Add the three privileges to the group (via Policies\User Rights):
"Act as part of the operating system" - SeTcbPrivilege
"Increase quotas" - SeIncreaseQuota
"Replace a process level token" - SeAssignPrimaryToken
NOTE: The three privileges will only be visible if you check
"Show Advanced User Rights" in the dialog box.
4- Add the desired users to the new group (via User\Properties\Group)
This program was compiled under Visual C++ 2.1 with the June '95 SDK
For more information about Porting from UNIX to NT check the FAQ:
http://www.shore.net/~wihl/unix2nt.html
*}
program su;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils { already has SysErrorMessage function };
//{$R VersInfo.RES}
//
// CUSTOMIZATION OPTIONS - put 'em here
const
DEFAULT_USER: string = 'Administrator';
// if we don't specify a username, who are we?
DEFAULT_CMD: string = 'cmd'; // if we don't specify a command, what do we do?
{$DEFINE VERBOSE} // quiet ?la UNIX, or chatty?
//
// END CUSTOMIZATION OPTIONS
//
const
SECURITY_DESCRIPTOR_REVISION = 1; // from winnt.h, missing in windows.pas
////////////////////////////////////////////////////////////////////////
// //
// NT Defined Privileges //
// //
////////////////////////////////////////////////////////////////////////
SE_CREATE_TOKEN_NAME = 'SeCreateTokenPrivilege';
SE_ASSIGNPRIMARYTOKEN_NAME = 'SeAssignPrimaryTokenPrivilege';
SE_LOCK_MEMORY_NAME = 'SeLockMemoryPrivilege';
SE_INCREASE_QUOTA_NAME = 'SeIncreaseQuotaPrivilege';
SE_UNSOLICITED_INPUT_NAME = 'SeUnsolicitedInputPrivilege';
SE_MACHINE_ACCOUNT_NAME = 'SeMachineAccountPrivilege';
SE_TCB_NAME = 'SeTcbPrivilege';
SE_SECURITY_NAME = 'SeSecurityPrivilege';
SE_TAKE_OWNERSHIP_NAME = 'SeTakeOwnershipPrivilege';
SE_LOAD_DRIVER_NAME = 'SeLoadDriverPrivilege';
SE_system_PROFILE_NAME = 'SesystemProfilePrivilege';
SE_systemTIME_NAME = 'SesystemtimePrivilege';
SE_PROF_SINGLE_PROCESS_NAME = 'SeProfileSingleProcessPrivilege';
SE_INC_BASE_PRIORITY_NAME = 'SeIncreaseBasePriorityPrivilege';
SE_CREATE_PAGEFILE_NAME = 'SeCreatePagefilePrivilege';
SE_CREATE_PERMANENT_NAME = 'SeCreatePermanentPrivilege';
SE_BACKUP_NAME = 'SeBackupPrivilege';
SE_RESTORE_NAME = 'SeRestorePrivilege';
SE_SHUTDOWN_NAME = 'SeShutdownPrivilege';
SE_DEBUG_NAME = 'SeDebugPrivilege';
SE_AUDIT_NAME = 'SeAuditPrivilege';
SE_system_ENVIRONMENT_NAME = 'SesystemEnvironmentPrivilege';
SE_CHANGE_NOTIFY_NAME = 'SeChangeNotifyPrivilege';
SE_REMOTE_SHUTDOWN_NAME = 'SeRemoteShutdownPrivilege';
{ ------------------------------------------------- }
{ support standard Error output, besides standard Output/Input }
var
Error: TextFile;
procedure InitErrorOutput;
begin
AssignFile(Error, EmptyStr);
Rewrite(Error);
TTextRec(Error).Handle := GetStdHandle(STD_ERROR_HANDLE);
end;
var
_TokenizeStr: PChar = nil;
_TokenizeLast: PChar = nil;
function Tokenize(const SourceText: string; const Delimiters: string): string;
{ this is my Delphi version of C's strtok():
1st call: SourceText is not empty, next calls: SourceText is EmptyStr;
set of delimiters can change while tokenizing;
implicit string memory allocation is hidden for the outside:
Tokenize only parses one SourceText at a time. }
var
R, S: PChar;
begin
if length(SourceText) = 0 then
R := _TokenizeLast
else
begin { cleanup and (re)initialize }
_TokenizeLast := nil;
StrDispose(_TokenizeStr);
_TokenizeStr := StrNew(PChar(SourceText));
R := _TokenizeStr;
end;
if R <> nil then
begin
S := R; { find next delim }
while (S^ <> chr(0)) and (StrScan(PChar(Delimiters), S^) = nil) do
inc(S);
if S^ <> chr(0) then
begin
S^ := chr(0); { got delim, truncate R result }
inc(S); { skip over delims to set _TokenizeLast }
while (S^ <> chr(0)) and (StrScan(PChar(Delimiters), S^) <> nil) do
inc(S);
if S^ <> chr(0) then
_TokenizeLast := S;
end;
Result := string(R);
if S^ = chr(0) then
begin { cleanup early }
_TokenizeLast := nil;
StrDispose(_TokenizeStr);
_TokenizeStr := nil;
end
end
else
Result := EmptyStr
end;
{ -------------------------------------------------------------- }
const
DEFWINSTATION: string = 'WinSta0';
DEFDESKTOP: string = 'Default';
WHITESPACE: string = ' ' {SPACE} + chr(9) {TAB} + chr(10) {LF};
DOMUSERSEP: string = '\';
procedure ErrorHandler(const errmsg: string);
var
err: dword;
begin
err := GetLastError;
writeln(Error, 'Error: ', errmsg, '.');
write(Error, SysErrorMessage(err));
end;
function SetUserObjectAllAccess(hUserObject: THANDLE): boolean;
var
pSD: PSecurity_Descriptor;
si: Security_Information; { dword }
begin
(* Initialize a security descriptor. *)
pSD := PSecurity_Descriptor(
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH));
if pSD = nil then
begin
ErrorHandler('Can''t Allocate Local Memory');
Result := FALSE;
exit;
end;
if not InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION) then
begin
ErrorHandler('Can''t Initialize Security Descriptor');
LocalFree(HLOCAL(pSD));
Result := FALSE;
exit;
end;
{* Add a NULL disc. ACL to the security descriptor. *}
if not SetSecurityDescriptorDacl(pSD,
TRUE, // specifying a disc. ACL
PACL(nil),
FALSE) then // not a default disc. ACL
begin
ErrorHandler('Can''t Set Security Descriptor DACL');
LocalFree(HLOCAL(pSD));
Result := FALSE;
exit;
end;
{* Add the security descriptor to the userobject (like a window or a DDE
conversation), NOT to a kernelobject (like a process, thread or event). *}
si := DACL_SECURITY_INformATION;
Result := SetUserObjectSecurity(hUserObject, si, pSD);
LocalFree(HLOCAL(pSD));
if not Result then
ErrorHandler('Can''t Set NewUser Object Security')
end;
function GetUserObjectName(hUserObject: THandle; var Name: string): boolean;
var
dw: DWord;
begin
Name := EmptyStr;
GetUserObjectInformation(hUserObject, UOI_NAME, PChar(Name), 0, dw);
SetLength(Name, dw + 1);
Result := GetUserObjectInformation(hUserObject, UOI_NAME, PChar(Name), dw, dw);
if Result then
SetLength(Name, dw - 1)
else
Name := EmptyStr;
end;
function GetPrivilegeDisplayName(const PrivilegeName: string): string;
{ PrivilegeName is of string type 'SE_'* }
var
dw, li: DWord;
begin
Result := EmptyStr;
dw := 0;
li := 0; { li:= dword(MAKELANGID(LANG_DEFAULT, LANG_USER)); }
if not LookupPrivilegeDisplayName(nil, PChar(PrivilegeName), PChar(Result), dw, li)
then
dw := 256;
SetLength(Result, dw + 1);
if LookupPrivilegeDisplayName(nil, PChar(PrivilegeName), PChar(Result), dw, li) then
SetLength(Result, StrLen(PChar(Result)))
else
Result := EmptyStr;
end;
function GetAccountInfo(var CurUser, CurDomain: string): boolean;
var
dw, dw2: DWord;
pSD: PSecurity_Descriptor;
snu: Sid_Name_Use;
begin
Result := False;
dw := 255;
Setlength(CurUser, dw + 1);
if GetUserName(PChar(CurUser), dw) then
begin
SetLength(CurUser, dw - 1);
dw2 := 256;
SetLength(CurDomain, dw2);
snu := SidTypeUser;
pSD := nil;
dw := 0; { get needed length for SID }
LookUpAccountName(nil {LocalMachine}, PChar(CurUser),
pSD, dw, PChar(CurDomain), dw2, snu);
if dw <> 0 then
begin
pSD := PSecurity_Descriptor(LocalAlloc(LPTR, dw));
if pSD <> nil then
begin
if LookUpAccountName(nil, PChar(CurUser), { get the real thing }
pSD, dw, PChar(CurDomain), dw2, snu) then
begin
SetLength(CurDomain, dw2);
Result := True;
end
else
CurDomain := EmptyStr;
LocalFree(HLOCAL(pSD));
end;
end;
end
else
CurUser := EmptyStr;
end;
function GetMachineName: string;
var
dw: DWord;
begin
dw := MAX_COMPUTERNAME_LENGTH + 1;
SetLength(Result, MAX_COMPUTERNAME_LENGTH + 1);
if GetComputerName(PChar(Result), dw) then
SetLength(Result, dw)
else
Result := EmptyStr;
end;
{ ---------------------------------------------------------- }
var
CurUser, // Current User
CurDomain, // Current Domain
pwstr, // password string
consoleTitle, // Title if new console only
NewDomUser, // NewDomain\NewUser combination
CommandLine, // command line we pass to the new process
NewDomain, // NewDomain to log onto
NewUser: string; // NewUser to log onto
startUpInfo: TStartupInfo;
procInfo: TProcessInformation; // child process info, from CreateProcessAsUser
hDesktop: HDESK;
hWindowStation: HWINSTA;
hUserToken, hConsIn: THANDLE;
OldConsInMode, NewConsInMode: DWORD;
NTversion: TOSVersionInfo;
S, DeskTopName, WinStaName: string;
RC: integer;
begin { program }
InitErrorOutput; // Attach outputfile Error to STDERR
// Make sure we are using the minimum OS version.
NTversion.dwOSVersionInfoSize := sizeof(TOSVersionInfo);
if not GetVersionEx(NTversion) then
begin
ErrorHandler('Unable to get OS version');
halt(1);
end;
if NTversion.dwPlatformId <> VER_PLATform_WIN32_NT then
begin
writeln(Error, 'SU will run only on Windows NT.');
halt(1);
end;
if NTversion.dwBuildNumber < 1057 then // Commercial 3.51 release
begin
writeln(Error, 'SU requires at minimum NT version 3.51 build 1057.');
halt(1);
end;
//{$IFDEF DEBUG}
writeln('SU: NT Version ', NTversion.dwMajorVersion, '.',
NTversion.dwMinorVersion, ', build ', NTversion.dwBuildNumber);
// {$ENDIF}
GetAccountInfo(CurUser, CurDomain);
writeln('You are ', CurDomain, '\', CurUser);
// Process the command line parameters
Tokenize(string(CmdLine), WHITESPACE);
NewDomUser := Tokenize(EmptyStr, WHITESPACE);
if length(NewDomUser) = 0 then
begin
NewDomUser := DEFAULT_USER;
CommandLine := DEFAULT_CMD;
end
else
begin
CommandLine := Tokenize(EmptyStr, EmptyStr);
if length(CommandLine) = 0 then
CommandLine := DEFAULT_CMD;
end;
if Pos(DOMUSERSEP, NewDomUser) > 0 then
begin
NewDomain := Tokenize(NewDomUser, DOMUSERSEP);
NewUser := Tokenize(EmptyStr, DOMUSERSEP);
if length(NewUser) = 0 then
NewUser := DEFAULT_USER;
end
else
begin
NewDomain := EmptyStr;
NewUser := NewDomUser;
end;
if (length(NewDomain) = 0) and
((NewUser = '-?') or (NewUser = '/?') or (NewUser = '?')) then
begin
writeln;
writeln('Runs Windows NT commands under another user''s account.');
writeln;
writeln('SU [newdomain\][newuser] [command-line]');
writeln;
writeln(' [newdomain\] Specifies desired domain logon (\\ is ok also).');
writeln(' [newuser] Specifies the name of the user to be impersonated.');
writeln(' The default is Administrator.');
writeln(' [command-line] Specifies the command to be executed, with parameters.');
writeln(' The default is CMD (a new NT Console).');
writeln;
writeln('Requires three extended NT privileges:');
writeln;
writeln(' ', GetPrivilegeDisplayName(SE_TCB_NAME), ',');
writeln(' ', GetPrivilegeDisplayName(SE_ASSIGNPRIMARYTOKEN_NAME), ' and');
writeln(' ', GetPrivilegeDisplayName(SE_INCREASE_QUOTA_NAME), '.');
writeln;
writeln('These can be granted as User Rights with NT User Manager.');
halt(0);
end;
// Turn off console mode echo, since we don't want clear-screen passwords
system.Reset(Input); {GetStdHandle(STD_INPUT_HANDLE)}
hConsIn := TTextRec(Input).Handle;
//if hConsIn = INVALID_HANDLE_values then
//begin
// ErrorHandler ('Can''t get handle of STDIN'); halt(1);
//end;
if not GetConsoleMode(hConsIn, OldConsInMode) then
begin
ErrorHandler('Can''t get current Console Mode');
halt(1);
end;
NewConsInMode := OldConsInMode and (not ENABLE_ECHO_INPUT);
if not SetConsoleMode(hConsIn, NewConsInMode) then
begin
ErrorHandler('Unable to turn off Echo');
halt(1);
end;
// Ask for the password
{$IFDEF VERBOSE}
if length(NewDomain) = 0 then
S := CurDomain
else
S := NewDomain;
writeln('Logging onto ', S, ' domain as ', NewUser, '.');
{$ENDIF}
write('Enter password: ');
readln(pwstr);
// When echo is off and NewUser hits , CR-LF is not echoed, so do it for him
writeln;
if not SetConsoleMode(hConsIn, OldConsInMode) then
begin
ErrorHandler('Unable to reset previous console mode');
halt(1);
end;
CloseHandle(hConsIn);
// Do the Logon
if not LogonUser(PChar(NewUser), PChar(NewDomain), PChar(pwstr),
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, hUserToken) then
begin
case GetLastError of
ERROR_PRIVILEGE_NOT_HELD:
begin
writeln(Error,
'Error: you do not have the following extended User Right:');
writeln(Error, GetPrivilegeDisplayName(SE_TCB_NAME), '.');
end;
ERROR_LOGON_FAILURE:
ErrorHandler('LogonUser failed.');
ERROR_ACCESS_DENIED:
ErrorHandler('Access is denied');
else
ErrorHandler('Unable to logon');
end;
halt(2);
end;
// give the NewUser access to the current WindowStation and Desktop
hWindowStation := GetProcessWindowStation;
if not GetUserObjectName(hWindowStation, WinStaName) then
WinStaName := DEFWINSTATION;
if not SetUserObjectAllAccess(hWindowStation) then
begin
write(Error, 'Can''t set WindowStation ', WinStaName, ' security.');
CloseHandle(hUserToken);
halt(3);
end;
hDesktop := GetThreadDesktop(GetCurrentThreadId);
if not GetUserObjectName(hDesktop, DeskTopName) then
DeskTopName := DEFDESKTOP;
if not SetUserObjectAllAccess(hDesktop) then
begin
write(Error, 'Can''t set Desktop ', DeskTopName, ' security.');
CloseHandle(hUserToken);
halt(3);
end;
// Set the STARTUPINFO for the new process
if length(NewDomain) <> 0 then
NewDomain := NewDomain + '\';
consoleTitle := 'SU: ' + NewDomain + NewUser;
FillChar(startUpInfo, sizeof(startUpInfo), 0);
with startUpInfo do
begin
cb := sizeof(startUpInfo);
lpTitle := PChar(consoleTitle);
S := WinStaName + '\' + DeskTopName;
lpDesktop := PChar(S);
end;
// Create the child process
if not CreateProcessAsUser(hUserToken,
nil, PChar(CommandLine), nil, nil, FALSE {no inherit handles},
CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP,
nil, nil, startUpInfo, procInfo) then
begin
case GetLastError of
ERROR_PRIVILEGE_NOT_HELD:
begin
writeln(Error, 'Error: missing (one of) following extended User Rights:');
writeln(Error, GetPrivilegeDisplayName(SE_ASSIGNPRIMARYTOKEN_NAME), ', or');
writeln(Error, GetPrivilegeDisplayName(SE_INCREASE_QUOTA_NAME), '.');
ErrorHandler(EmptyStr);
end;
ERROR_FILE_NOT_FOUND:
ErrorHandler('Error: command in ''' + CommandLine + ''' not found.');
else
ErrorHandler('Error: CreateProcessAsUser failed.');
end;
RC := 4;
end
else
RC := 0;
CloseHandle(hWindowStation);
CloseHandle(hDesktop);
CloseHandle(hUserToken);
if RC = 0 then
begin
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
end;
halt(RC);
end.
2011. január 30., vasárnap
How to avoid direct input into a TDBGrid when there is a lookup list available
Problem/Question/Abstract:
In my TDBGrid, some columns have a picklist. Users can enter any value in these columns, not only values from the picklist. Does anybody know a simple way to prevent this? I would like either to suppress direct input and only allow selection of an existing item of the picklist, or check that input is in the picklist.
Answer:
procedure DBGridColExit(Sender: TObject);
var
S: string;
begin
with TDBGrid(Sender) do
begin
if (SelectedField = myfield) and
(Columns[SelectedIndex].PickList.IndexOf(SelectedField.AsString) = -1) then
with SelectedField do
begin
S := AsString;
AsString := '';
raise ExceptionCreate(S + ' is not a valid thingummy.' +
#13'Choose a value from the drop-down list.');
end;
end;
end;
2011. január 29., szombat
How to check if a menu selection has been dispatched by the TPopupList
Problem/Question/Abstract:
I am creating a dynamic popup menu, fill it and call Popup to show the menu. After that method returns I am done with the menu, so I free it (see comment of Peter below). The problems is the OnClick events never get called. I found the problem but don't see how to get around it. When the menu is created it is added to the PopupList which has created a hidden window whose responsibility it is to dispatch the wm_Command message sent by all popup menus. The problem is that the wm_Command is happening after the Popup menu method returns and after I have freed the menu and it has been removed from the PopupList.
How do I get around this? I don't see a mechanism to check if the menu selection has been dispatched by the PopupList before freeing the menu item. I guess I could make the Popup menu a field of my class and free it in the OnClick Events but I won't be able to free the menu if no menu item is selected. I don't like this solution since the only place I need the popup is in one method of my class so I want to keep it a local variable.
Answer:
You go badly wrong when you think you're done. When Popup returns you are not done with the menu, you have just shown it and the user can now make a menu selection or close the menu by clicking elsewhere or hittin ESC. Only after that has happended are you truely "done" with the menu, if you destroy the VCl wrapper earlier the windows menu may visually persist but you have destroyed the link between it and your code.
In D5 there is a solution to your problem. Add this unit to your project (no further code is needed) and the active form will get the custom messages declared in the units interface. You could destroy the popup menu instance when you see CM_EXITMENULOOP. This solution does not work in earlier versions of Delphi which did not expose the Popuplist to the outside world. In these versions the only solution would be to install a WH_CALLWNDPROC hook (thread specific) when the menu is popped up and remove it again when it gets the WM_EXITMENULOOP message.
unit ExPopupList;
interface
uses
Controls;
const
CM_MENUCLOSED = CM_BASE - 1;
CM_ENTERMENULOOP = CM_BASE - 2;
CM_EXITMENULOOP = CM_BASE - 3;
implementation
uses Messages, Forms, Menus;
type
TExPopupList = class(TPopupList)
protected
procedure WndProc(var Message: TMessage); override;
end;
{ TExPopupList }
procedure TExPopupList.WndProc(var Message: TMessage);
procedure Send(msg: Integer);
begin
if Assigned(Screen.Activeform) then
Screen.ActiveForm.Perform(msg, Message.wparam, Message.lparam);
end;
begin
case message.Msg of
WM_ENTERMENULOOP:
Send(CM_ENTERMENULOOP);
WM_EXITMENULOOP:
Send(CM_EXITMENULOOP);
WM_MENUSELECT:
with TWMMenuSelect(Message) do
if (Menuflag = $FFFF) and (Menu = 0) then
Send(CM_MENUCLOSED);
end;
inherited;
end;
initialization
PopupList.Free;
PopupList := TExPopupList.Create;
{Note: will be freed by Finalization section of Menus unit}
end.
2011. január 28., péntek
How to use WriteBinaryData
Problem/Question/Abstract:
Can someone explain exactly how to use WriteBinaryData? I've tried several different ways but the output is never what it should be.
Answer:
uses
Registry
var
RG: Registry;
PrinterName: string; {This is just a value}
Buffer: array[0..1024] of Char; {This is the Binary data}
J, K: Integer;
dFiles: array[0..5] of string;
{The string data that will be converted to Binary Data}
S, S2: string;
sChar: Char;
procedure Whatever;
begin
{Reset buffer}
for i := 0 to 1024 do
buffer[i] := #00;
{Reset S}
S := '';
dfiles[0]: 'MyPrint.Drv';
dfiles[1]: 'MyPrint.DLL';
dfiles[2]: 'Myunit.DLL';
dfiles[3]: 'MyPrint.HLP';
dfiles[4]: 'MyPrnt2.DLL';
dfiles[5]: 'MyPrnt3.DLL';
{Copy all items into one string seperated by a NULL ##00}
for J := 0 to 5 do
begin
S := S + dFiles[k] + #00; {Copy strings to 1 string and add a #00}
K := Length(S); {K function is to store the string length}
end;
{Convert String to char array called buffer}
for J := 1 to K do
begin
S2 := Copy(S, J, 1); {Copy 1 char to string S2}
sChar := S2[1]; {Copy S2 to sChar}
buffer[J - 1] := sChar; {Copy sChar to buffer}
end;
{The char array MUST end with 2 NULLS}
Buffer[K + 1] := #00;
Printername := 'MyPrinter';
rg := TRegistry.create;
rg.rootkey := hkey_local_machine;
rg.Openkey('System\CurrentControlSet\Control\Print\Environments\Windows4.0\Drivers\'
+ PrinterName, true);
rg.writebinarydata('Dependent Files', buffer, K + 1);
rg.Closekey;
end;
2011. január 27., csütörtök
How to get the scrollbar positon in a RichEdit control
Problem/Question/Abstract:
Is it possible to read the actual position of a scrollbar from a richedit-component?
Answer:
Solve 1:
YES, you can get the position of the scroll bar, by several ways. Here is some code that gets the scroll bar position for a RichEdit. I also subclass the RichRdit to get the WM_VSCROLL message and move the Claret to the top line plus one character
private
{ Private declarations }
PRichEdWndProc, POldWndProc: Pointer;
procedure RichEdWndProc(var Msg: TMessage);
procedure TForm1.FormCreate(Sender: TObject);
begin
{subclass the richedit to get the windows messages}
PRichEdWndProc := MakeObjectInstance(RichEdWndProc);
POldWndProc := Pointer(SetWindowLong(RichEdit1.Handle, GWL_WNDPROC,
Integer(PRichEdWndProc)));
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
{un-sublass it so you app can close}
if Assigned(PRichEdWndProc) then
begin
SetWindowLong(RichEdit1.Handle, GWL_WNDPROC, Integer(POldWndProc));
FreeObjectInstance(PRichEdWndProc);
end;
end;
procedure TForm1.RichEdWndProc(var Msg: TMessage);
begin
Msg.Result := CallWindowProc(POldWndProc, RichEdit1.Handle, Msg.Msg, Msg.WParam,
Msg.LParam);
if (Msg.Msg = WM_VSCROLL) and (LOWORD(Msg.wParam) = SB_THUMBTRACK) then
begin
{the SB_THUMBTRACK message is only sent when the user moves the scroll
bar position}
Label1.Caption := 'thumb Pos is ' + IntToStr(HIWORD(Msg.Wparam));
RichEdit1.SelStart := RichEdit1.Perform(EM_LINEINDEX,
RichEdit1.Perform(EM_GETFIRSTVISIBLELINE, 0, 0), 0) + 1;
end;
end;
procedure TForm1.but_REditInfoClick(Sender: TObject);
var
ScrolPos, VisLineOne: Integer;
ScrollInfo1: TScrollInfo;
begin
{use some windows messages to get scroll position and other info}
VisLineOne := RichEdit1.Perform(EM_GETFIRSTVISIBLELINE, 0, 0);
ScrollInfo1.cbSize := SizeOf(TScrollInfo);
ScrollInfo1.fMask := SIF_RANGE;
GetScrollInfo(RichEdit1.Handle, SB_VERT, ScrollInfo1);
ScrolPos := GetScrollPos(RichEdit1.Handle, SB_VERT);
ShowMessage('ScrolPos is ' + IntToStr(ScrolPos) + ' First Vis Line is ' +
IntToStr(VisLineOne) + ' Scroll max is ' + IntToStr(ScrollInfo1.nMax));
end;
Solve 2:
The most simple way
function GetHorzScrollBarPosition: Integer;
begin
// constants: SB_HORZ = 0, SB_VERT = 1,
// SB_BOTH = 3, SB_THUMBPOSITION = 4
Result := GetScrollPos(RichEdit1.Handle, SB_HORZ);
// Result is the number of pixels the RichEdit is scrolled
end;
2011. január 26., szerda
Make a Word document read-only when opend in a TOleContainer
Problem/Question/Abstract:
How to make a Word document read-only when opend in a TOleContainer
Answer:
You could use the document's Protect method, e.g.:
{ ... }
OleContainer1.DoVerb(ovShow);
OleContainer1.OleObject.Protect(wdAllowOnlyComments, EmptyParam, EmptyParam);
{ ... }
2011. január 25., kedd
How to write a component resource to a file
Problem/Question/Abstract:
I decided to change the way my application saves its form details by using WriteComponentResFile. Basically, my application allows users to create their own forms at run-time (but really just let them change the contents of a panel). Using WriteComponentResFile('Panel.Dfm', MainPanel); doesn't write the buttons or images held by the panel to the DFM file. All that is saved is the panel info itself.
Answer:
The key is to make the controls the user drops on the panel owned by the panel, not the form. Here is an example project to show the principle. Note that you need to register all classes the user can drop so the streaming system knowns how to create them.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Menus, ExtCtrls;
type
TForm1 = class(TForm)
CloseButton: TButton;
Panel1: TPanel;
PopupMenu1: TPopupMenu;
Button2: TMenuItem;
Edit1: TMenuItem;
Label2: TMenuItem;
SaveButton: TButton;
RestoreButton: TButton;
procedure CloseButtonClick(Sender: TObject);
procedure PopupMenuClick(Sender: TObject);
procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure SaveButtonClick(Sender: TObject);
procedure RestoreButtonClick(Sender: TObject);
private
{ Private declarations }
FPopupPosition: TPoint;
function Filename: string;
procedure CustomButtonClick(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.CloseButtonClick(Sender: TObject);
begin
Close;
end;
procedure TForm1.PopupMenuClick(Sender: TObject);
var
ctrl: TControl;
S: string;
i: Integer;
begin
case (Sender as TMenuItem).Tag of
1:
begin
ctrl := TButton.Create(panel1);
TButton(ctrl).OnClick := CustombuttonClick;
end;
2: ctrl := TEdit.Create(Panel1);
3: ctrl := TLabel.Create(Panel1);
else
Exit;
end;
ctrl.Top := FPopupPosition.Y;
ctrl.Left := FPopupPOsition.x;
ctrl.Parent := panel1;
S := ctrl.Classname;
Delete(S, 1, 1);
i := 1;
while panel1.FindComponent(S + IntToStr(i)) <> nil do
Inc(i);
ctrl.Name := S + IntToStr(i);
end;
procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
p: TPoint;
begin
if Button <> mbLeft then
Exit;
FPopupPosition := Point(X, Y);
p := (Sender as TPanel).ClientToScreen(FPopupPosition);
PopupMenu1.Popup(p.x, p.y);
end;
function TForm1.Filename: string;
begin
result := ExtractFilePath(ParamStr(0)) + Name + '.DAT';
end;
procedure TForm1.SaveButtonClick(Sender: TObject);
var
fs: TFileStream;
i: Integer;
begin
fs := TFileStream.Create(filename, fmCreate);
try
fs.WriteComponent(panel1);
finally
fs.free
end;
for i := panel1.ComponentCount - 1 downto 0 do
panel1.Components[i].Free;
end;
procedure TForm1.RestoreButtonClick(Sender: TObject);
var
fs: TFileStream;
i: Integer;
begin
fs := TFileStream.Create(filename, fmOpenread or fmShareDenyWrite);
try
fs.ReadComponent(panel1);
{ Note: this will restore all properties of the read objects,
with the exception of events. Since the event handlers belong to the form,
not the panel, the reader is unable to resolve the method names to
method pointers. So we have to reconnect the event here manually. }
for i := panel1.ComponentCount - 1 downto 0 do
if panel1.Components[i] is TButton then
TButton(panel1.Components[i]).OnClick := CustomButtonClick;
finally
fs.free
end;
end;
procedure TForm1.CustomButtonClick(Sender: TObject);
begin
ShowMessage((Sender as TButton).Name);
end;
initialization
RegisterClasses([TButton, TEdit, TLabel]);
end.
---------------------------------------------------------------------------------------------
object Form1: TForm1
Left = 215
Top = 109
Width = 556
Height = 303
Caption = 'Form1'
Color = clBtnFace
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -15
Font.Name = 'Arial'
Font.Style = []
OldCreateOrder = False
Scaled = False
PixelsPerInch = 120
TextHeight = 17
object CloseButton: TButton
Left = 24
Top = 28
Width = 75
Height = 25
Caption = 'Close'
TabOrder = 0
OnClick = CloseButtonClick
end
object Panel1: TPanel
Left = 112
Top = 24
Width = 421
Height = 237
Caption = 'Click me!'
TabOrder = 1
OnMouseDown = Panel1MouseDown
end
object SaveButton: TButton
Left = 24
Top = 60
Width = 75
Height = 25
Caption = 'Save'
TabOrder = 2
OnClick = SaveButtonClick
end
object RestoreButton: TButton
Left = 24
Top = 92
Width = 75
Height = 25
Caption = 'Restore'
TabOrder = 3
OnClick = RestoreButtonClick
end
object PopupMenu1: TPopupMenu
Left = 112
Top = 4
object Button2: TMenuItem
Tag = 1
Caption = 'Button'
OnClick = PopupMenuClick
end
object Edit1: TMenuItem
Tag = 2
Caption = 'Edit'
OnClick = PopupMenuClick
end
object Label2: TMenuItem
Tag = 3
Caption = 'Label'
OnClick = PopupMenuClick
end
end
end
---------------------------------------------------------------------------------------------
2011. január 24., hétfő
Create an item in a TActionMainMenuBar at runtime
Problem/Question/Abstract:
How to create an item in a TActionMainMenuBar at runtime
Answer:
{ ... }
var
iItem: TActionClientItem;
iAction: TAction;
begin
iAction := TAction.Create(myActionManager); {Create the Action for the item}
iItem := myActionManager.ActionBars[0].Items.Add; {Add item to menu with the index 0}
iAction.Caption := 'My Item';
iItem.Action := iAction; {Assign Action to the item}
end;
To create a submenu item, do:
{ ... }
var
iItem: TActionClientItem;
iAction: TAction;
begin
iAction := TAction.Create(myActionManager);
iItem :=
myActionManager.ActionBars[0].Items[MyFirstMenu].Items[MyFirstSubmenu].Items.Add;
iAction.Caption := 'My item in the submenu';
iItem.Action := iAction;
end;
How to create an item in a TActionMainMenuBar at runtime
Answer:
{ ... }
var
iItem: TActionClientItem;
iAction: TAction;
begin
iAction := TAction.Create(myActionManager); {Create the Action for the item}
iItem := myActionManager.ActionBars[0].Items.Add; {Add item to menu with the index 0}
iAction.Caption := 'My Item';
iItem.Action := iAction; {Assign Action to the item}
end;
To create a submenu item, do:
{ ... }
var
iItem: TActionClientItem;
iAction: TAction;
begin
iAction := TAction.Create(myActionManager);
iItem :=
myActionManager.ActionBars[0].Items[MyFirstMenu].Items[MyFirstSubmenu].Items.Add;
iAction.Caption := 'My item in the submenu';
iItem.Action := iAction;
end;
2011. január 23., vasárnap
How to mirror and rotate bitmaps
Problem/Question/Abstract:
How to mirror and rotate bitmaps
Answer:
{Turn off Range Checking because of ARRAY[0..0] construct below}
{$R-}
unit Bitmap;
interface
uses
Windows, Graphics;
procedure MirrorHorizontal(Bitmap: TBitmap);
procedure MirrorVertical(Bitmap: TBitmap);
procedure Rotate90Degrees(Bitmap: TBitmap);
procedure Rotate270Degrees(Bitmap: TBitmap);
procedure Rotate180Degrees(Bitmap: TBitmap);
implementation
uses
dialogs, Classes, SysUtils;
type
EBitmapError = class(Exception);
TRGBArray = array[0..0] of TRGBTriple;
pRGBArray = ^TRGBArray;
procedure MirrorHorizontal(Bitmap: TBitmap);
var
i, j, w: Integer;
RowIn: pRGBArray;
RowOut: pRGBArray;
begin
w := bitmap.width * sizeof(TRGBTriple);
GetMem(rowin, w);
for j := 0 to Bitmap.Height - 1 do
begin
move(Bitmap.Scanline[j]^, rowin^, w);
rowout := Bitmap.Scanline[j];
for i := 0 to Bitmap.Width - 1 do
rowout[i] := rowin[Bitmap.Width - 1 - i];
end;
bitmap.Assign(bitmap);
FreeMem(rowin);
end;
procedure MirrorVertical(Bitmap: TBitmap);
var
j, w: Integer;
help: TBitmap;
begin
help := TBitmap.Create;
help.Width := Bitmap.Width;
help.Height := Bitmap.Height;
help.PixelFormat := Bitmap.PixelFormat;
w := Bitmap.Width * sizeof(TRGBTriple);
for j := 0 to Bitmap.Height - 1 do
move(Bitmap.Scanline[j]^, Help.Scanline[Bitmap.Height - 1 - j]^, w);
Bitmap.Assign(help);
help.free;
end;
type
THelpRGB = packed record
rgb: TRGBTriple;
dummy: byte;
end;
procedure Rotate270Degrees(Bitmap: TBitmap);
var
aStream: TMemorystream;
header: TBITMAPINFO;
dc: hDC;
P: ^THelpRGB;
x, y, b, h: Integer;
RowOut: pRGBArray;
begin
aStream := TMemoryStream.Create;
aStream.SetSize(Bitmap.Height * Bitmap.Width * 4);
with header.bmiHeader do
begin
biSize := SizeOf(TBITMAPINFOHEADER);
biWidth := Bitmap.Width;
biHeight := Bitmap.Height;
biPlanes := 1;
biBitCount := 32;
biCompression := 0;
biSizeimage := aStream.Size;
biXPelsPerMeter := 1;
biYPelsPerMeter := 1;
biClrUsed := 0;
biClrImportant := 0;
end;
dc := GetDC(0);
P := aStream.Memory;
GetDIBits(dc, Bitmap.Handle, 0, Bitmap.Height, P, header, dib_RGB_Colors);
ReleaseDC(0, dc);
b := bitmap.Height; {rotate}
h := bitmap.Width; {rotate}
bitmap.Width := b;
bitmap.height := h;
for y := 0 to (h - 1) do
begin
rowOut := Bitmap.ScanLine[(h - 1) - y];
P := aStream.Memory; {reset pointer}
inc(p, y);
for x := (b - 1) downto 0 do
begin
rowout[x] := p^.rgb;
inc(p, h);
end;
end;
aStream.Free;
end;
procedure Rotate90Degrees(Bitmap: TBitmap);
var
aStream: TMemorystream;
header: TBITMAPINFO;
dc: hDC;
P: ^THelpRGB;
x, y, b, h: Integer;
RowOut: pRGBArray;
begin
aStream := TMemoryStream.Create;
aStream.SetSize(Bitmap.Height * Bitmap.Width * 4);
with header.bmiHeader do
begin
biSize := SizeOf(TBITMAPINFOHEADER);
biWidth := Bitmap.Width;
biHeight := Bitmap.Height;
biPlanes := 1;
biBitCount := 32;
biCompression := 0;
biSizeimage := aStream.Size;
biXPelsPerMeter := 1;
biYPelsPerMeter := 1;
biClrUsed := 0;
biClrImportant := 0;
end;
dc := GetDC(0);
P := aStream.Memory;
GetDIBits(dc, Bitmap.Handle, 0, Bitmap.Height, P, header, dib_RGB_Colors);
ReleaseDC(0, dc);
b := bitmap.Height; {rotate}
h := bitmap.Width; {rotate}
bitmap.Width := b;
bitmap.height := h;
for y := 0 to (h - 1) do
begin
rowOut := Bitmap.ScanLine[y];
P := aStream.Memory; {reset pointer}
inc(p, y);
for x := 0 to (b - 1) do
begin
rowout[x] := p^.rgb;
inc(p, h);
end;
end;
aStream.Free;
end;
procedure Rotate180Degrees(Bitmap: TBitmap);
var
i, j: Integer;
rowIn: pRGBArray;
rowOut: pRGBArray;
help: TBitmap;
begin
help := TBitmap.Create;
help.Width := Bitmap.Width;
help.Height := Bitmap.Height;
help.PixelFormat := Bitmap.PixelFormat;
< {only pf24bit for now}
for j := 0 to Bitmap.Height - 1 do
begin
rowIn := Bitmap.ScanLine[j];
rowOut := help.ScanLine[Bitmap.Height - j - 1];
for i := 0 to Bitmap.Width - 1 do
rowOut[Bitmap.Width - i - 1] := rowIn[i]
end;
bitmap.assign(help);
help.free;
end;
end.
2011. január 22., szombat
Check if MS SQL Server is reachable
Problem/Question/Abstract:
Can I find programmatically if certain MS SQL Server is reachable on net?
Answer:
Here is function, performing check if MS SQL Server is reachable on net against given server name (usually host on that MS SQL Server is installed), user name and password
function CheckMSSQLServer(fServerName, fUserName, fPsw: string): Bool;
var
wDb: TDatabase;
begin // Check if MS SQL Server is reachable
// Important! BDE Must be installed
Result := False;
wDb := TDatabase.Create(nil);
with wDb do
begin
DatabaseName := 'wDbDatabaseName'; // arbitrary name, must be unique
// in current Session
Params.Values['SERVER Name'] := fServerName;
Params.Values['USER Name'] := fUserName;
Params.Values['PASSWORD'] := fPsw;
LoginPrompt := False;
end;
try
wDb.DriverName := 'MSSQL';
try
wDb.Connected := True;
wDb.Connected := False;
except
ShowMessage('Server is not reachable');
end;
Result := True;
finally
wDb.Free;
end;
end;
2011. január 21., péntek
How to get and set the DPI in a TBitmap
Problem/Question/Abstract:
How to get and set the DPI in a TBitmap
Answer:
I got tired of GetDIBits returning zero's in the BitmapInfoHeader XPelsPerMeter and YPelsPerMeter. The following unit allow you to get/set the dpi to a bitmap file or bitmap image.
As has been discussed previously, a bitmap doesn't really have a dpi as far as the screen is concerned, just a height and width. But the original dpi is indispensable for determining the original size or the scale of the original scan, as is necessary in a program I am working on right now.
The XPelsPerMeter and YPelsPerMeter are stored in 2 bytes each at an offset of 38(26h) and 42(2Ah), I don't know or care which one is at which address. These 2 values are virtually always the same. So in both of my 'Get' functions I just get the one at offset 38. When I set the dpi though, I write both. You can change this accordingly if you like.
There are 39.370079 In/Meter, so, dpi * 39.370079 = dots/meter (PelsPerMeter).
unit MyGraphic;
interface
uses
SysUtils, Classes, Graphics, Dialogs;
function GetBMPFileDPI(FileName: string): LongInt;
procedure SetBMPFileDPI(FileName: string; DPI: Integer);
function GetBmpDPI(Bitmap: TBitmap): LongInt;
procedure SetBmpDPI(Bitmap: TBitmap; DPI: Integer);
implementation
function GetBMPFileDPI(FileName: string): LongInt;
var
Stream: TFileStream;
Data: Word;
A: Double;
begin
try
Result := 0;
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
Stream.Position := 38;
if Stream.Read(Data, 2) = 2 then
begin
A := Data;
Result := Round(A / 39.370079);
end;
finally
Stream.Free;
end;
end;
procedure SetBMPFileDPI(FileName: string; DPI: Integer);
var
Stream: TFileStream;
Data: Word;
begin
try
Stream := TFileStream.Create(FileName, fmOpenWrite or fmShareExclusive);
Data := Round(DPI * 39.370079);
Stream.Position := 38;
if Stream.Write(Data, 2) = 2 then
begin
Stream.Position := 42;
end
else
{Error writing to Stream...}
ShowMessage('Error writing to Stream. Data not written.');
finally
Stream.Free;
end;
end;
function GetBmpDPI(Bitmap: TBitmap): LongInt;
var
Stream: TMemoryStream;
Data: Word;
A: Double;
begin
try
Result := 0;
Stream := TMemoryStream.Create;
Bitmap.SaveToStream(Stream);
Stream.Position := 38;
if Stream.Read(Data, 2) = 2 then
begin
A := Data;
Result := Round(A / 39.370079);
end;
finally
Stream.Free;
end;
end;
procedure SetBmpDPI(Bitmap: TBitmap; DPI: Integer);
var
Stream: TMemoryStream;
Data: Word;
begin
try
Stream := TMemoryStream.Create;
Bitmap.SaveToStream(Stream);
Data := Round(DPI * 39.370079);
Stream.Position := 38;
if Stream.Write(Data, 2) = 2 then
begin
Stream.Position := 42;
if Stream.Write(Data, 2) = 2 then
begin
Stream.Position := 0;
Bitmap.LoadFromStream(Stream);
end
else
{Error writing to Stream...}
ShowMessage('Error writing to Stream. Data not written.');
end
else
{Error writing to Stream...}
ShowMessage('Error writing to Stream. Data not written.');
finally
Stream.Free;
end;
end;
end.
2011. január 20., csütörtök
How to load a frame from a DLL
Problem/Question/Abstract:
I want load a TFrame from a DLL in the main program. I always get the error "No parent window", although I already have the main form's handle.
Answer:
library DLLFrame;
uses
SysUtils, Classes, Controls, Forms, Windows,
DllFrameFrame in 'DllFrameFrame.pas' {Frame1: TFrame};
procedure AddFrame(ApplicationHandle, ParentHandle: THandle); stdcall;
var
Frame1: TFrame1;
AppHandle: THandle;
begin
AppHandle := Application.Handle;
Application.Handle := ApplicationHandle;
Frame1 := TFrame1.Create(Application);
Frame1.ParentWindow := ParentHandle;
SetParent(Frame1.Handle, ParentHandle);
Frame1.Align := alClient;
Frame1.Visible := True;
Application.Handle := AppHandle;
end;
exports
AddFrame;
begin
end.
unit DllFrameFrame;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TFrame1 = class(TFrame)
Label1: TLabel;
Label2: TLabel;
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.
program LoadDLLFrame;
uses
Forms, LoadDLLFrameMn in 'LoadDLLFrameMn.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
unit LoadDLLFrameMn;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Panel1: TPanel;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure AddFrame(ApplicationHandle, ParentHandle: THandle); stdcall;
implementation
{$R *.dfm}
procedure AddFrame(ApplicationHandle, ParentHandle: THandle); stdcall;
external 'DLLFrame.dll';
procedure TForm1.Button1Click(Sender: TObject);
begin
AddFrame(Application.Handle, Panel1.Handle);
end;
end.
2011. január 19., szerda
Error message "bordbk50.dll not registered"
Problem/Question/Abstract:
When starting a debug session under Delphi 5.0, many people receive the error message stating "bordbk50.dll not registered". Borland CBuilder 5 has similar issues with library bordbk51.dll.
Answer:
What is the problem?
“Bordbk” is the debugger kernel (“BORland DeBug Kernel”).
There is a bug in the installation program which fails to register this DLL. Some people believe that this occurs only on systems where a certain registry editor command-line tool is not on the search path – this is not true since I experienced the problem also.
It looks like Borland made a mistake both in their Delphi 5.0 and CBuilder 5 installation scripts.
Solution
The good news is that there is a simple solution. Check on your hard disk for the missing file. The default location is
C:\Program Files\Common Files\Borland Shared\Debugger\bordbk50.dll
Then open a DOS shell, go into that directory and type in this command:
Regsvr32 bordbk50.dll
Regsvr32 is the aforementioned command-line editing tool. It will register the DLL. If regsv32 is not in you path then you will need to prefix it with the full path.
2011. január 18., kedd
A Delphi implementation of the Shellsort algorithm
Problem/Question/Abstract:
A Delphi implementation of the Shellsort algorithm
Answer:
function FirstLow(var A, B: Str64): Boolean;
begin
if A < B then
FirstLow := True
else
FirstLow := false;
end;
procedure BinArraySort(Start, Finish: Integer; var Data: PosAry);
var
StarterKey: Str64;
Temp: PositionRec; {see remark below}
Left: Integer;
Right: Integer;
begin
Left := Start;
Right := Finish;
StarterKey := Data[(Start + Finish) div 2].PBStr;
repeat
while FirstLow(Data[Left].PBStr, StarterKey) do
Left := Left + 1;
while FirstLow(StarterKey, Data[Right].PBStr) do
Right := Right - 1;
if Left <= Right then
begin
Temp := Data[Left];
Data[Left] := Data[Right];
Data[Right] := Temp;
Left := Left + 1;
Right := Right - 1;
end;
until
Right <= Left;
if Start < Right then
BinArraySort(Start, Right, Data);
if Left < Finish then
BinArraySort(Left, Finish, Data);
end;
Remark:
PositionRec = record
PBStr: Str64; {key}
{additional fields}
end;
PosAry = array[1..??] of PositionRec;
PosAryPtr = ^PosAry
is better than just the array itself. Then:
procedure BinArraySort(Start, Finish: Integer; Data: PosAryPtr);
The caller:
var
PA: PosAryPtr;
begin
BinArraySort(1, BItems, PA);
2011. január 17., hétfő
How to stop an application and make it wait until the BDE is installed
Problem/Question/Abstract:
I have a CD-ROM catalog Paradox application. When it is run, it checks if the BDE is installed. If it raises an exception, I do:
ShellExecute(handle, 'open', PChar(ExtractFilePath(Application.ExeName) +
'BDESetup\Setup.exe), '', nil , SW_SW_SHOWMINNOACTIVE);
My problem is, that the application continues to run before the BDE is installed.
Answer:
The following unit contains two functions that might solve your problem.
The two functions defined in the unit below provide two alternative ways to allow an application to call another application and wait for it to exit before continuing. The called application can be a Win32 app, a Win16 app, or a DOS app. To call a batch file or an internal command.com or cmd.exe command, use something like: 'command.com' or 'cmd.exe' as the app, and '/c dir' as the parameter.
If you want the user to see the app's window, then pass SW_SHOW as the Visibility parameter. If you want to hide it, pass SW_HIDE (defined in Windows.pas).
If the called application cannot be run, then the function returns false, and you can use GetLastError to get an error code, and use SysErrorMessage to turn that into a text error message, if necessary.
If the called application runs, then the function returns true. If the called application runs, but signals an abnormal termination by setting its Exit Code to a non-zero value (rare among Windows applications) but common among DOS utilities), then this Exit Code can be seen in the final var parameter ResultingExitCode.
The wait loop includes a Windows message loop which explicitly looks out for a wm_Quit message to allow the calling application to be closed even if the called application hangs.
unit Exec;
{
Author: Bill Sparrow (bsparrow@cix.co.uk)
Revision history in reverse chronological order:-
13/10/1999 WFS Original version, tested only in Delphi 3 on NT4 SP3.
Acknowledgements: the code borrows heaviliy from two contributions
posted on the CIX Conferencing system, one of which in turn borrows
from a Compuserve posting:
magsys@cix.co.uk cix:borland/3delphi32:3488 29/07/1998.
Francis PARLANT CIS : 100113,3015.
jatkins@cix.co.uk cix:borland/6delphi:3540 01/11/1998
}
interface
uses Windows;
function ShellExecAndWait(App, Params: string; Visibility: Integer;
var ResultingExitCode: DWord): Boolean;
function CreateProcAndWait(App, Params: string; Visibility: Word;
var ResultingExitCode: DWord): Boolean;
implementation
uses
shellAPI, {for ShellExecuteEx, TShellExecuteInfo, etc.}
Messages; {for WM_QUIT}
{
Based on a version from jatkins@cix.co.uk cix: borland / 6 delphi: 3540
01 / 11 / 1998
}
{
One advantage of ShellExecuteEx is that it can find the path to the executable without you having to specify it in full, so long as the app has set a registry key under the appropriate App Paths branch.
Another is that instead of passing an application name plus a document filename as a parameter, you can just pass the document name. So long as the document file type has an association, Windows will find the appropriate application to open the document.
ShellExecuteEx is presumably what gets called when you double click a file in Windows Explorer to open it.
Without SEE_MASK_FLAG_NO_UI, if ShellExecuteEx encounters an error, it will display an error dialog to the user before returning False. Furthermore, the text of the error dialog may be an inappropriate level for the user. For instance, if you try to open a document for which there is no association, the error dialog tells the user to set up an association. Turning off the UI allows us to handle the error ourselves and put up an error dialog if appropriate.
}
function ShellExecAndWait(App, Params: string; Visibility: Integer;
var ResultingExitCode: DWord): Boolean;
var
Msg: TMsg;
E: TShellExecuteInfo;
begin
FillChar(E, SizeOf(E), 0); {Superfulous, but what the heck!}
E.cbSize := sizeof(E);
E.fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI;
E.wnd := 0; {Still not sure about leaving this at zero}
E.lpVerb := nil; {Defaults to 'open'}
E.lpFile := PChar(App); {Application or document to open}
E.lpParameters := PChar(Params); {Optional Command line parameter to pass}
E.lpDirectory := nil; {Defaults to current directory}
E.nShow := Visibility; {E.g. SW_SHOW or SW_HIDE}
if ShellExecuteEx(@E) then
begin
repeat
while PeekMessage(Msg, 0, 0, 0, pm_Remove) do
begin
if Msg.Message = wm_Quit then
Halt(Msg.WParam);
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
until
WaitForSingleObject(E.hProcess, 50) <> WAIT_TIMEOUT;
GetExitCodeProcess(E.hProcess, ResultingExitCode);
CloseHandle(E.hProcess); {Prevent leakage}
Result := True; {ShellExecuteEx succeeded}
end
else
begin
ResultingExitCode := 1; {Just so that it is not left undefined}
Result := False; {ShellExecuteEx failed}
end;
end;
{From the Win32 help for CreateProcess...
"The created process remains in the system until all threads within the process have
terminated and all handles to the process and any of its threads have been closed
through calls to CloseHandle.The handles for both the process and the main
thread must be closed through calls to CloseHandle.If these handles are not needed,
it is best to close them immediately after the process is created."
Testing this under NT4 shows a memory leak of 12 K if you don't close the handles.
}
{Based on a version from magsys@cix.co.uk cix:borland/3delphi32:3488
29/07/1998.}
function CreateProcAndWait(App, Params: string; Visibility: Word;
var ResultingExitCode: DWord): Boolean;
var
Msg: TMsg;
SI: TStartupInfo;
PI: TProcessInformation;
CommandLine: string;
begin
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(SI);
SI.dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
SI.wShowWindow := Visibility; {E.g. SW_SHOW or SW_HIDE}
{The first whitespace-delimited 'parameter' in the lpCommandLine needs to be the
app's path and file name if any following 'real' parameters are to be correctly
seen by the called application.
Setting lpApplicationName is optional so long as we comply with the above. If
we did also set lpApplicationName, however, we would have to ensure that the copy in
lpCommandLine was in quotes in case it contains a space. If we leave
lpApplicationName as nil, Windows takes care of this problem for us. Also, if the
called app is 16 bit, we have to do it this way! On second thoughts, relying on
Windows to do the quoting would leave us
open to an ambiguity, so do it explicitly.}
{If the app's filename contains a space, and is not already quoted, then quote it...}
if (Pos(' ', App) <> 0) and (Pos('"', App) = 0) then
CommandLine := '"' + App + '"'
else
CommandLine := App;
{Join the App and the Params into one string with a space between them...}
if (App <> '') and (Params <> '') then
CommandLine := CommandLine + ' ';
CommandLine := CommandLine + Params;
if CreateProcess(nil, PChar(CommandLine), nil, nil, False, 0, nil, nil, SI, PI) then
begin
repeat
while PeekMessage(Msg, 0, 0, 0, pm_Remove) do
begin
if Msg.Message = wm_Quit then
Halt(Msg.WParam);
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
until
WaitForSingleObject(PI.hProcess, 50) <> WAIT_TIMEOUT;
GetExitCodeProcess(PI.hProcess, ResultingExitCode);
CloseHandle(PI.hThread); {Prevent leakage}
CloseHandle(PI.hProcess); {Prevent leakage}
Result := True; {CreateProcess succeeded}
end
else
begin
ResultingExitCode := 1; {Just so that it is not left undefined}
Result := False; {CreateProcess failed}
end;
end;
end.
2011. január 16., vasárnap
How to put an image (e.g sort arrow) on a listview column header
Problem/Question/Abstract:
When sorting TListViews it is good practise to show which column is sorted and in which direction.
Answer:
Add this to your form:
uses commctrl;
procedure TForm1.SetColumnImage(List: TListView; Column, Image: Integer; ShowImage:
Boolean);
var
Align, hHeader: integer;
HD: HD_ITEM;
begin
hHeader := SendMessage(List.Handle, LVM_GETHEADER, 0, 0);
with HD do
begin
case List.Columns[Column].Alignment of
taLeftJustify: Align := HDF_LEFT;
taCenter: Align := HDF_CENTER;
taRightJustify: Align := HDF_RIGHT;
else
Align := HDF_LEFT;
end;
mask := HDI_IMAGE or HDI_FORMAT;
pszText := PChar(List.Columns[Column].Caption);
if ShowImage then
fmt := HDF_STRING or HDF_IMAGE or HDF_BITMAP_ON_RIGHT
else
fmt := HDF_STRING or Align;
iImage := Image
end;
SendMessage(hHeader, HDM_SETITEM, Column, Integer(@HD));
end;
Images are taken from the SmallImages list. You should call this function for each column, and set the ShowImage to TRUE for the column you sorted. You can do this in the OnColumnClick() function:
var
Ascending: boolean;
procedure TForm1.ListViewColumnClick(Sender: TObject; Column: TListColumn);
var
i: integer;
begin
// Toggle column Tag
Column.Tag := 1 - Column.Tag; // 0 -> 1 ; 1 -> 0
// Determine sort order based on the value of the Tag
Ascending := Column.Tag = 1;
// This loop displays the icon in the selected column.
for i := 0 to ListView.Columns.Count - 1 do
SetColumnImage(ListView, i, Column.Tag, i = Column.Index);
// The CustomSort function is not covered in this
// article but is explained elsewhere in Delphi Knowledge Base
TListView(Sender).CustomSort(@SortByColumn, Column.Index);
end;
Problem: Resizing the column header causes a WM_PAINT which will erase the image.
Solution: Override WM_PAINT and call SetColumnImage again from there. I used TApplicationEvents component from delphi 5.
If someone knows a better solution please let me know.
2011. január 15., szombat
Populate a TStringList with file names from a folder
Problem/Question/Abstract:
I am trying to populate a TStringlist with files matching *.pas;*.dfm;*.dpr etc. from the current folder which is easy enough, but I also want to search sub folder for the same file types and keep their folder structure so that I can copy all the files in one go to another folder and then to CD for extra backups.
Answer:
procedure GetFileList(const Path: string; const Extensions: string; FileList:
TStrings);
var
SR: TSearchRec;
begin
if FindFirst(Path + '*.*', faAnyFile, SR) = 0 then
try
repeat
if (SR.Attr and faDirectory) > 0 then
begin
if SR.Name[1] <> '.' then
GetFileList(Path + SR.Name + '\', Extensions, FileList)
end
else if Pos(UpperCase(ExtractFileExt(SR.Name)), Extensions) > 0 then
FileList.Add(Path + SR.Name);
until
FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
Usage:
GetFileList('c:\', '.PAS .FRM .DPR', MyStringList);
2011. január 14., péntek
Change the desktop wallpaper through code
Problem/Question/Abstract:
Does anyone have any idea how to change the wallpaper background in Windows. Browsers can do it, but I have no idea how to do this in Delphi. I need to write an app which changes the background wallpaper on demand.
Answer:
Solve 1:
procedure TfrmWallpaperChanger.ChangeWallPaper(Bitmap: string);
var
pBitmap: pchar;
begin
bitmap := bitmap + #0;
pBitmap := @bitmap[1];
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, pBitmap, SPIF_UPDATEINIFILE);
end;
Solve 2:
procedure TForm1.Button1Click(Sender: TObject);
var
St: array[0..100] of Char;
begin
St := 'C:\Windows\MyWallPaper.bmp';
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, @St, SPIF_SENDCHANGE);
end;
Solve 3:
You can easily change the wallpaper for a Windows 95/ NT system from a Delphi application. Here's the code:
procedure ChangeIt;
var
Reg: TRegIniFile;
begin
Reg := TRegIniFile.Create('Control Panel');
Reg.WriteString('desktop', 'Wallpaper', 'c:\windows\forest.bmp');
Reg.WriteString('desktop', 'TileWallpaper', '1');
Reg.Free;
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, nil, SPIF_SENDWININICHANGE);
end;
That's it! When you execute this procedure, you'll see the wallpaper change to the FOREST.BMP image. (This assumes that you're not using this image already.)
Solve 4:
The code below supports setting the exact position of the wallpaper and the ability to resize the wallpaper to fit the screen
uses
Registry, WinProcs, SysUtils;
const
{WallPaperStyles}
WPS_Tile = 0;
WPS_Center = 1;
WPS_SizeToFit = 2;
WPS_XY = 3;
{sWallpaperBMPPath: Path to a BMP file
nStyle: Any of the above WallPaperStyles
nX, nY: If the nStyle is set to WPS_XY, nX and nY can be used to set the exact
position of the wall paper}
procedure SetWallpaperExt(sWallpaperBMPPath: string; nStyle, nX, nY: integer);
var
reg: TRegIniFile;
s1: string;
X, Y: integer;
begin
{Change registry:
HKEY_CURRENT_USER\
Control Panel\Desktop
TileWallpaper (REG_SZ)
Wallpaper (REG_SZ)
WallpaperStyle (REG_SZ)
WallpaperOriginX (REG_SZ)
WallpaperOriginY (REG_SZ)
}
reg := TRegIniFile.Create('Control Panel\Desktop');
with reg do
begin
s1 := '0';
X := 0;
Y := 0;
case nStyle of
WPS_Tile: s1 := '1';
WPS_Center: nStyle := WPS_Tile;
WPS_XY:
begin
nStyle := WPS_Tile;
X := nX;
Y := nY;
end;
end;
WriteString('', 'Wallpaper', sWallpaperBMPPath);
WriteString('', 'TileWallpaper', s1);
WriteString('', 'WallpaperStyle', IntToStr(nStyle));
WriteString('', 'WallpaperOriginX', IntToStr(X));
WriteString('', 'WallpaperOriginY', IntToStr(Y));
end;
reg.Free;
{Let everyone know that we changed a system parameter}
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, nil, SPIF_SENDWININICHANGE);
end;
Here are two examples on how to call the above SetWallpaperExt() function.
Set wallpaper to winnt.bmp and stretch it to fit the screen
SetWallpaperExt('c:\winnt\winnt.bmp', WPS_SizeToFit, 0, 0);
Set the wallpaper origin to (10, 200)
SetWallpaperExt('c:\winnt\winnt.bmp', WPS_XY, 10, 200);
Solve 5:
{ ... }
type
TThemeWallpaperItem = record
Filename: TFilename;
Pattern: TFilename;
Tile: Boolean;
Style: TWallpaperStyle;
ScreensaverActive: Boolean;
end;
TWallpaperValues = (wvWallpaper, wvPattern, wvTileWallpaper,
wvWallpaperStyle, wvScreensaverActive);
const
ThemeWallpaperSectionName = 'Control Panel\Desktop';
ThemeWallpaperValueNames: array[TWallpaperValues] of string = ('Wallpaper',
'Pattern', 'TileWallpaper', 'WallPaperStyle', 'ScreensaveActive');
procedure TThemeFile.WriteWallpaperToWindows;
var
RegInifile: TRegIniFile;
const
DefaultSectionName = 'Control Panel\Desktop';
begin
RegInifile := TRegIniFile.Create;
try
RegIniFile.RootKey := HKEY_CURRENT_USER;
{TWallpaperValues = (wvWallpaper, wvPattern, wvTileWallpaper, wvWallpaperStyle,
wvScreensaverActive);}
RegIniFile.WriteString(DefaultSectionName, ThemeWallpaperValueNames[wvWallpaper],
FWallpaper.Filename);
RegIniFile.WriteString(DefaultSectionName, ThemeWallpaperValueNames[wvPattern],
FWallpaper.Pattern);
RegIniFile.WriteBool(DefaultSectionName,
ThemeWallpaperValueNames[wvTileWallpaper],
FWallpaper.Tile);
RegIniFile.WriteInteger(DefaultSectionName,
ThemeWallpaperValueNames[wvWallPaperStyle],
Integer(FWallpaper.Style))
finally
RegInifile.Free;
end;
SystemParametersInfo(SPI_SETDESKWALLPAPER, SPI_SETDESKWALLPAPER,
PChar(FWallpaper.Filename), SPIF_SENDCHANGE or
SPIF_UPDATEINIFILE);
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, Ord(FWallPaper.ScreensaverActive),
nil, SPIF_SENDCHANGE or SPIF_UPDATEINIFILE);
RefreshDesktop;
end;
Solve 6:
{ ... }
uses
Registry, JPEG;
type
TWallPaperStyle = (wpsCenter, wpsStretch, wpsTile);
const
WALLPAPERSTYLESTRS: array[TWallPaperStyle] of string = ('Center', 'Stretch',
'Tile');
function StrToWallPaperStyle(const s: string): TWallPaperStyle;
var
wps: TWallPaperStyle;
begin
result := wpsStretch;
for wps := wpsCenter to wpsTile do
if AnsiCompareText(s, WALLPAPERSTYLESTRS[wps]) = 0 then
begin
result := wps;
exit;
end;
end;
function RegGetWallPaperStyle: TWallPaperStyle;
var
Reg: TRegistry;
Center,
Tile: boolean;
s: string;
begin
result := wpsStretch;
Center := false;
Tile := false;
Reg := TRegistry.Create;
try
if Reg.OpenKey('Control Panel\Desktop', false) then
begin
if Reg.ValueExists('TileWallpaper') then
begin
s := Reg.ReadString('TileWallpaper');
Tile := (s = '1');
end;
if Reg.ValueExists('WallpaperStyle') then
begin
s := Reg.ReadString('WallpaperStyle');
Center := (s = '0');
end;
if Tile then
result := wpsTile
else if Center then
result := wpsCenter;
end;
finally
Reg.Free;
end;
end;
procedure RegSetWallPaperStyle(wps: TWallPaperStyle);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
if Reg.OpenKey('Control Panel\Desktop', true) then
begin
case wps of
wpsCenter:
begin
Reg.WriteString('TileWallpaper', '0');
Reg.WriteString('WallpaperStyle', '0');
end;
wpsStretch:
begin
Reg.WriteString('TileWallpaper', '0');
Reg.WriteString('WallpaperStyle', '2');
end;
wpsTile:
begin
Reg.WriteString('TileWallpaper', '1');
{Reg.WriteString('WallpaperStyle', '0');}
end;
end;
end;
finally
Reg.Free;
end;
end;
procedure ChangeWallpaper(lpNewPaper: string; wps: TWallPaperStyle);
var
Reg: TRegistry;
const
SFolderKey = '\Control Panel\Desktop';
begin
if not FileExists(lpNewPaper) or (CompareText('.bmp', ExtractFileExt(lpNewPaper)) <>
0) then
exit;
Reg := TRegistry.Create;
try
if Reg.OpenKey(SFolderKey, false) then
begin
Reg.WriteString('Wallpaper', lpNewPaper);
Reg.CloseKey;
end;
finally
Reg.Free;
end;
RegSetWallPaperStyle(wps);
SystemParametersInfo(20, 0, PChar(lpNewPaper), $2);
end;
procedure SetWallpaper(const AFilename, NewFilename: string; wps:
TWallPaperStyle);
var
IsBitmapFile: boolean;
bmp: TBitmap;
pic: TPicture;
begin
if FileExists(AFilename) then
begin
IsBitmapFile := AnsiCompareText('.bmp', ExtractFileExt(AFilename) = 0;
if not IsBitmapFile then
begin
if (NewFilename = '') or (AnsiCompareText('.bmp', ExtractFileExt(NewFilename)
<> 0) then
raise Exception.Create('Wallpaper must be a bitmap file (*.bmp)');
bmp := TBitmap.Create;
try
pic := TPicture.Create;
try
pic.LoadFromFile(AFilename);
bmp.Assign(pic.Graphic);
finally
pic.Free;
end;
bmp.PixelFormat := pf24bit;
bmp.SaveToFile(NewFilename);
ChangeWallpaper(NewFilename, wps);
finally
bmp.Free;
end;
end
else
ChangeWallpaper(AFilename, wps);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
wps: TWallPaperStyle;
begin
WallpaperCmbx.Clear; {TComboBox, style = csDropDownList, Sorted = false}
for wps := wpsCenter to wpsTile do
WallpaperCmbx.Items.Add(WALLPAPERSTYLESTRS[wps]);
wps := RegGetWallPaperStyle;
WallpaperCmbx.ItemIndex := ord(wps);
end;
procedure TForm1Button1Click(Sender: TObject);
var
F, NewFName: string;
wps: TWallPaperStyle;
begin
with TOpenPictureDialog.Create(Self) do
try
if Execute then
begin
F := Filename;
if AnsiCompareText('.bmp', ExtractFileExt(F)) <> 0 then
NewFName := ChangeFileExt(F, '.bmp')
else
NewFName := '';
wps := TWallPaperStyle(WallPaperCmbx.ItemIndex);
SetWallpaper(F, NewFName, wps);
finally
Free;
end;
end;
2011. január 13., csütörtök
Show the property dialog for a file
Problem/Question/Abstract:
How can I show the property dialog for a file?
Answer:
procedure ShowPropertiesDialog(Filename: string);
var
SEI: TShellExecuteInfo;
begin
FillChar(SEI, SizeOf(SEI), 0);
with SEI do
begin
cbSize := SizeOf(SEI);
lpFile := PChar(Filename);
lpVerb := 'properties';
fMask := SEE_MASK_INVOKEIDLIST;
end;
ShellExecuteEx(@SEI);
end;
2011. január 12., szerda
NT Native API
Problem/Question/Abstract:
NT Native API is basic API in Windows NT/2000. All other API (Win32 is included) are use this API. But functions from ntdll.dll are poorely documented.
Answer:
You can download full source code (ntdll.pas is included):
http://homepages.mtgroup.ru/alexk/files/NativeApp.zip
Unit ntdll.pas contains only definitions for some functions and structures from NT Native API.
program NativeApp;
// PURPOSE: Simple Windows NT/2000 console application that calls
// Native API functions
{$APPTYPE CONSOLE}
uses
Windows, SysUtils, ntdll;
type
TEnumNtObjectCallBack = function(
pusObjectName: PNtUnicodeString;
ObjectTypeName: string;
DirectoryHandle: THandle;
UserData: Pointer
): Boolean; // False => stop
//-------------------------------------------------------------
function NtUnicodeStringToString(pusString: PNtUnicodeString): string;
var
asString: TNtAnsiString;
begin
Result := '';
if (pusString = nil) or (pusString^.Length = 0) then
Exit;
// convert with allocating
RtlUnicodeStringToAnsiString(@asString, pusString, True);
try
SetString(Result, asString.Buffer, asString.Length);
finally
RtlFreeAnsiString(@asString); // free allocated memory
end;
end;
//-------------------------------------------------------------
// Open any named NT object.
// If DirectoryHandle=0 then ObjectName must be full qualified name
// (start with backslash symbol),
// otherwise ObjectName specify relative path from this directory
// You must call CloseHandle to free obtained handle.
function OpenObject(ObjectName: PNtUnicodeString;
DirectoryHandle: THandle;
DesireAccess: ACCESS_MASK): THandle;
var
ObjectAttributes: TNtObjectAttributes;
IoStatus: TIoStatusBlock;
doserr: DWORD;
rc: NTSTATUS;
begin
InitializeObjectAttributes(@ObjectAttributes, ObjectName,
OBJ_CASE_INSENSITIVE, DirectoryHandle, nil);
rc := NtOpenFile(Result, DesireAccess, ObjectAttributes, IoStatus,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, 0);
if rc <> STATUS_SUCCESS then
begin
doserr := RtlNtStatusToDosError(rc);
SetLastError(doserr);
Result := INVALID_HANDLE_VALUE;
end;
end;
//-------------------------------------------------------------
// Open directory and call Treate function for all objects
// in this directory.
function EnumNtObjects(Path: string;
Treate: TEnumNtObjectCallBack;
UserData: Pointer): NTSTATUS;
const
BufferSize = 2048;
var
hDir: THandle;
doserr: DWORD;
DirObject: TNtObjectAttributes;
asDirName: TNtAnsiString;
usDirName: TNtUnicodeString;
cbBytesReturned: DWORD;
dwIndex: DWORD;
Buffer: array[0..BufferSize - 1] of Byte;
DirInfo: TDirectoryInformationTYpe1 absolute Buffer;
FileNameInfo: TFileNameInformation absolute Buffer;
begin
if not Assigned(Treate) or (Path = '') then
begin
Result := STATUS_INVALID_PARAMETER;
Exit;
end;
RtlInitAnsiString(@asDirName, PChar(Path));
RtlAnsiStringToUnicodeString(@usDirName, @asDirName, True);
try
InitializeObjectAttributes(@DirObject, @usDirName,
OBJ_CASE_INSENSITIVE, 0, nil);
hDir := 0;
Result := NtOpenDirectoryObject(hDir,
DIRECTORY_TRAVERSE or DIRECTORY_QUERY, DirObject);
if Result <> STATUS_SUCCESS then
begin
doserr := RtlNtStatusToDosError(Result);
SetLastError(doserr);
end
else
try
dwIndex := 0;
repeat
Result := NtQueryDirectoryObject(hDir,
@Buffer, BufferSize,
TDirectoryInformationClass(1), // ???
False, dwIndex, cbBytesReturned);
if Result <> 0 then
begin
if Result = STATUS_NO_MORE_DATA then
begin
Result := STATUS_SUCCESS;
Break;
end;
doserr := RtlNtStatusToDosError(Result);
SetLastError(doserr);
Break;
end;
until not Treate(@DirInfo.ObjectName,
NtUnicodeStringToString(@DirInfo.ObjectTypeName),
hDir, UserData);
finally
CloseHandle(hDir);
end;
finally
RtlFreeUnicodeString(@usDirName);
end;
end;
//=============================================================
// Sample for EnumNtObjects callback function
type
TUserData = record
DesireObjectType: PChar;
DesireAccess: ACCESS_MASK;
end;
PUserData = ^TUserData;
function EnumNtObjectsCallBack(pusObjectName: PNtUnicodeString;
ObjectTypeName: string;
DirectoryHandle: THandle;
UserData: Pointer): Boolean;
var
sObjectName: string;
hObject: THandle;
begin
Result := True;
sObjectName := NtUnicodeStringToString(pusObjectName);
with PUserData(UserData)^ do
if (DesireObjectType <> '*')
and (CompareText(ObjectTypeName, DesireObjectType) <> 0) then
Exit;
if (CompareText(ObjectTypeName, 'Directory') = 0)
//NtOpenDirectoryObject
or (CompareText(ObjectTypeName, 'Type') = 0)
or (CompareText(ObjectTypeName, 'Port') = 0)
or (CompareText(ObjectTypeName, 'Key') = 0) // NtOpenKey
or (CompareText(ObjectTypeName, 'Event') = 0) // OpenEvent
or (CompareText(ObjectTypeName, 'Semaphore') = 0) // OpenSemaphore
or (CompareText(ObjectTypeName, 'Mutant') = 0) // OpenMutex
or (CompareText(ObjectTypeName, 'Timer') = 0) // NtOpenTimer
or (CompareText(ObjectTypeName, 'Section') = 0) // NtOpenSection
or (CompareText(ObjectTypeName, 'SymbolicLink') = 0)
{// NtOpenSymbolicLinkObject } then
begin
WriteLn(ObjectTypeName, ' ', sObjectName);
Exit;
end;
hObject := OpenObject(pusObjectName, DirectoryHandle,
PUserData(UserData)^.DesireAccess);
if hObject = INVALID_HANDLE_VALUE then
begin
Write(' NtOpenObject failed for ', sObjectName, ': ');
WriteLn(SysErrorMessage(GetLastError));
Exit;
end;
try
WriteLn(ObjectTypeName, ' ', sObjectName,
' is opened successfully');
// do something with object here
finally
CloseHandle(hObject);
end;
end;
//=============================================================
// Application
var
sObjectType, sPath: string;
rUserData: TUserData;
begin
if (ParamCount = 0) or (ParamCount > 2) then
begin
WriteLn('(c) 20 jul 2000 Alex Konshin mailto:alexk@mtgroup.ru');
WriteLn;
WriteLn('Sample console application that use NT Native API.');
WriteLn;
WriteLn('Using:');
WriteLn;
WriteLn(' NativeApp ObjectType [Path]');
WriteLn;
WriteLn('Where:');
WriteLn;
WriteLn(' ObjectType = *(all objects), Directory, Type, Device, Mutant,');
WriteLn(' Section, Semaphore,...');
WriteLn(' (use NativeApp Type \ObjectTypes to list NT object types)');
WriteLn;
WriteLn(' Path = NT objects directory name.');
WriteLn;
WriteLn('Examples:');
WriteLn;
WriteLn(' NativeApp Device \Device');
WriteLn(' NativeApp Mutant \BaseNamedObjects');
WriteLn;
Exit;
end;
sObjectType := ParamStr(1);
sPath := ParamStr(2);
if sPath = '' then
sPath := '\';
with rUserData do
begin
DesireObjectType := PChar(sObjectType);
DesireAccess := FILE_READ_DATA; // GENERIC_READ or GENERIC_WRITE;
end;
EnumNtObjects(sPath, EnumNtObjectsCallBack, @rUserData);
end.
Links for more informations:
http://www.sysinternals.com/ntdll.htm
http://www.sysinternals.com/winobj.htm
Books:
http://www.amazon.com/exec/obidos/ASIN/1578701996/systemsinternals
See also Zw*, Rtl* functions descriptions in Win2000 DDK.
2011. január 11., kedd
Check how many COM ports are available
Problem/Question/Abstract:
How to check how many COM ports are available
Answer:
Solve 1:
function ExtComName(ComNr: DWORD): string;
begin
if ComNr > 9 then
Result := Format('\\\\.\\COM%d', [ComNr])
else
Result := Format('COM%d', [ComNr]);
end;
function CheckCom(AComNumber: Integer): Integer;
var
FHandle: THandle;
begin
Result := 0;
FHandle := CreateFile(PChar(ExtComName(AComNumber)),
GENERIC_READ or GENERIC_WRITE,
0, {exclusive access}
nil, {no security attrs}
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if FHandle <> INVALID_HANDLE_VALUE then
CloseHandle(FHandle)
else
Result := GetLastError;
end;
var
XX, Err: Integer;
for XX := 1 to 20 do
begin
Err := CheckCom(XX);
if (Err = 0) or (Err = ERROR_ACCESS_DENIED) then
{the Port exists, if Err = ERROR_ACCESS_DENIED then the port is already open}
else if (Err = ERROR_FILE_NOT_FOUND) then
{the Port does not exists}
else
{another Error}
end;
Solve 2:
The following bit of code checks both the Comm Ports and the JoyStick Ports, placing them in a combobox. Those which were used were displayed grey and those free were displayed black. A log message was constructed during the enumeration and could be displayed to show what was found. The comm ports are held in two places in the registry and are slightly different for Win9? and NT.
procedure GetCommNames(CommNames: TStringList);
{searches the *PNP0501 and SerialComm entries in the registry fo commport names}
var
Reg: TRegistry;
SerPtSL: TStringList;
i: integer;
CommStr: string;
const
CommPNPKey: string = '\Enum\BIOS\*PNP0501';
HardwareKey: string = '\hardware\devicemap\serialcomm';
begin
{stringlist to hold key or value names during search}
SerPtSL := TStringList.Create;
Reg := TRegistry.Create;
with Reg do
begin
RootKey := HKEY_LOCAL_MACHINE;
LogStr := LogStr + ' HKEY_LOCAL_MACHINE' + #13;
{check PNP entries}
if OpenKey(CommPNPKey, false) then
begin
LogStr := Format('%s %s opened%s', [LogStr, CommPNPKey, #13]);
{get all serial port keys - one key for each interupt used}
GetKeyNames(SerPtSL);
{get the Comm names for all the keys - into CommSL}
for i := 0 to SerPtSL.Count - 1 do
begin
OpenKey(CommPNPKey + '\' + SerPtSL.Strings[i], false);
if GetDataType('PortName') = rdString then
begin
CommNames.Add(ReadString('PortName'));
LogStr := Format('%s %s%s', [LogStr, CommNames.Strings[i], #13]);
end;
end;
end
else
LogStr := LogStr + ' Unable to open ' + CommPNPKey + #13;
SerPtSL.Clear; {to use for hardware value names}
{check the hardware entries}
if OpenKey(HardwareKey, false) then
begin
LogStr := Format('%s %s opened%s', [LogStr, HardwareKey, #13]);
{get the value names for the commports - NT is "Serialn" W95 is "COMn"}
GetValueNames(SerPtSL);
{now get the data value for each commport}
for i := 0 to SerPtSL.Count - 1 do
if GetDataType(SerPtSL.Strings[i]) = rdString then
begin
CommStr := ReadString(SerPtSL.Strings[i]);
LogStr := LogStr + ' ' + CommStr;
{if it's not in CommNames already ...}
if CommNames.IndexOf(CommStr) < 0 then
begin
{... add it}
CommNames.Add(CommStr);
LogStr := LogStr + ' added' + #13;
end
else
LogStr := LogStr + ' already in list' + #13;
end;
end
else
LogStr := Format('%s Unable to open %s', [LogStr, HardwareKey, #13]);
Free; {TRegistry}
end;
SerPtSL.Free;
end;
procedure TForm1.GetComBtnClick(Sender: TObject);
{this is the initiator of the "fill combobox with com ports" action}
var
PortList: TStringList;
begin
LogStr := '';
LogBtn.Enabled := false;
PortList := TStringList.Create;
GetAvailableJoyPort(PortList);
GetAvailableCommPorts(PortList);
with PortComboBox do
begin
{put the stringlist into the combobox}
Items.Assign(PortList);
{select the first available port to show}
ItemIndex := PortComboBox.Items.IndexOfObject(pointer(true));
if Pos('COM', Items[ItemIndex]) > 0 then
EnableDCBBtns(ItemIndex > -1);
Enabled := true;
end;
PortList.Free;
LogBtn.Enabled := true;
end;
procedure TForm1.GetAvailableJoyPort(JoyList: TStringList);
{gets the joystick ports - they are available only if a joystick is plugged in}
var
Res: DWord;
begin
LogStr := 'JoyPort' + #13;
Res := JoySetCapture(Self.Handle, JOYSTICKID1, 0, true);
JoyReleaseCapture(JOYSTICKID1);
case Res of
JOYERR_NOERROR:
begin
JoyList.AddObject('Joystick', pointer(true));
LogStr := LogStr + ' OK : JOYERR_NOERROR' + #13;
end;
JOYERR_PARMS:
LogStr := LogStr + ' Error : JOYERR_PARMS' + #13;
JOYERR_NOCANDO:
LogStr := LogStr + ' Error : JOYERR_NOCANDO' + #13;
JOYERR_UNPLUGGED:
begin
JoyList.AddObject('Joystick', pointer(false));
LogStr := LogStr + ' Eror : JOYERR_UNPLUGGED' + #13;
end;
else
LogStr := Format('%s Unknown Error : %d%s', [LogStr, Res, #13]);
end;
end;
procedure TForm1.GetAvailableCommPorts(ComList: TStringList);
{puts the COM ports into a list. available ports have the stringlist objects
set to a non-nil value. to be available the ports must be a hardware port (in
the registry list comm ports) and have a ProviderSubType of PST_RS232}
var
CommSL: TStringList;
CommName: string;
hComm: THandle;
PtrCommConfig: PCommConfig;
i, CommConfigSize: integer;
Available: boolean;
begin
LogStr := LogStr + 'CommPorts' + #13;
CommSL := TStringList.Create;
GetCommNames(CommSL);
LogStr := Format('%s %d Different CommPorts Found in Registry%s',
[LogStr, CommSL.Count, #13]);
LogStr := Format('%sOpening Ports as a File%s', [LogStr, #13]);
{CommSL now contains the list of commports from the registry}
for i := 0 to CommSL.Count - 1 do
begin
CommName := CommSL.Strings[i]; {Format('COM%d', [i]);}
Available := false;
LogStr := LogStr + ' ' + CommName + ' : ';
{open the port as a file}
hComm := CreateFile(PChar(CommName), GENERIC_READ or GENERIC_WRITE,
0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if hComm <> INVALID_HANDLE_VALUE then
begin
{its a useable COM port - check if its an RS232 type}
CommConfigSize := SizeOf(TCommConfig);
PtrCommConfig := AllocMem(CommConfigSize);
if not GetCommConfig(hComm, PtrCommConfig^, CommConfigSize) then
begin
{not enough memory - get what's needed}
ReAllocMem(PtrCommConfig, CommConfigSize);
GetCommConfig(hComm, PtrCommConfig^, CommConfigSize);
end;
Available := (PtrCommConfig^.dwProviderSubType = PST_RS232);
if Available then
LogStr := LogStr + 'PST_RS232' + #13
else
LogStr := Format('%sdwProviderSubType : %d%s', [LogStr,
PtrCommConfig^.dwProviderSubType, #13]);
FreeMem(PtrCommConfig);
end
else
begin
Available := false;
LogStr := LogStr + ' Not Available - INVALID_HANDLE_VALUE' + #13;
end;
CloseHandle(hComm);
ComList.AddObject(CommName, pointer((Available)))
end;
CommSL.Free;
end;
procedure TForm1.PortComboBoxDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
{draws items in gray if the Items.Objects[n] is nil, in black if it is <> nil}
begin
with PortComboBox do
begin
if not bool(Items.Objects[Index]) then
begin
{item is not available ...}
Canvas.Brush.Color := clWhite; { never indicate as selected}
Canvas.Font.Color := clBtnFace; {grey out text}
end;
{now draw background and text}
Canvas.FillRect(Rect);
Canvas.TextOut(Rect.Left, Rect.Top, Items[Index]);
end;
end;
Solve 3:
This checks for LPT1:
uses
WinSpool;
type
TArrayPORT_INFO_1 = array[0..0] of PORT_INFO_1;
PArrayPORT_INFO_1 = ^TArrayPORT_INFO_1;
procedure LPT1Check();
var
apiBuffer: PArrayPORT_INFO_1;
lwBufferSize: LongWord;
lwPortCount: LongWord;
lwIndex: LongWord;
sMessage: string;
begin
{Find required size of the buffer}
EnumPorts(nil, 1, nil, 0, lwBufferSize, lwPortCount);
{Alloc and fill buffer}
apiBuffer := AllocMem(lwBufferSize);
EnumPorts(nil, 1, apiBuffer, lwBufferSize, lwBufferSize, lwPortCount);
{Search returned buffer}
{Using word so must check for 0 as 0 - 1 = 4294967295 not -1!}
if lwPortCount = 0 then
sMessage := 'No ports installed on this system'
else
begin
sMessage := 'LPT1: not found on this system';
for lwIndex := 0 to lwPortCount - 1 do
begin
if UpperCase(apiBuffer[lwIndex].pName) = 'LPT1:' then
begin
sMessage := 'LPT1: exists';
Break;
end;
end;
end;
{Free the buffer and show result}
FreeMem(apiBuffer);
ShowMessage(sMessage);
end;
2011. január 10., hétfő
Access and control a NT service
Problem/Question/Abstract:
The unit described in this article show how we can start or stop a NT service by programming, or getting the location of its binary implentation.
Answer:
unit SvcUtils;
// Written by Bertrand Goetzmann (http://www.object-everywhere.com)
// Keywords : Service, OpenSCManager, OpenService, CloseServiceHandle, QueryServiceConfig, StartService, QueryServiceStatus, ControlService
interface
// This function returns the entire path location of the implementation of the given name service
function GetBinaryPathName(const ServiceName: string): string;
// This function starts the service with the given service name
procedure StartService(const ServiceName: string);
// This function stops the service with the given service name
procedure StopService(const ServiceName: string);
implementation
uses SysUtils, WinSvc;
function GetBinaryPathName(const ServiceName: string): string;
var
SvcMgr, Svc: Integer;
QuerySvc: TQueryServiceConfig;
BytesNeeded: Cardinal;
Buffer: PQueryServiceConfig;
begin
// Establish a connection to the service control manager
SvcMgr := OpenSCManager(nil {*MachineName*}, nil {*DatabaseName*},
SC_MANAGER_ALL_ACCESS);
try
if SvcMgr = 0 then
RaiseLastOSError;
Svc := OpenService(SvcMgr, PChar(ServiceName), SERVICE_ALL_ACCESS);
if Svc = 0 then
RaiseLastOSError;
try
// Make a call to know the number of bytes needed
QueryServiceConfig(Svc, @QuerySvc, 0, BytesNeeded);
GetMem(Buffer, BytesNeeded);
try
if not QueryServiceConfig(Svc, Buffer, BytesNeeded, BytesNeeded) then
RaiseLastOSError;
Result := PQueryServiceConfig(Buffer).lpBinaryPathName;
finally
FreeMem(Buffer);
end;
finally
CloseServiceHandle(Svc);
end;
finally
CloseServiceHandle(SvcMgr);
end;
end;
procedure StartService(const ServiceName: string);
var
SvcMgr, Svc: Integer;
ServiceArgVectors: PChar;
begin
// Establish a connection to the service control manager
SvcMgr := OpenSCManager(nil {*MachineName*}, nil {*DatabaseName*},
SC_MANAGER_ALL_ACCESS);
try
if SvcMgr = 0 then
RaiseLastOSError;
Svc := OpenService(SvcMgr, PChar(ServiceName), SERVICE_ALL_ACCESS);
if Svc = 0 then
RaiseLastOSError;
try
if not WinSvc.StartService(Svc, 0 {*NumServiceArgs*}, ServiceArgVectors) then
RaiseLastOSError;
finally
CloseServiceHandle(Svc);
end;
finally
CloseServiceHandle(SvcMgr);
end;
end;
procedure StopService(const ServiceName: string);
var
SvcMgr, Svc: Integer;
ServiceStatus: _SERVICE_STATUS;
begin
// Establish a connection to the service control manager
SvcMgr := OpenSCManager(nil {*MachineName*}, nil {*DatabaseName*},
SC_MANAGER_ALL_ACCESS);
try
if SvcMgr = 0 then
RaiseLastOSError;
Svc := OpenService(SvcMgr, PChar(ServiceName), SERVICE_ALL_ACCESS);
if Svc = 0 then
RaiseLastOSError;
try
// if not QueryServiceStatus(Svc, ServiceStatus) then
// RaiseLastOSError;
// You can test the ServiceStatus.dwCurrentState field
if not ControlService(Svc, SERVICE_CONTROL_STOP, ServiceStatus) then
RaiseLastOSError;
finally
CloseServiceHandle(Svc);
end;
finally
CloseServiceHandle(SvcMgr);
end;
end;
end.
FAQ:
As do to register my application as a service?
You can register your service application simply by executing it with the /INSTALL option.
How to pass multi-arguments to StartService?
You can pass some argument with the starting of a service with the call of the StartService function of the Win32 API. The StartService procedure of the SvcUtils unit makes a such call with 0 argument :
WinSvc.StartService(Svc, 0 (*NumServiceArgs*), ServiceArgVectors)
What is RaiseLastOSError?
RaiseLastOSError is a function from the SysUtils unit that permits to raise an exception for the last operating system error or library system error.
I did know how to manage this, but I'm looking for a way to have the list of all installed services. Do you have a solution for that?
I think that the EnumServicesStatus function from the Windows API is the solution.
The function enumerates services in the specified service control manager database. The name and status of each service are provided.
The code looks very nice, but one problem; what is it suposed to do?? Any insight on what this program can do to benefit us would be greatly apprecaited!
On the Windows NT plateform you can look all the installed services by executing MMC (Microsoft Managment Console). All these services can be handled by this GUI application (start, stop, suspend, etc.).
This article show the use of some of the Service Functions from the Window API to do the same things by programming
Feliratkozás:
Bejegyzések (Atom)