I'm trying make a translation of this C++ code that was suggested as possible solution to verify if TESTSIGNING is enabled.
My code almost worked fine, but in this part:
while (status = STATUS_BUFFER_OVERFLOW) or (status = STATUS_INFO_LENGTH_MISMATCH) do
begin
n := Max(br, n * 2);
ReallocMem(Buffer, n * SizeOf(TSystemCodeIntegrityInformation));
Status := NtQuerySystemInformation({SystemCodeIntegrityInformation}103, Buffer, n * SizeOf(TSystemCodeIntegrityInformation), #br);
Writeln('0x'+IntToHex(Status));
end;
where i'm receiving an error:
out of memory
How this can be solved?
Full code:
program test;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
SysUtils,
Math;
type
NTSTATUS = DWORD;
TSystemInformationClass = SYSTEM_INFORMATION_CLASS;
TNativeQuerySystemInformation = function(SystemInformationClass: TSystemInformationClass; SystemInformation: Pointer; SystemInformationLength: ULONG; ReturnLength: PULONG): NTSTATUS; stdcall;
SYSTEM_CODEINTEGRITY_INFORMATION = record
Length: ULONG;
CodeIntegrityOptions: ULONG;
end;
TSystemCodeIntegrityInformation = SYSTEM_CODEINTEGRITY_INFORMATION;
PSystemCodeIntegrityInformation = ^TSystemCodeIntegrityInformation;
const
NTDLL_DLL = 'NTDLL.DLL';
STATUS_SUCCESS = $00000000;
STATUS_BUFFER_OVERFLOW = $80000005;
STATUS_INFO_LENGTH_MISMATCH = $C0000004;
var
NtQuerySystemInformation: TNativeQuerySystemInformation = nil;
NTDLLHandle: THandle = 0;
UnloadNTDLL: Boolean;
Buffer: Pointer;
Status: Cardinal;
psci: PSystemCodeIntegrityInformation;
n, br: Cardinal;
function InitNativeAPI: Boolean;
begin
NTDLLHandle := GetModuleHandle(NTDLL_DLL);
UnloadNTDLL := NTDLLHandle = 0;
if NTDLLHandle = 0 then
NTDLLHandle := LoadLibrary(NTDLL_DLL);
if NTDLLHandle <> 0 then
begin
#NtQuerySystemInformation := GetProcAddress(NTDLLHandle, 'NtQuerySystemInformation');
end;
Result := (NTDLLHandle <> 0) and Assigned(NtQuerySystemInformation);
end;
procedure FreeNativeAPI;
begin
if (NTDLLHandle <> 0) and UnloadNTDLL then
begin
if not FreeLibrary(NTDLLHandle) then
raise Exception.Create(Format('Unload Error: %s - 0x%x', [NTDLL_DLL, GetModuleHandle(NTDLL_DLL)]))
else
NTDLLHandle := 0;
end;
end;
begin
try
Writeln(InitNativeAPI);
Buffer := nil;
n := $100;
Buffer := AllocMem(n * SizeOf(TSystemCodeIntegrityInformation));
Status := NtQuerySystemInformation(SystemCodeIntegrityInformation, Buffer, n * SizeOf(TSystemCodeIntegrityInformation), #br);
while (status = STATUS_BUFFER_OVERFLOW) or (status = STATUS_INFO_LENGTH_MISMATCH) do
begin
n := Max(br, n * 2);
ReallocMem(Buffer, n * SizeOf(TSystemCodeIntegrityInformation));
Status := NtQuerySystemInformation(SystemCodeIntegrityInformation, Buffer, n * SizeOf(TSystemCodeIntegrityInformation), #br);
Writeln('0x'+IntToHex(Status));
end;
try
if Status = STATUS_SUCCESS then
begin
psci := PSystemCodeIntegrityInformation(Buffer);
Writeln(IntToHex(psci.CodeIntegrityOptions));
end;
finally
Reallocmem(Buffer, 0);
Buffer := nil;
end;
FreeNativeAPI;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
You are allocating way more memory than you need. The C++ code allocates only 1 SYSTEM_CODEINTEGRITY_INFORMATION but you are allocating a huge array of them. That is not necessary.
But, more importantly, you are not initializing the SYSTEM_CODEINTEGRITY_INFORMATION.Length field before calling NtQuerySystemInformation(), like the C++ code is doing.
Try this instead:
program test;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
SysUtils,
Math;
type
NTSTATUS = DWORD;
TSystemInformationClass = SYSTEM_INFORMATION_CLASS;
TNativeQuerySystemInformation = function(SystemInformationClass: TSystemInformationClass; SystemInformation: Pointer; SystemInformationLength: ULONG; ReturnLength: PULONG): NTSTATUS; stdcall;
SYSTEM_CODEINTEGRITY_INFORMATION = record
Length: ULONG;
CodeIntegrityOptions: ULONG;
end;
TSystemCodeIntegrityInformation = SYSTEM_CODEINTEGRITY_INFORMATION;
PSystemCodeIntegrityInformation = ^TSystemCodeIntegrityInformation;
const
NTDLL_DLL = 'NTDLL.DLL';
STATUS_SUCCESS = $00000000;
var
NtQuerySystemInformation: TNativeQuerySystemInformation = nil;
NTDLLHandle: THandle = 0;
UnloadNTDLL: Boolean = False;
Status: DWORD;
sci: TSystemCodeIntegrityInformation;
br: ULONG;
function InitNativeAPI: Boolean;
begin
Result := False;
NTDLLHandle := GetModuleHandle(NTDLL_DLL);
if NTDLLHandle = 0 then
begin
NTDLLHandle := LoadLibrary(NTDLL_DLL);
UnloadNTDLL := (NTDLLHandle <> 0);
end;
if NTDLLHandle <> 0 then
begin
#NtQuerySystemInformation := GetProcAddress(NTDLLHandle, 'NtQuerySystemInformation');
Result := Assigned(NtQuerySystemInformation);
end;
end;
procedure FreeNativeAPI;
begin
if (NTDLLHandle <> 0) and UnloadNTDLL then
begin
if not FreeLibrary(NTDLLHandle) then
raise Exception.Create(Format('Unload Error: %s - 0x%x', [NTDLL_DLL, GetModuleHandle(NTDLL_DLL)]);
NTDLLHandle := 0;
end;
end;
begin
try
Writeln(InitNativeAPI);
try
sci.Length := sizeof(sci);
Status := NtQuerySystemInformation(SystemCodeIntegrityInformation, #sci, SizeOf(sci), #br);
Writeln('0x'+IntToHex(Status));
if Status = STATUS_SUCCESS then
begin
Writeln(IntToHex(sci.CodeIntegrityOptions));
end;
finally
FreeNativeAPI;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Related
I am trying to get the % of total CPU usage to a label1.Caption
I've searched and found these:
didn't work - http://www.vbforums.com/showthread.php?345723-DELPHI-Get-CPU-Usage
not what I need - http://delphi.cjcsoft.net/viewthread.php?tid=42837
also found bunch of solutions regarding calculating the Usage per process but that is not what i am looking for , i just want the total CPU usage
like this widget :
this is what i am working on :
I believe there is a simple way like when we get RAM usage.
GlobalMemoryStatus(RamStats);
Label1.Caption := Format('RAM: %d %%', [RamStats.dwMemoryLoad]);
I have found an article, determine-cpu-usage-of-current-process-c-and-c, about how to get the CPU usage of the current process.
Now we need to do a bit more to compute the Total CPU usage percentage by adding up CPU usage percentage for each running processes:
function GetTotalCpuUsagePct(): Double;
var
ProcessID: TProcessID;
RunningProcessIDs : TArray<TProcessID>;
begin
Result := 0.0;
RunningProcessIDs := GetRunningProcessIDs;
DeleteNonExistingProcessIDsFromCache(RunningProcessIDs);
for ProcessID in RunningProcessIDs do
Result := Result + GetProcessCpuUsagePct( ProcessID );
end;
After getting running process id's, we start out calling
DeleteNonExistingProcessIDsFromCache to clean up the cache, that holds previous Cpu usage times needed in GetProcessCpuUsagePct: Every process that has been stopped since last query is removed from this cache.
The GetProcessCpuUsagePct is the core, which is a translation of determine-cpu-usage-of-current-process-c-and-c. This function needs to retrieve the previous reading from the Cpu Usage Cache LatestProcessCpuUsageCache (global in the unit) using the ProcessID.
Note, it is not recommended to call GetToalCpuUsageCpu less than every 200 ms, as it may give wrong results.
function GetProcessCpuUsagePct(ProcessID: TProcessID): Double;
function SubtractFileTime(FileTime1: TFileTIme; FileTime2: TFileTIme): TFileTIme;
begin
Result := TFileTIme(Int64(FileTime1) - Int64(FileTime2));
end;
var
ProcessCpuUsage: TProcessCpuUsage;
ProcessHandle: THandle;
SystemTimes: TSystemTimesRec;
SystemDiffTimes: TSystemTimesRec;
ProcessDiffTimes: TProcessTimesRec;
ProcessTimes: TProcessTimesRec;
SystemTimesIdleTime: TFileTime;
ProcessTimesCreationTime: TFileTime;
ProcessTimesExitTime: TFileTime;
begin
Result := 0.0;
LatestProcessCpuUsageCache.TryGetValue(ProcessID, ProcessCpuUsage);
if ProcessCpuUsage = nil then
begin
ProcessCpuUsage := TProcessCpuUsage.Create;
LatestProcessCpuUsageCache.Add(ProcessID, ProcessCpuUsage);
end;
// method from:
// http://www.philosophicalgeek.com/2009/01/03/determine-cpu-usage-of-current-process-c-and-c/
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, ProcessID);
if ProcessHandle <> 0 then
begin
try
if GetSystemTimes(SystemTimesIdleTime, SystemTimes.KernelTime, SystemTimes.UserTime) then
begin
SystemDiffTimes.KernelTime := SubtractFileTime(SystemTimes.KernelTime, ProcessCpuUsage.LastSystemTimes.KernelTime);
SystemDiffTimes.UserTime := SubtractFileTime(SystemTimes.UserTime, ProcessCpuUsage.LastSystemTimes.UserTime);
ProcessCpuUsage.LastSystemTimes := SystemTimes;
if GetProcessTimes(ProcessHandle, ProcessTimesCreationTime, ProcessTimesExitTime, ProcessTimes.KernelTime, ProcessTimes.UserTime) then
begin
ProcessDiffTimes.KernelTime := SubtractFileTime(ProcessTimes.KernelTime, ProcessCpuUsage.LastProcessTimes.KernelTime);
ProcessDiffTimes.UserTime := SubtractFileTime(ProcessTimes.UserTime, ProcessCpuUsage.LastProcessTimes.UserTime);
ProcessCpuUsage.LastProcessTimes := ProcessTimes;
if (Int64(SystemDiffTimes.KernelTime) + Int64(SystemDiffTimes.UserTime)) > 0 then
Result := (Int64(ProcessDiffTimes.KernelTime) + Int64(ProcessDiffTimes.UserTime)) / (Int64(SystemDiffTimes.KernelTime) + Int64(SystemDiffTimes.UserTime)) * 100;
end;
end;
finally
CloseHandle(ProcessHandle);
end;
end;
end;
Here is a screen shot of the result on a Windows 7.
Full Listing of unit:
unit uTotalCpuUsagePct;
interface
function GetTotalCpuUsagePct : Double;
implementation
uses
SysUtils, DateUtils, Windows, PsAPI, TlHelp32, ShellAPI, Generics.Collections;
type
TProcessID = DWORD;
TSystemTimesRec = record
KernelTime: TFileTIme;
UserTime: TFileTIme;
end;
TProcessTimesRec = record
KernelTime: TFileTIme;
UserTime: TFileTIme;
end;
TProcessCpuUsage = class
LastSystemTimes: TSystemTimesRec;
LastProcessTimes: TProcessTimesRec;
ProcessCPUusagePercentage: Double;
end;
TProcessCpuUsageList = TObjectDictionary<TProcessID, TProcessCpuUsage>;
var
LatestProcessCpuUsageCache : TProcessCpuUsageList;
LastQueryTime : TDateTime;
(* -------------------------------------------------------------------------- *)
function GetRunningProcessIDs: TArray<TProcessID>;
var
SnapProcHandle: THandle;
ProcEntry: TProcessEntry32;
NextProc: Boolean;
begin
SnapProcHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if SnapProcHandle <> INVALID_HANDLE_VALUE then
begin
try
ProcEntry.dwSize := SizeOf(ProcEntry);
NextProc := Process32First(SnapProcHandle, ProcEntry);
while NextProc do
begin
SetLength(Result, Length(Result) + 1);
Result[Length(Result) - 1] := ProcEntry.th32ProcessID;
NextProc := Process32Next(SnapProcHandle, ProcEntry);
end;
finally
CloseHandle(SnapProcHandle);
end;
TArray.Sort<TProcessID>(Result);
end;
end;
(* -------------------------------------------------------------------------- *)
function GetProcessCpuUsagePct(ProcessID: TProcessID): Double;
function SubtractFileTime(FileTime1: TFileTIme; FileTime2: TFileTIme): TFileTIme;
begin
Result := TFileTIme(Int64(FileTime1) - Int64(FileTime2));
end;
var
ProcessCpuUsage: TProcessCpuUsage;
ProcessHandle: THandle;
SystemTimes: TSystemTimesRec;
SystemDiffTimes: TSystemTimesRec;
ProcessDiffTimes: TProcessTimesRec;
ProcessTimes: TProcessTimesRec;
SystemTimesIdleTime: TFileTime;
ProcessTimesCreationTime: TFileTime;
ProcessTimesExitTime: TFileTime;
begin
Result := 0.0;
LatestProcessCpuUsageCache.TryGetValue(ProcessID, ProcessCpuUsage);
if ProcessCpuUsage = nil then
begin
ProcessCpuUsage := TProcessCpuUsage.Create;
LatestProcessCpuUsageCache.Add(ProcessID, ProcessCpuUsage);
end;
// method from:
// http://www.philosophicalgeek.com/2009/01/03/determine-cpu-usage-of-current-process-c-and-c/
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, ProcessID);
if ProcessHandle <> 0 then
begin
try
if GetSystemTimes(SystemTimesIdleTime, SystemTimes.KernelTime, SystemTimes.UserTime) then
begin
SystemDiffTimes.KernelTime := SubtractFileTime(SystemTimes.KernelTime, ProcessCpuUsage.LastSystemTimes.KernelTime);
SystemDiffTimes.UserTime := SubtractFileTime(SystemTimes.UserTime, ProcessCpuUsage.LastSystemTimes.UserTime);
ProcessCpuUsage.LastSystemTimes := SystemTimes;
if GetProcessTimes(ProcessHandle, ProcessTimesCreationTime, ProcessTimesExitTime, ProcessTimes.KernelTime, ProcessTimes.UserTime) then
begin
ProcessDiffTimes.KernelTime := SubtractFileTime(ProcessTimes.KernelTime, ProcessCpuUsage.LastProcessTimes.KernelTime);
ProcessDiffTimes.UserTime := SubtractFileTime(ProcessTimes.UserTime, ProcessCpuUsage.LastProcessTimes.UserTime);
ProcessCpuUsage.LastProcessTimes := ProcessTimes;
if (Int64(SystemDiffTimes.KernelTime) + Int64(SystemDiffTimes.UserTime)) > 0 then
Result := (Int64(ProcessDiffTimes.KernelTime) + Int64(ProcessDiffTimes.UserTime)) / (Int64(SystemDiffTimes.KernelTime) + Int64(SystemDiffTimes.UserTime)) * 100;
end;
end;
finally
CloseHandle(ProcessHandle);
end;
end;
end;
(* -------------------------------------------------------------------------- *)
procedure DeleteNonExistingProcessIDsFromCache(const RunningProcessIDs : TArray<TProcessID>);
var
FoundKeyIdx: Integer;
Keys: TArray<TProcessID>;
n: Integer;
begin
Keys := LatestProcessCpuUsageCache.Keys.ToArray;
for n := Low(Keys) to High(Keys) do
begin
if not TArray.BinarySearch<TProcessID>(RunningProcessIDs, Keys[n], FoundKeyIdx) then
LatestProcessCpuUsageCache.Remove(Keys[n]);
end;
end;
(* -------------------------------------------------------------------------- *)
function GetTotalCpuUsagePct(): Double;
var
ProcessID: TProcessID;
RunningProcessIDs : TArray<TProcessID>;
begin
Result := 0.0;
RunningProcessIDs := GetRunningProcessIDs;
DeleteNonExistingProcessIDsFromCache(RunningProcessIDs);
for ProcessID in RunningProcessIDs do
Result := Result + GetProcessCpuUsagePct( ProcessID );
end;
(* -------------------------------------------------------------------------- *)
initialization
LatestProcessCpuUsageCache := TProcessCpuUsageList.Create( [ doOwnsValues ] );
// init:
GetTotalCpuUsagePct;
finalization
LatestProcessCpuUsageCache.Free;
end.
Test Code:
unit Unit1;
interface
uses
Vcl.Forms, System.SysUtils, Vcl.Controls, Vcl.StdCtrls, System.Classes,
Vcl.ExtCtrls,
uTotalCpuUsagePct;
type
TForm1 = class(TForm)
Timer1: TTimer;
Label1: TLabel;
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// start cpu load thread
TThread.CreateAnonymousThread(
procedure
begin
while True do
begin
end;
end).Start;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
TotalCPUusagePercentage: Double;
begin
TotalCPUusagePercentage := GetTotalCpuUsagePct();
Label1.Caption := 'Total cpu: ' + IntToStr(Round(TotalCPUusagePercentage)) + '%';
end;
end.
You can achieve your goal using the Performance Counters Functions from Microsoft.
Limited User Access Support
Only the administrator of the computer or users in the Performance Logs User Group can log and view counter data. Users in the Administrator group can log and view counter data only if the tool they use to log and view counter data is started from a Command Prompt window that is opened with Run as administrator.... Users in the Performance Monitoring Users group can view counter data.
I have found this answer - see CPU currently used - from the Lanzelot user here on SO and I have done some porting to Delphi.
Raw porting:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
pdh in 'pdh.pas';
var
cpuQuery: HQUERY;
cpuTotal: HCOUNTER;
i: Integer;
procedure init;
begin
PdhOpenQuery(nil, 0, cpuQuery);
PdhAddCounter(cpuQuery, '\Processor(_Total)\% Processor Time', 0, cpuTotal);
PdhCollectQueryData(cpuQuery);
end;
function getCurrentValue: Double;
var
counterVal: TPdhFmtCounterValue;
begin
PdhCollectQueryData(cpuQuery);
PdhGetFormattedCounterValue(cpuTotal, PDH_FMT_DOUBLE, nil, counterVal);
Result := counterVal.doubleValue;
end;
The example requires the pdh unit which I have grabbed from here.
The WinPerf unit is needed by the pdh and I have downloaded it from here.
Basic test in a console application:
begin
init;
for i := 1 to 60 do begin
//let's monitor the CPU usage for one minute
WriteLn(getCurrentValue);
Sleep(1000);
end;
PdhCloseQuery(cpuQuery);
end.
A more useful example based on the TThread class.
This allows to obtain different counters based on the parameter passed to the ACounterPath argument in the constructor.
counterThread.pas
unit counterThread;
interface
uses
Classes, Windows, SyncObjs, pdh;
type
TCounterNotifyEvent = procedure(AValue: Double) of object;
TCounterThread = class(TThread)
private
FInterval: Integer;
FWaitEvent: TEvent;
FHQuery: HQUERY;
FHCounter: HCOUNTER;
procedure checkSuccess(AResult: Integer);
protected
procedure Execute; override;
procedure TerminatedSet; override;
public
OnCounter: TCounterNotifyEvent;
constructor Create(const ACounterPath: PChar; AInterval: Cardinal; ACreateSuspended: Boolean);
destructor Destroy; override;
end;
implementation
uses
SysUtils;
procedure TCounterThread.checkSuccess(AResult: Integer);
begin
if ERROR_SUCCESS <> AResult then
RaiseLastOSError;
end;
constructor TCounterThread.Create(const ACounterPath: PChar; AInterval: Cardinal; ACreateSuspended: Boolean);
begin
inherited Create(ACreateSuspended);
FInterval := AInterval;
FWaitEvent := TEvent.Create(nil, False, False, '');
FHQuery := INVALID_HANDLE_VALUE;
checkSuccess(PdhOpenQuery(nil, 0, FHQuery));
checkSuccess(PdhAddCounter(FHQuery, ACounterPath, 0, FHCounter));
//checkSuccess(PdhAddEnglishCounter(FHQuery, ACounterPath, 0, FHCounter));
checkSuccess(PdhCollectQueryData(FHQuery));
end;
destructor TCounterThread.Destroy;
begin
FWaitEvent.Free;
if (FHQuery <> 0) and (FHQuery <> INVALID_HANDLE_VALUE) then
PdhCloseQuery(FHQuery);
inherited;
end;
procedure TCounterThread.TerminatedSet;
begin
inherited;
FWaitEvent.SetEvent;
end;
procedure TCounterThread.Execute;
var
counterVal: TPdhFmtCounterValue;
begin
inherited;
while not Terminated do begin
checkSuccess(PdhCollectQueryData(FHQuery));
FillChar(counterVal, SizeOf(TPdhFmtCounterValue), 0);
checkSuccess(PdhGetFormattedCounterValue(FHCounter, PDH_FMT_DOUBLE, nil, counterVal));
if Assigned(OnCounter) then
OnCounter(counterVal.doubleValue);
FWaitEvent.WaitFor(FInterval);
end;
end;
end.
Unit1.pas
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,
counterThread;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FCpuCounter: TCounterThread;
procedure CpuCounterCounter(AValue: Double);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
FCpuCounter := TCounterThread.Create('\Processor(_Total)\% Processor Time', 1000, False);
//'\Processore(_Total)\% Tempo Processore'
with FCpuCounter do begin
FreeOnTerminate := True;
OnCounter := CpuCounterCounter;
end;
Button1.Enabled := False;
end;
procedure TForm1.CpuCounterCounter(AValue: Double);
begin
Edit1.Text := FloatToStr(AValue);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(FCpuCounter) then
FCpuCounter.Terminate;
end;
end.
Unit1.dfm
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 123
ClientWidth = 239
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 8
Top = 24
Width = 97
Height = 13
Caption = 'Total CPU usage %:'
end
object Edit1: TEdit
Left = 111
Top = 21
Width = 99
Height = 21
TabOrder = 0
end
object Button1: TButton
Left = 111
Top = 80
Width = 99
Height = 25
Caption = 'Start monitoring'
TabOrder = 1
OnClick = Button1Click
end
end
OFF TOPIC
I'm currently at home and I've not a Delphi XE here so I coded it with Turbo Delphi, I have no pdh unit installed on my machine and I can't know at the moment if Delphi XE has the units.
NOTICE
I have used the PdhAddCounter function instead of the PdhAddEnglishCounter because the function reference is missing in the unit. Unfortunately, after I added the reference, the function was still missing in the Pdh.dll on my old Windows XP.
The szFullCounterPath of the PdhAddCounter is localized so I have to use the italian localized path on my Windows \Processore(_Total)\% Tempo Processore.
If you use the PdhAddEnglishCounter function or your locale is english, you have to use the path \Processor(_Total)\% Processor Time.
If your system locale is other than english or italian, you have to find the path by yourself using the PdhBrowseCounters function.
The very basic function usage which follows needs the PdhMsg unit.
See also MSDN Browsing Performance Counters for further reference.
function CounterPathCallBack(dwArg: DWORD_PTR): Longint; stdcall;
begin
Form1.Memo1.Lines.Add(PChar(dwArg));
Result := ERROR_SUCCESS;
end;
procedure TForm1.Button2Click(Sender: TObject);
const
PDH_MAX_COUNTER_PATH = 255;//maybe ?
BROWSE_DIALOG_CAPTION: PChar = 'Select a counter to monitor.';
var
browseDlgData: TPdhBrowseDlgConfig;
counterPathBuffer: array [0..PDH_MAX_COUNTER_PATH-1] of Char;
status: LongInt;
begin
FillChar(browseDlgData, SizeOf(TPdhBrowseDlgConfig), 0);
with browseDlgData do begin
{bIncludeInstanceIndex = FALSE;
bSingleCounterPerAdd = TRUE;
bSingleCounterPerDialog = TRUE;
bLocalCountersOnly = FALSE;
bWildCardInstances = TRUE;
bHideDetailBox = TRUE;
bInitializePath = FALSE;
bDisableMachineSelection = FALSE;
bIncludeCostlyObjects = FALSE;
bShowObjectBrowser = FALSE;}
hWndOwner := Self.Handle;
szReturnPathBuffer := #counterPathBuffer[0];
cchReturnPathLength := PDH_MAX_COUNTER_PATH;
pCallBack := CounterPathCallBack;
dwCallBackArg := DWORD_PTR(#counterPathBuffer[0]);
CallBackStatus := ERROR_SUCCESS;
dwDefaultDetailLevel := PERF_DETAIL_WIZARD;
szDialogBoxCaption := BROWSE_DIALOG_CAPTION;
end;
status := PdhBrowseCounters(browseDlgData);
case status of
PDH_DIALOG_CANCELLED, ERROR_SUCCESS:
;
else
RaiseLastOSError;
end;
end;
http://www.magsys.co.uk/delphi/
Get the MagWMI component. It's free.
This component will allow you to access the WMI pretty easily which already has the info you want. I just tested an old program I had using this on Win 10 and it correctly found all 8 of my cores and the processor usage.
And then do something like this:
var
compname:string;
WmiResults: T2DimStrArray ;
instances, i : Integer
Begin
compname:=getcompname; // a function in the MagWMI to get the computer name.
MagWmiGetInfoEx (compname, '', '',
'', 'SELECT percentidletime FROM Win32_PerfFormattedData_PerfOS_Processor', WmiResults, instances, errstr) ;
for i := 1 to instances do
begin
// wmiresults[i,2] will hold the percentage for each processor found.
end;
I solve this way:
function TCPU.get_param_value(param_name: String): String;
var
command,
file_out: String;
data_file: TStringList;
begin
data_file := TStringList.Create;
try
try
file_out := TPath.GetTempPath + FormatDateTime('yyyymmddhhnnss', Now) + '_CPUInfo.txt';
comando := '"wmic cpu get '+param_name+' /value | find "'+param_name+'" > ' +
file_out + '&&exit"';
// "runas" for admin privileges, or "open" to any user
ShellExecute(0, 'open', 'cmd.exe', PChar('/k ' + command), nil, SW_HIDE);
// Wait 4 sec to cmd release the process...
Sleep(4000);
data_file.LoadFromFile(file_out);
Result := data_file.Values[param_name];
except
Result := '';
end;
finally
TFile.Delete(file_out);
data_file.Free;
end;
In this way, you can get any param values from wmic
I found t h i s
does the job
uses adCpuUsage;
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
u:string;
begin
collectcpudata;
for i:=0 to GetCPUCount-1 do
u:=FloatToStr(Round(getcpuusage(i)*100)); //Round to approximate 1.0003 to 1
label1.Caption:=u
end;
end.
worked for me
While querying the WlanQueryInterface to get the channel number see that the return value is a large integer like 1820789 etc.. any help?
uses nduWlanTypes, nduWlanAPI;
Function GetWifiChannelTest: String;
var
hClient: THandle;
dwVersion: DWORD;
ResultInt: DWORD;
pInterface: Pndu_WLAN_INTERFACE_INFO_LIST;
i: Integer;
pInterfaceGuid: TGUID;
pdwDataSize: DWORD;
ppData: Tndu_WLAN_INTF_OPCODE;
pI: Pinteger;
p: pvoid;
begin
ResultInt := WlanOpenHandle(1, nil, #dwVersion, #hClient);
try
if ResultInt <> ERROR_SUCCESS then
begin
ShowMessage('Error Open CLient' + IntToStr(ResultInt));
Exit;
end;
ResultInt := WlanEnumInterfaces(hClient, nil, #pInterface);
if ResultInt <> ERROR_SUCCESS then
begin
ShowMessage('Error Enum Interfaces ' + IntToStr(ResultInt));
Exit;
end;
for i := 0 to pInterface^.dwNumberOfItems - 1 do
begin
pInterfaceGuid := pInterface^.InterfaceInfo[pInterface^.dwIndex]
.InterfaceGuid;
ResultInt := WlanQueryInterface(hClient, #pInterfaceGuid,
wlan_intf_opcode_channel_number, nil, #pdwDataSize, #ppData, nil);
try
if (ResultInt = ERROR_SUCCESS) and (pdwDataSize = SizeOf(ppData)) then
begin
p := #ppData;
pI := pvoid(p);
Result := IntToStr(pI^);
// the result is 1820789 ,but i need channel number like 10, or 11 etc...
end;
except
end;
end;
finally
WlanCloseHandle(hClient, nil);
end;
end;
You are not enumerating interfaces correctly. You should not be using dwIndex as the index into the InterfaceInfo[] array. Use your loop counter i instead.
Also, you are not calling WlanQueryInterface() correctly. wlan_intf_opcode_channel_number outputs a ULONG value, not a WLAN_INTF_OPCODE value.
Also, you are leaking the WLAN_INTERFACE_INFO_LIST that WlanEnumInterfaces() allocates.
Try something more like this instead:
uses
nduWlanTypes, nduWlanAPI;
Function GetWifiChannelTest: String;
var
hClient: THandle;
dwVersion: DWORD;
ResultInt: DWORD;
pIntfList: PWLAN_INTERFACE_INFO_LIST;
i: DWORD;
IntfGuid: TGUID;
dwDataSize: DWORD;
ChannelNumber: ULONG;
begin
Result := '';
ResultInt := WlanOpenHandle(1, nil, #dwVersion, #hClient);
if ResultInt <> ERROR_SUCCESS then
begin
ShowMessage('Error Open Client: ' + IntToStr(ResultInt));
Exit;
end;
try
ResultInt := WlanEnumInterfaces(hClient, nil, #pIntfList);
if ResultInt <> ERROR_SUCCESS then
begin
ShowMessage('Error Enumerating Interfaces: ' + IntToStr(ResultInt));
Exit;
end;
try
for i := 0 to pIntfList^.dwNumberOfItems - 1 do
begin
IntfGuid := pIntfList^.InterfaceInfo[i].InterfaceGuid;
ResultInt := WlanQueryInterface(hClient, #IntfGuid, wlan_intf_opcode_channel_number, nil, #dwDataSize, #ChannelNumber, nil);
if ResultInt = ERROR_SUCCESS then
begin
Result := IntToStr(ChannelNumber);
Exit;
end;
end;
finally
WlanFreeMemory(pIntfList);
end;
finally
WlanCloseHandle(hClient, nil);
end;
end;
UPDATE: after further review, it seems that wlan_intf_opcode_channel_number might be outputting a pointer to a ULONG, so you would need to give it a pointer to a pointer to a ULONG, and then you can dereference the outputted pointer to get the actual ULONG. Try this:
uses
nduWlanTypes, nduWlanAPI;
Function GetWifiChannelTest: String;
var
hClient: THandle;
dwVersion: DWORD;
ResultInt: DWORD;
pIntfList: PWLAN_INTERFACE_INFO_LIST;
i: DWORD;
IntfGuid: TGUID;
dwDataSize: DWORD;
pChannelNumber: PULONG; // <--
begin
Result := '';
ResultInt := WlanOpenHandle(1, nil, #dwVersion, #hClient);
if ResultInt <> ERROR_SUCCESS then
begin
ShowMessage('Error Open Client: ' + IntToStr(ResultInt));
Exit;
end;
try
ResultInt := WlanEnumInterfaces(hClient, nil, #pIntfList);
if ResultInt <> ERROR_SUCCESS then
begin
ShowMessage('Error Enumerating Interfaces: ' + IntToStr(ResultInt));
Exit;
end;
try
for i := 0 to pIntfList^.dwNumberOfItems - 1 do
begin
IntfGuid := pIntfList^.InterfaceInfo[i].InterfaceGuid;
ResultInt := WlanQueryInterface(hClient, #IntfGuid, wlan_intf_opcode_channel_number, nil, #dwDataSize, #pChannelNumber, nil);
if ResultInt = ERROR_SUCCESS then
begin
Result := IntToStr(pChannelNumber^); // <--
Exit;
end;
end;
finally
WlanFreeMemory(pIntfList);
end;
finally
WlanCloseHandle(hClient, nil);
end;
end;
i am using indy TidTcpserver inside my server application its working good but some times after 10 clients connected my server application got a deadlock and stop from response here is my server execute and broadcast protocol codes
Tcp server execute
procedure TfMain.TCPServerExecute(AContext: TIdContext);
var
Connection: TConnection;
Command: String;
Params: array[1..10] of String;
ParamsCount, P: Integer;
PackedParams: TPackedParams;
IdBytes: TIdBytes;
MS: TMemoryStream;
ReceiveParams, ReceiveStream: Boolean;
Size: Int64;
begin
Connection := Pointer(AContext.Data);
MS := TMemoryStream.Create;
ReceiveParams := False;
ReceiveStream := False;
Command := AContext.Connection.Socket.ReadLn; //read command
if Command[1] = '1' then //command with params
begin
Command := Copy(Command, 2, Length(Command));
ReceiveParams := True;
end
else if Command[1] = '2' then //command + memorystream
begin
Command := Copy(Command, 2, Length(Command));
ReceiveStream := True;
MS.Position := 0;
end
else if Command[1] = '3' then //command with params + memorystream
begin
Command := Copy(Command, 2, Length(Command));
ReceiveParams := True;
ReceiveStream := True;
end;
if ReceiveParams then //params is incomming
begin
AContext.Connection.Socket.ReadBytes(IdBytes, SizeOf(PackedParams), False);
BytesToRaw(IdBytes, PackedParams, SizeOf(PackedParams));
ParamsCount := 0;
repeat
Inc(ParamsCount);
p := Pos(Sep, String(PackedParams.Params));
Params[ParamsCount] := Copy(String(PackedParams.Params), 1, P - 1);
Delete(PackedParams.Params, 1, P + 4);
until PackedParams.Params = '';
end;
if ReceiveStream then //stream is incomming
begin
Size := AContext.Connection.Socket.ReadInt64;
AContext.Connection.Socket.ReadStream(MS, Size, False);
MS.Position := 0;
end;
if Command = 'LOGIN' then
begin
usrnm := Params[1];
passwd := params[2];
if not userexists(usrnm, passwd) then
AContext.Connection.Socket.WriteLn('INVALIDPASSWORD')
else
begin
userslq.Close;
userslq.SQL.Clear;
userslq.SQL.Add('SELECT * FROM `users` WHERE `username` = "'+ trim(usrnm) +'" AND `password` = "' + trim(passwd) + '"');
userslq.Open;
if NOT userslq.IsEmpty then
begin
SendCommandWithParams(Connection, 'SENDYOURINFO', IntToStr(Connection.UniqueID) + Sep);
userslq.Close;
end;
userslq.Close;
userslq.SQL.Clear;
userslq.SQL.Add('UPDATE `users` SET `lastlogin` = :Date, `timeslogin`=(`timeslogin`+1) WHERE `users`.`username` = :uname;');
userslq.ParamByName('uname').AsString := trim(usrnm);
userslq.ParamByName('Date').AsDate := Now;
userslq.ExecSQL;
userslq.Close;
end;
end;
if Command = 'TAKEMYINFO' then //login ok, add to listview
begin
Connection.Name := Params[1];
Connections.Add(Connection);
AddConnectionToListView(Connection);
end;
if Command = 'TEXTMESSAGE' then
begin
BroadCastTextMessage(Params[1], Connection.UniqueID, Connection.Name, Connection.IP);
end;
if Command = 'DISCONNECTED' then
begin
DeleteConnectionFromList(Connection.UniqueID);
DeleteConnectionFromListView(Connection.UniqueID);
end;
MS.Free;
end;
broadcast Protocol and used procedures
procedure TfMain.AddConnectionToListView(Connection: TConnection);
begin
with lwConnections.Items.Add do
begin
Caption := Connection.Name;
SubItems.Add(Connection.IP);
SubItems.Add(FormatDateTime('hh:mm:ss', Connection.Connected));
SubItems.Add(IntToStr(Connection.UniqueID));
end;
end;
procedure TfMain.DeleteConnectionFromListView(UniqueID: DWord);
var
I: Integer;
begin
for I := 0 to lwConnections.Items.Count - 1 do
begin
if lwConnections.Items.Item[I].SubItems.Strings[2] = IntToStr(UniqueID) then
begin
lwConnections.Items.Delete(I);
Break;
end;
end;
end;
procedure TfMain.DeleteConnectionFromList(UniqueID: DWord);
var
I, Pos: Integer;
begin
Pos := -1;
for I := 0 to Connections.Count - 1 do
begin
if TConnection(Connections.Items[I]).UniqueID = UniqueID then
begin
Pos := I;
Break;
end;
end;
if Pos <> -1 then
Connections.Delete(Pos);
end;
procedure TfMain.BroadCastTextMessage(const TextMessage: String; const FromUniqueID: DWord;
const FromName: string; const dip: string);
var
I: Integer;
Connection: TConnection;
begin
for I := 0 to Connections.Count - 1 do
begin
Connection := Connections.Items[I];
if Connection.UniqueID <> FromUniqueID then
SendCommandWithParams(Connection, 'TEXTMESSAGE', FromName + Sep + TextMessage + Sep + dip + Sep);
end;
end;
procedure TfMain.SendCommandWithParams(Connection: TConnection; Command, Params:String);
var
PackedParams: TPackedParams;
begin
if not TIdContext(Connection.Thread).Connection.Socket.Connected then
Exit;
TCPServer.Contexts.LockList;
try
PackedParams.Params := ShortString(Params);
with TIdContext(Connection.Thread).Connection.Socket do
begin
WriteLn('1' + Command);
Write(RawToBytes(PackedParams, SizeOf(PackedParams)));
end;
finally
TCPServer.Contexts.UnlockList;
end;
end;
on connect server event
procedure Tfmain.TcpServerConnect(AContext: TIdContext);
var
Connection : TConnection;
begin
Connection := TConnection.Create;
Connection.IP := AContext.Connection.Socket.Binding.PeerIP;
Connection.Connected := Now;
Connection.UniqueID := GetTickCount;
if Connection.UniqueID = LastUniqueID then
Connection.UniqueID := GetTickCount + 1000;
LastUniqueID := Connection.UniqueID;
Connection.Thread := AContext;
AContext.Data := Connection;
end;
Updated
by following remy answer and his great details i started to do synchronize but in remy answer i am confused about TCriticalSection also i will have to rewrite the client code to be able to do same as his code doing , so i had to go with thread synchronize first here is example of what i did by following remy code i did some manage and removed database temporarily to avoid confusing here is the code of trying synchronization UI inside server execute
if Command = 'LOGIN' then
begin
if Password <> Params[1] then
AContext.Connection.Socket.WriteLn('INVALIDPASSWORD')
else
SendCommandWithParams(Connection, 'SENDYOURINFO', IntToStr(Connection.UniqueID) + Sep);
end;
if Command = 'TAKEMYINFO' then //login ok, add to listview
begin
Connection.Name := Params[1];
Connections.Add(Connection);
AddConnectionToListView(Connection);// this is not safe i know and thats what makes me confused so in this procedure call i do same as remy doing
end;
procedure TfMain.AddConnectionToListView(Connection: TConnection);
begin
TThread.Queue(nil,
procedure
var
Item: TListItem;
begin
Item := lwConnections.Items.Add;
try
Item.Caption := Connection.Name;
Item.SubItems.Add(Connection.IP);
Item.SubItems.Add(FormatDateTime('hh:mm:ss', Connection.Connected));
Item.SubItems.Add(IntToStr(Connection.UniqueID));
except
Item.Delete;
raise;
end;
end
);
end;
is this correct to synchronize ? whats makes me confused is this thread synchronize by itself ? i mean there is no thread class to execute and synchronize is this correct way ?
Updates about synchronize
Remy answer helps me i thanks him too much , but iam trying to understand thus synchronize part i found some ways on google as example include
idsync in my uses
and call it like this as example
uses
idsync;
// and in server execute i call TiDNotify To synchronize what ever i want ?
procedure TfMain.DeleteConnectionFromListView;
var
I: Integer;
begin
for I := 0 to lwConnections.Items.Count - 1 do
begin
if lwConnections.Items.Item[I].SubItems.Strings[2] = IntToStr(linetToID) then
begin
DeleteConnectionFromList(linetToID);
lwConnections.Items.Delete(I);
Break;
end;
end;
end;
procedure TfMain.TCPServerExecute(AContext: TIdContext);
var
Connection: TConnection;
Command: String;
Params: array[1..10] of String;
ParamsCount, P: Integer;
PackedParams: TPackedParams;
IdBytes: TIdBytes;
MS: TMemoryStream;
ReceiveParams, ReceiveStream: Boolean;
Size: Int64;
begin
Connection := Pointer(AContext.Data);
MS := TMemoryStream.Create;
ReceiveParams := False;
ReceiveStream := False;
Command := AContext.Connection.Socket.ReadLn; //read command
if Command[1] = '1' then //command with params
begin
Command := Copy(Command, 2, Length(Command));
ReceiveParams := True;
end
else if Command[1] = '2' then //command + memorystream
begin
Command := Copy(Command, 2, Length(Command));
ReceiveStream := True;
MS.Position := 0;
end
else if Command[1] = '3' then //command with params + memorystream
begin
Command := Copy(Command, 2, Length(Command));
ReceiveParams := True;
ReceiveStream := True;
end;
if ReceiveParams then //params is incomming
begin
AContext.Connection.Socket.ReadBytes(IdBytes, SizeOf(PackedParams), False);
BytesToRaw(IdBytes, PackedParams, SizeOf(PackedParams));
ParamsCount := 0;
repeat
Inc(ParamsCount);
p := Pos(Sep, String(PackedParams.Params));
Params[ParamsCount] := Copy(String(PackedParams.Params), 1, P - 1);
Delete(PackedParams.Params, 1, P + 4);
until PackedParams.Params = '';
end;
if ReceiveStream then //stream is incomming
begin
Size := AContext.Connection.Socket.ReadInt64;
AContext.Connection.Socket.ReadStream(MS, Size, False);
MS.Position := 0;
end;
if Command = 'LOGIN' then
begin
if Password <> Params[1] then
AContext.Connection.Socket.WriteLn('INVALIDPASSWORD')
else
SendCommandWithParams(Connection, 'SENDYOURINFO', IntToStr(Connection.UniqueID) + Sep);
end;
if Command = 'TAKEMYINFO' then //login ok, add to listview
begin
Connection.Name := Params[1];
Connections.Add(Connection);
TIdNotify.NotifyMethod(Connection.AddToListView);
end;
if Command = 'TEXTMESSAGE' then
begin
BroadCastTextMessage(Params[1], Connection.UniqueID);
end;
if Command = 'GETLIST' then
begin
SendClientsListTo(Connection.UniqueID);
end;
if Command = 'DISCONNECTED' then
begin
linetToID := Connection.UniqueID;// fmain private string variable
TIdNotify.NotifyMethod(DeleteConnectionFromListView);
end;
MS.Free;
end;
TIdTCPServer is a multi-threaded component. Its OnExecute event is triggered in the context of a worker thread. But your TAKEMYINFO and DISCONNECTED command handlers are directly accessing UI controls without synchronizing with the main UI thread. That can easily cause deadlocks (amongst other problems, including crashes, killing the UI, etc). You MUST sync!
Also, is userexists() thread-safe? Is userslq? Your use of the Connections list is definitely not thread-safe.
Why is SendCommandWithParams() locking the server's Contexts list, especially when called by OnExecute? You don't need to do that. You should be locking it in BroadCastTextMessage() instead.
Try something more like this:
type
TConnnection = class(TIdServerContext)
private
WriteLock: TCriticalSection;
public
Name: String;
IP: String;
Connected: TDateTime;
UniqueID: Dword;
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
procedure AddToListView;
procedure DeleteFromListView;
procedure BroadcastTextMessage(const TextMessage: String);
procedure SendCommandWithParams(const Command, Params: String);
procedure SendLn(const S: String);
function UserExists(const User, Passwd: string): Boolean;
procedure UpdateLastLogin(const User: String);
end;
constructor TConnection.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited;
WriteLock := TCriticalSection.Create;
end;
destructor TConnection.Destroy;
begin
WriteLock.Free;
inherited;
end;
procedure TConnection.AddToListView;
var
LName: string;
LIP: string;
LConnected: TDateTime;
LUniqueID: Dword;
begin
// in case the client disconnects and destroys this object before
// TThread.Queue() can update the ListView, capture the values so
// this object's fields are not accessed directly...
//
LName := Self.Name;
LIP := Self.IP;
LConnected := Self.Connected;
LUniqueID := Self.UniqueID;
TThread.Queue(nil,
procedure
var
Item: TListItem;
begin
if (fMain = nil) or (fMai.lwConnections = nil) then Exit;
Item := fMain.lwConnections.Items.Add;
try
Item.Data := Self;
Item.Caption := LName;
Item.SubItems.Add(LIP);
Item.SubItems.Add(FormatDateTime('hh:mm:ss', LConnected));
Item.SubItems.Add(IntToStr(LUniqueID));
except
Item.Delete;
raise;
end;
end
);
end;
procedure TConnection.DeleteFromListView;
begin
TThread.Queue(nil,
procedure
var
Item: TListItem;
begin
if (fMain = nil) or (fMai.lwConnections = nil) then Exit;
Item := fMain.lwConnections.FindData(0, Self, True, False);
if Item <> nil then Item.Delete;
end
);
end;
procedue TConnection.BroadCastTextMessage(const TextMessage: String);
var
List: TList; // or TIdContextList if using a modern Indy version
I: Integer;
Connection: TConnection;
begin
List := Server.Contexts.LockList;
try
for I := 0 to List.Count - 1 do
begin
Connection := TConnection(List.Items[I]);
if Connection <> Self then
begin
try
Connection.SendCommandWithParams('TEXTMESSAGE', FromName + Sep + TextMessage + Sep + dip + Sep);
except
end;
end;
finally
Server.Contexts.UnlockList;
end;
end;
procedure TConnection.SendCommandWithParams(const Command, Params: String);
var
PackedParams: TPackedParams;
begin
PackedParams.Params := ShortString(Params);
WriteLock.Enter;
try
with Connection.Socket do
begin
WriteLn('1' + Command);
Write(RawToBytes(PackedParams, SizeOf(PackedParams)));
end;
finally
WriteLock.Leave;
end;
end;
procedure TConnection.SendLn(const S: String);
begin
WriteLock.Enter;
try
Connection.Socket.WriteLn(S);
finally
WriteLock.Leave;
end;
end;
function TConnection.UserExists(const User, Passwd: string): Boolean;
var
Exists: Boolean;
begin
// if you give each client its own DB connection, or use
// a thread-safe DB pool, you don't have to sync this ...
//
TThread.Synchronize(nil,
procedure
if (fMain = nil) or (fMai.userslq = nil) then Exit;
with fMain.userslq do
begin
Close;
SQL.Text := 'SELECT * FROM `users` WHERE `username` = :uname AND `password` = :passwd;';
ParamByName('uname').AsString := Trim(User);
ParamByName('passwd').AsString := Trim(Passwd);
Open;
try
Exists := not IsEmpty;
finally
Close;
end;
end;
end
);
Result := Exists;
end;
procedure TConnection.UpdateLastLogin(const User: String);
begin
// if you give each client its own DB connection, or use
// a thread-safe DB pool, you don't have to sync this ...
//
TThread.Synchronize(nil,
procedure
if (fMain = nil) or (fMai.userslq = nil) then Exit;
with fMain.userslq do
begin
Close;
SQL.Text := 'UPDATE `users` SET `lastlogin` = :Date, `timeslogin`=(`timeslogin`+1) WHERE `users`.`username` = :uname;';
ParamByName('uname').AsString := Trim(User);
ParamByName('Date').AsDate := Now;
ExecSQL;
Close;
end;
end
);
end;
procedure TfMain.FormCreate(Sender: TObject);
begin
// set this before activating the server
TCPServer.ContextClass := TConnection;
end;
procedure TfMain.TCPServerConnect(AContext: TIdContext);
var
Connection: TConnection;
begin
Connection := AContext as TConnection;
Connection.Name := '';
Connection.IP := AContext.Binding.PeerIP;
Connection.Connected := Now;
Connection.UniqueID := ...;
end;
procedure TfMain.TCPServerDisconnect(AContext: TIdContext);
var
Connection: TConnection;
begin
Connection := AContext as TConnection;
Connection.DeleteFromListView;
end;
procedure TfMain.TCPServerExecute(AContext: TIdContext);
var
Connection: TConnection;
Command: String;
Params: array[1..10] of String;
ParamsCount, P: Integer;
PackedParams: TPackedParams;
IdBytes: TIdBytes;
MS: TMemoryStream;
ReceiveParams, ReceiveStream: Boolean;
S: String;
begin
Connection := AContext as TConnection;
Command := AContext.Connection.Socket.ReadLn; //read command
if Command = '' then Exit;
ReceiveParams := False;
ReceiveStream := False;
if Command[1] = '1' then //command with params
begin
Command := Copy(Command, 2, MaxInt);
ReceiveParams := True;
end
else if Command[1] = '2' then //command + memorystream
begin
Command := Copy(Command, 2, MaxInt);
ReceiveStream := True;
end
else if Command[1] = '3' then //command with params + memorystream
begin
Command := Copy(Command, 2, MaxInt);
ReceiveParams := True;
ReceiveStream := True;
end;
if ReceiveParams then //params is incomming
begin
AContext.Connection.Socket.ReadBytes(IdBytes, SizeOf(PackedParams), False);
BytesToRaw(IdBytes, PackedParams, SizeOf(PackedParams));
S := String(PackedParams.Params);
ParamsCount := 0;
while (S <> '') and (ParamsCount < 10) do
begin
Inc(ParamsCount);
p := Pos(Sep, S);
if p = 0 then
Params[ParamsCount] := S
else
begin
Params[ParamsCount] := Copy(S, 1, P - 1);
Delete(S, 1, P + 4);
end;
end;
end;
MS := nil;
try
if ReceiveStream then //stream is incomming
begin
MS := TMemoryStream.Create;
AContext.Connection.Socket.LargeStream := True;
AContext.Connection.Socket.ReadStream(MS, -1, False);
MS.Position := 0;
end;
if Command = 'LOGIN' then
begin
if ParamsCount <> 2 then
begin
Connection.SendLn('INVALIDPARAMS');
Exit;
end;
if not Connection.UserExists(Params[1], Params[2]) then
begin
Connection.SendLn('INVALIDPASSWORD');
Exit;
end;
Connection.UpdateLastLogin(Params[1]);
Connection.SendCommandWithParams('SENDYOURINFO', IntToStr(Connection.UniqueID) + Sep);
end
else if Command = 'TAKEMYINFO' then //login ok, add to listview
begin
if ParamsCount <> 1 then
begin
Connection.SendLn('INVALIDPARAMS');
Exit;
end;
Connection.Name := Params[1];
Connection.AddToListView;
end
else if Command = 'TEXTMESSAGE' then
begin
if ParamsCount <> 1 then
begin
Connection.SendLn('INVALIDPARAMS');
Exit;
end;
Connection.BroadCastTextMessage(Params[1]);
end
else if Command = 'DISCONNECTED' then
begin
AContext.Connection.Disconnect;
Exit;
end;
finally
MS.Free;
end;
end;
Is it possible to obtain information about the Windows Explorer tree (what nodes are currently expanded, when a node is expanded etc.)?
Starting from Vista there is official way to communicate with tree in Explorer window. This way uses INameSpaceTreeControl interface.
If you want to get INameSpaceTreeControl from external app you must:
1) Get IDispatch of shell window:
var
ShellWindows: IShellWindows;
i: Integer;
Dispatch: IDispatch;
SL: TStrings;
begin
OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IShellWindows, ShellWindows));
try
for i := ShellWindows.Count - 1 downto 0 do
begin
Dispatch := ShellWindows.Item(i);
try
SL := CreateNodeList(Dispatch);
try
Memo1.Lines.Assign(SL);
finally
SL.Free;
end;
Exit;
finally
Dispatch := nil;
end;
end;
finally
ShellWindows := nil;
end;
end;
2) Query IServiceProvider from IDispatch.
3) Query INameSpaceTreeControl from IServiceProvider.
4) After this you can enum elements of tree. I used the following algorithm:
function GetItemName(AShellItem: IShellItem): UnicodeString;
var
Name: PWideChar;
begin
OleCheck(AShellItem.GetDisplayName(SIGDN_NORMALDISPLAY, Name));
try
Result := Name;
finally
CoTaskMemFree(Name);
end;
end;
function GetLevelSpaces(ALevel: Integer): UnicodeString;
var
i: Integer;
begin
Result := '';
for i := 0 to ALevel - 1 do
Result := Result + ' ';
end;
function CalcLavel(AShellItem: IShellItem): Integer;
var Parent: IShellItem;
begin
Result := 0;
if Succeeded(AShellItem.GetParent(Parent)) then
try
Inc(Result);
Result := Result + CalcLavel(Parent);
finally
Parent := nil;
end;
end;
function GetExpanded(ATree: INameSpaceTreeControl; AItem: IShellItem): WideChar;
var
State: DWORD;
begin
OleCheck(ATree.GetItemState(AItem, NSTCIS_EXPANDED, State));
if State and NSTCIS_EXPANDED <> 0 then Result := '+'
else Result := '-';
end;
function CreateNodeList(ADispatch: IDispatch): TStrings;
var
ServiceProvider: IServiceProvider;
Tree: INameSpaceTreeControl;
L: Integer;
ShellItem, ShellItem2: IShellItem;
begin
OleCheck(ADispatch.QueryInterface(IServiceProvider, ServiceProvider));
try
OleCheck(ServiceProvider.QueryService(SID_SNavigationPane, INameSpaceTreeControl, Tree));
try
Result := TStringList.Create;
try
if Succeeded(Tree.GetNextItem(nil, NSTCGNI_CHILD, ShellItem)) then
repeat
try
L := CalcLavel(ShellItem);
Result.Add(GetLevelSpaces(L - 1) + GetExpanded(Tree, ShellItem) + ' ' + GetItemName(ShellItem));
finally
ShellItem2 := ShellItem;
ShellItem := nil;
end;
until Failed(Tree.GetNextItem(ShellItem2, NSTCGNI_NEXTVISIBLE, ShellItem));
finally
ShellItem2 := nil;
end;
finally
Tree := nil;
end;
finally
ServiceProvider := nil;
end;
end;
Result:
If you want to subscribe to tree actions use INameSpaceTreeControl.TreeAdvise.
I'm maintaining an old code base that's using waveOutGetDevCaps to get the names of the audio devices on the system. On Windows 7 machines this results in truncated names, as WAVEOUTCAPS.szPname is limited by MAXPNAMELEN (31 chars).
What's the Win7 way of doing this?
You could use one of the Core Audio APIs:
// get the device enumerator
IMMDeviceEnumerator* pEnumerator = NULL;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL,__uuidof(IMMDeviceEnumerator),
(void**)&pEnumerator);
// get the endpoint collection
IMMDeviceCollection* pCollection = NULL;
DWORD mask = DEVICE_STATE_ACTIVE || DEVICE_STATE_UNPLUGGED;
hr = pEnumerator->EnumAudioEndpoints(eRender, mask, &pCollection);
// get the size of the collection
UINT count = 0;
hr = pCollection->GetCount(&count);
for (int i = 0; i < (int)count; i++)
{
// get the endpoint
IMMDevice* pEndPoint = NULL;
hr = pCollection->Item(i, &pEndPoint);
// get the human readable name
String^ friendlyName;
IPropertyStore* pProps = NULL;
HRESULT hr = pEndPoint->OpenPropertyStore(STGM_READ, &pProps);
PROPVARIANT varName;
PropVariantInit(&varName);
hr = pProps->GetValue(PKEY_Device_FriendlyName, &varName);
friendlyName = gcnew String(varName.pwszVal);
PropVariantClear(&varName);
}
Error handling was removed in the above code to make it more readable. (I happen to love using C++/CLI to move between C# and the Windows APIs.)
Now the harder part will be to relate the endpoint names to the MME devices in your old code base.
I have found another way using the registry to find audio devices' full name, both Input and Output.
Works on Windows 7 and Windows 10.
procedure TForm_Config.FormCreate(Sender: TObject);
type
tagWAVEOUTCAPS2A = packed record
wMid: WORD;
wPid: WORD;
vDriverVersion: MMVERSION;
szPname: array[0..MAXPNAMELEN-1] of AnsiChar;
dwFormats: DWORD;
wChannels: WORD;
wReserved1: WORD;
dwSupport: DWORD;
ManufacturerGuid: System.TGUID;
ProductGuid: System.TGUID;
NameGuid: System.TGUID;
end;
var
i,outdevs: Integer;
woCaps: tagWAVEOUTCAPS2A;
RegistryService: TRegistry;
iClasses, iSubClasses, iNames: Integer;
audioDeviceClasses, audioDeviceSubClasses, audioDeviceNames: TStringList;
initialDeviceName, partialDeviceName, fullDeviceName: string;
begin
audioDeviceClasses := TStringList.Create;
audioDeviceSubClasses := TStringList.Create;
audioDeviceNames := TStringList.Create;
try
RegistryService := TRegistry.Create;
try
RegistryService.RootKey := HKEY_LOCAL_MACHINE;
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\') then begin
RegistryService.GetKeyNames(audioDeviceClasses);
RegistryService.CloseKey();
for iClasses := 0 to audioDeviceClasses.Count - 1 do begin
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\'+audioDeviceClasses[iClasses]) then begin
RegistryService.GetKeyNames(audioDeviceSubClasses);
RegistryService.CloseKey();
for iSubClasses := 0 to audioDeviceSubClasses.Count - 1 do begin
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\'+audioDeviceClasses[iClasses]+'\'+audioDeviceSubClasses[iSubClasses]) then begin
if RegistryService.ValueExists('DeviceDesc') then begin
fullDeviceName := Trim(RegistryService.ReadString('DeviceDesc'));
if AnsiPos(';',fullDeviceName) > 0 then begin
fullDeviceName := Trim(AnsiMidStr(fullDeviceName, AnsiPos(';',fullDeviceName)+1, Length(fullDeviceName)));
end;
audioDeviceNames.Add(fullDeviceName);
end;
RegistryService.CloseKey();
end;
end;
end;
end;
end;
finally
FreeAndNil(RegistryService);
end;
// WaveOutDevComboBox is a selection box (combo) placed in the form and will receive the list of output audio devices
WaveOutDevComboBox.Clear;
try
outdevs := waveOutGetNumDevs;
for i := 0 to outdevs - 1 do begin
ZeroMemory(#woCaps, sizeof(woCaps));
if waveOutGetDevCaps(i, #woCaps, sizeof(woCaps)) = MMSYSERR_NOERROR then begin
RegistryService := TRegistry.Create;
try
RegistryService.RootKey := HKEY_LOCAL_MACHINE;
if RegistryService.OpenKeyReadOnly('\System\CurrentControlSet\Control\MediaCategories\' + GUIDToString(woCaps.NameGuid)) then begin
WaveOutDevComboBox.Items.Add(RegistryService.ReadString('Name'));
RegistryService.CloseKey();
end
else begin
initialDeviceName := '';
partialDeviceName := Trim(woCaps.szPname);
if AnsiPos('(',partialDeviceName) > 0 then begin
initialDeviceName := Trim(AnsiLeftStr(partialDeviceName,AnsiPos('(',partialDeviceName)-1));
partialDeviceName := Trim(AnsiMidStr(partialDeviceName,AnsiPos('(',partialDeviceName)+1,Length(partialDeviceName)));
if AnsiPos(')',partialDeviceName) > 0 then begin
partialDeviceName := Trim(AnsiLeftStr(partialDeviceName,AnsiPos(')',partialDeviceName)-1));
end;
end;
for iNames := 0 to audioDeviceNames.Count - 1 do begin
fullDeviceName := audioDeviceNames[iNames];
if AnsiStartsText(partialDeviceName,fullDeviceName) then begin
break;
end
else begin
fullDeviceName := partialDeviceName;
end;
end;
WaveOutDevComboBox.Items.Add(initialDeviceName + IfThen(initialDeviceName<>EmptyStr,' (','') + fullDeviceName + IfThen(initialDeviceName<>EmptyStr,')',''));
end;
finally
FreeAndNil(RegistryService);
end;
end;
end;
except
WaveOutDevComboBox.Enabled := False;
end;
finally
FreeAndNil(audioDeviceClasses);
FreeAndNil(audioDeviceSubClasses);
FreeAndNil(audioDeviceNames);
end;
end;