Why do we need so much code just to tell the debugger to call .ToString on some objects?
I looked at the included examples under C:\Program Files (x86)\Embarcadero\Studio\19.0\source\Visualizers and adapted them for one of my objects - it works well.
Is there an easier way?
unit uMyObjectDebugVisualizer;
interface
procedure Register;
implementation
uses
Classes, Forms, SysUtils, ToolsAPI;
type
TMyObjectDebugVisualizer = class(TInterfacedObject,
IOTADebuggerVisualizer, IOTADebuggerVisualizerValueReplacer, IOTAThreadNotifier, IOTAThreadNotifier160)
private
FNotifierIndex: Integer;
FCompleted: Boolean;
FDeferredResult: string;
public
{ IOTADebuggerVisualizer }
function GetSupportedTypeCount: Integer;
procedure GetSupportedType(Index: Integer; var TypeName: string;
var AllDescendants: Boolean);
function GetVisualizerIdentifier: string;
function GetVisualizerName: string;
function GetVisualizerDescription: string;
{ IOTADebuggerVisualizerValueReplacer }
function GetReplacementValue(const Expression, TypeName, EvalResult: string): string;
{ IOTAThreadNotifier }
procedure EvaluteComplete(const ExprStr: string; const ResultStr: string;
CanModify: Boolean; ResultAddress: Cardinal; ResultSize: Cardinal;
ReturnCode: Integer);
procedure ModifyComplete(const ExprStr: string; const ResultStr: string;
ReturnCode: Integer);
procedure ThreadNotify(Reason: TOTANotifyReason);
procedure AfterSave;
procedure BeforeSave;
procedure Destroyed;
procedure Modified;
{ IOTAThreadNotifier160 }
procedure EvaluateComplete(const ExprStr: string; const ResultStr: string;
CanModify: Boolean; ResultAddress: TOTAAddress; ResultSize: LongWord;
ReturnCode: Integer);
end;
{ TMyObjectDebugVisualizer }
procedure TMyObjectDebugVisualizer.AfterSave;
begin
// don't care about this notification
end;
procedure TMyObjectDebugVisualizer.BeforeSave;
begin
// don't care about this notification
end;
procedure TMyObjectDebugVisualizer.Destroyed;
begin
// don't care about this notification
end;
procedure TMyObjectDebugVisualizer.Modified;
begin
// don't care about this notification
end;
procedure TMyObjectDebugVisualizer.ModifyComplete(const ExprStr, ResultStr: string; ReturnCode: Integer);
begin
// don't care about this notification
end;
procedure TMyObjectDebugVisualizer.EvaluteComplete(const ExprStr,
ResultStr: string; CanModify: Boolean; ResultAddress, ResultSize: Cardinal;
ReturnCode: Integer);
begin
EvaluateComplete(ExprStr, ResultStr, CanModify, TOTAAddress(ResultAddress),
LongWord(ResultSize), ReturnCode);
end;
procedure TMyObjectDebugVisualizer.EvaluateComplete(const ExprStr,
ResultStr: string; CanModify: Boolean; ResultAddress: TOTAAddress; ResultSize: LongWord;
ReturnCode: Integer);
begin
FCompleted:= True;
if ReturnCode = 0 then
FDeferredResult:= ResultStr;
end;
procedure TMyObjectDebugVisualizer.ThreadNotify(Reason: TOTANotifyReason);
begin
// don't care about this notification
end;
function TMyObjectDebugVisualizer.GetReplacementValue(const Expression, TypeName, EvalResult: string): string;
var
CurProcess: IOTAProcess;
CurThread: IOTAThread;
ResultStr: array[0..255] of Char;
CanModify: Boolean;
ResultAddr, ResultSize, ResultVal: LongWord;
EvalRes: TOTAEvaluateResult;
DebugSvcs: IOTADebuggerServices;
Done: Boolean;
begin
Result:= EvalResult;
if Supports(BorlandIDEServices, IOTADebuggerServices, DebugSvcs) then CurProcess:= DebugSvcs.CurrentProcess;
if (CurProcess <> nil) and (CurProcess.GetProcessType <> optOSX32) then begin
CurThread:= CurProcess.CurrentThread;
if CurThread <> nil then repeat
Done:= True;
EvalRes:= CurThread.Evaluate(Expression + '.ToString', #ResultStr, Length(ResultStr),
CanModify, eseAll, '', ResultAddr, ResultSize, ResultVal, '', 0);
case EvalRes of
erOK: Result:= ResultStr;
erDeferred: begin
FCompleted:= False;
FDeferredResult:= '';
FNotifierIndex:= CurThread.AddNotifier(Self);
while not FCompleted do DebugSvcs.ProcessDebugEvents;
CurThread.RemoveNotifier(FNotifierIndex);
FNotifierIndex:= -1;
if FDeferredResult <> '' then Result:= FDeferredResult
else Result:= EvalResult;
end;
erBusy: begin
DebugSvcs.ProcessDebugEvents;
Done:= False;
end;
end;
until Done;
end;
end;
function TMyObjectDebugVisualizer.GetSupportedTypeCount: Integer;
begin
Result:= 1;
end;
procedure TMyObjectDebugVisualizer.GetSupportedType(Index: Integer; var TypeName: string; var AllDescendants: Boolean);
begin
AllDescendants:= True;
TypeName:= 'TMyObject';
end;
function TMyObjectDebugVisualizer.GetVisualizerDescription: string;
begin
Result:= 'Displays TMyObject objects';
end;
function TMyObjectDebugVisualizer.GetVisualizerIdentifier: string;
begin
Result:= ClassName;
end;
function TMyObjectDebugVisualizer.GetVisualizerName: string;
begin
Result:= 'TMyObject Visualizer for Delphi';
end;
var
MyObjectVis: IOTADebuggerVisualizer;
procedure Register;
begin
MyObjectVis:= TMyObjectDebugVisualizer.Create;
(BorlandIDEServices as IOTADebuggerServices).RegisterDebugVisualizer(MyObjectVis);
end;
procedure RemoveVisualizer;
var
DebuggerServices: IOTADebuggerServices;
begin
if Supports(BorlandIDEServices, IOTADebuggerServices, DebuggerServices) then begin
DebuggerServices.UnregisterDebugVisualizer(MyObjectVis);
MyObjectVis:= nil;
end;
end;
initialization
finalization
RemoveVisualizer;
end.
To get this working, you have to add it to a package. Also add "designide.dcp" to the required packages.
Related
I have the process ID (PID), how can I get its full command line (path and arguments)?
I recently had to do this; needed to terminate known applications, perform a task, then restart them with the same command line parameters they started with.
Of all the methods that I explored that can achieve getting command line parameters for each running process, WMI proved to be the safest and least painful way.
Add the 2 units pasted below, then call GetProcessList() and loop through the objects in the TObjectList. In your application, ensure you call CoInitialize(nil) on start up, and CoUninitialize() before it's closed. Code tested in Delphi 6.
unit uWmi;
interface
uses
Classes, Contnrs,
Variants, ActiveX, ComObj;
const
WMI_RESULT_OK = 0;
WMI_RESULT_NO_RECORDS = 1;
WMI_RESULT_NULL = 2;
WMI_RESULT_INVALID_PROPERTY = 3;
WMI_RESULT_ERROR_EXEC_QUERY = 4;
WMI_RESULT_UNKNOWN_ERROR = 5;
WMI_RESULT_STRINGS: array [0..5] of string = (
'OK',
'No records',
'Property value is null',
'Invalid property',
'Error executing query',
'Unknown error'
);
type
TWmi = class
private
FService: OleVariant;
function ExecWmiQuery(const AWMIQuery: string; var AItems: OleVariant; var AEnum: IEnumVariant): Boolean;
public
constructor Create;
class function GetWmiObject(const objectName: string): IDispatch;
class function GetWmiSelectQuery(const AWMIClass: string; const ASelectAll: Boolean; const AWMIProperties: TStrings = nil; const AWMIProperty: string = ''): string;
class function GetWmiPropertyValue(const AItem: OleVariant; const AProperty: string; var AValue: string): Integer;
class procedure AddWmiPropertyValueToList(const AValue: string; AResult: Integer; AValues: TStrings); overload;
class procedure AddWmiPropertyValueToList(const AItem: OleVariant; const AProperty: string; AValues: TStrings); overload;
function GetFirstRecordSinglePropertyValue(const AWMIClass, AWMIProperty: string; var AValue: string;
const ASelectAll: Boolean): Integer; overload;
function GetFirstRecordSinglePropertyValue(const AWMIProperty: string; var AValue: string;
const AWMIQuery: string): Integer; overload;
function GetAllRecordsSinglePropertyValues(const AWMIClass, AWMIProperty: string; AValues: TStrings;
const ASelectAll: Boolean): Integer; overload;
function GetAllRecordsSinglePropertyValues(const AWMIProperty: string; AValues: TStrings;
const AWMIQuery: string): Integer; overload;
function GetAllRecordsMultiplePropertiesValues(const AWMIClass: string; AProperties: TStrings;
ARecords: TObjectList; const ASelectAll: Boolean): Integer; overload;
function GetAllRecordsMultiplePropertiesValues(AProperties: TStrings; ARecords: TObjectList;
const AWMIQuery: string): Integer; overload;
function GetAllRecordsMultiplePropertiesValues(const AWMIClass: string; const AProperties: array of string;
ARecords: TObjectList; const ASelectAll: Boolean): Integer; overload;
function GetAllRecordsMultiplePropertiesValues(const AProperties: array of string;
ARecords: TObjectList; const AWMIQuery: string): Integer; overload;
end;
implementation
uses
SysUtils, uStringUtils;
const
wbemFlagForwardOnly = $00000020;
{ TWmi }
function TWmi.ExecWmiQuery(const AWMIQuery: string; var AItems: OleVariant; var AEnum: IEnumVariant): Boolean;
begin
try
AItems := FService.ExecQuery(AWMIQuery, 'WQL', wbemFlagForwardOnly);
AEnum := IUnknown(AItems._NewEnum) as IEnumVariant;
Result := True;
except
Result := False;
end;
end;
constructor TWmi.Create;
{$IFDEF USE_LOCATOR}
const
USER = '';
PASSWORD = '';
COMPUTER = 'localhost';
var
locator: OleVariant;
{$ENDIF}
begin
{$IFDEF USE_LOCATOR}
locator := CreateOleObject('WbemScripting.SWbemLocator');
FService := locator.ConnectServer(COMPUTER, 'root\CIMV2', USER, PASSWORD);
{$ELSE}
FService := GetWmiObject('winmgmts:\\localhost\root\cimv2');
{$ENDIF}
end;
class function TWmi.GetWmiObject(const objectName: string): IDispatch;
var
chEaten: Integer;
BindCtx: IBindCtx;
Moniker: IMoniker;
begin
OleCheck(CreateBindCtx(0, bindCtx));
OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));
OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
end;
class function TWmi.GetWmiSelectQuery(const AWMIClass: string; const ASelectAll: Boolean;
const AWMIProperties: TStrings = nil; const AWMIProperty: string = ''): string;
var
props: string;
i: Integer;
begin
if ASelectAll then
props := '*'
else
if AWMIProperties = nil then
props := AWMIProperty
else
for i := 0 to AWMIProperties.Count - 1 do
if props = '' then
props := AWMIProperties[i]
else
props := props + ',' + AWMIProperties[i];
Result := Format('SELECT %s FROM %s', [props, AWMIClass]);
end;
class function TWmi.GetWmiPropertyValue(const AItem: OleVariant; const AProperty: string; var AValue: string): Integer;
var
v: OleVariant;
begin
try
v := AItem.Properties_.Item(AProperty).Value;
except
Result := WMI_RESULT_INVALID_PROPERTY;
AValue := '';
Exit;
end;
if VarIsNull(v) then
begin
Result := WMI_RESULT_NULL;
AValue := '';
Exit;
end;
AValue := Trim(v);
Result := WMI_RESULT_OK;
end;
class procedure TWmi.AddWmiPropertyValueToList(const AValue: string; AResult: Integer; AValues: TStrings);
begin
AValues.AddObject(AValue, TObject(AResult));
end;
class procedure TWmi.AddWmiPropertyValueToList(const AItem: OleVariant; const AProperty: string; AValues: TStrings);
var
value: string;
r: Integer;
begin
r := GetWmiPropertyValue(AItem, AProperty, value);
AddWmiPropertyValueToList(value, r, AValues);
end;
function TWmi.GetFirstRecordSinglePropertyValue(const AWMIClass, AWMIProperty: string; var AValue: string;
const ASelectAll: Boolean): Integer;
begin
Result := GetFirstRecordSinglePropertyValue(AWMIProperty, AValue,
GetWmiSelectQuery(AWMIClass, ASelectAll, nil, AWMIProperty));
end;
function TWmi.GetFirstRecordSinglePropertyValue(const AWMIProperty: string; var AValue: string;
const AWMIQuery: string): Integer;
var
items, item: OleVariant;
enum: IEnumVariant;
value: Cardinal;
begin
try
if not ExecWmiQuery(AWMIQuery, items, enum) then
begin
Result := WMI_RESULT_ERROR_EXEC_QUERY;
Exit;
end;
if enum.Next(1, item, value) <> 0 then
Result := WMI_RESULT_NO_RECORDS
else
try
Result := GetWmiPropertyValue(item, AWMIProperty, AValue);
finally
item := Unassigned;
end;
except {on: EOleException do Writeln(e.Source, ' ', e.ErrorCode, ' ', e.Message);}
Result := WMI_RESULT_UNKNOWN_ERROR;
end;
end;
function TWmi.GetAllRecordsSinglePropertyValues(const AWMIClass, AWMIProperty: string; AValues: TStrings;
const ASelectAll: Boolean): Integer;
begin
Result := GetAllRecordsSinglePropertyValues(AWMIProperty, AValues,
GetWmiSelectQuery(AWMIClass, ASelectAll, nil, AWMIProperty));
end;
function TWmi.GetAllRecordsSinglePropertyValues(const AWMIProperty: string; AValues: TStrings; const AWMIQuery: string): Integer;
var
items, item: OleVariant;
enum: IEnumVariant;
value: Cardinal;
begin
try
if not ExecWmiQuery(AWMIQuery, items, enum) then
begin
Result := WMI_RESULT_ERROR_EXEC_QUERY;
Exit;
end;
AValues.Clear;
while enum.Next(1, item, value) = 0 do
try
AddWmiPropertyValueToList(item, AWMIProperty, AValues);
finally
item := Unassigned;
end;
if AValues.Count = 0 then
Result := WMI_RESULT_NO_RECORDS
else
Result := WMI_RESULT_OK;
except {on: EOleException do Writeln(e.Source, ' ', e.ErrorCode, ' ', e.Message);}
Result := WMI_RESULT_UNKNOWN_ERROR;
AValues.Clear;
end;
end;
function TWmi.GetAllRecordsMultiplePropertiesValues(const AWMIClass: string; AProperties: TStrings;
ARecords: TObjectList; const ASelectAll: Boolean): Integer;
begin
Result := GetAllRecordsMultiplePropertiesValues(AProperties, ARecords,
GetWmiSelectQuery(AWMIClass, ASelectAll, AProperties));
end;
function TWmi.GetAllRecordsMultiplePropertiesValues(AProperties: TStrings; ARecords: TObjectList;
const AWMIQuery: string): Integer;
var
items, item: OleVariant;
enum: IEnumVariant;
value: Cardinal;
values: TStrings;
i: Integer;
begin
try
if not ExecWmiQuery(AWMIQuery, items, enum) then
begin
Result := WMI_RESULT_ERROR_EXEC_QUERY;
Exit;
end;
ARecords.Clear;
while enum.Next(1, item, value) = 0 do
try
values := TStringList.Create;
ARecords.Add(values);
for i := 0 to AProperties.Count - 1 do
AddWmiPropertyValueToList(item, AProperties[i], values);
finally
item := Unassigned;
end;
if ARecords.Count = 0 then
Result := WMI_RESULT_NO_RECORDS
else
Result := WMI_RESULT_OK;
except {on: EOleException do Writeln(e.Source, ' ', e.ErrorCode, ' ', e.Message);}
Result := WMI_RESULT_UNKNOWN_ERROR;
ARecords.Clear;
end;
end;
function TWmi.GetAllRecordsMultiplePropertiesValues(const AWMIClass: string; const AProperties: array of string;
ARecords: TObjectList; const ASelectAll: Boolean): Integer;
var
props: TStringList;
begin
props := CreateStringList(AProperties);
try
Result := GetAllRecordsMultiplePropertiesValues(AWMIClass, props, ARecords, ASelectAll);
finally
props.Free;
end;
end;
function TWmi.GetAllRecordsMultiplePropertiesValues(const AProperties: array of string;
ARecords: TObjectList; const AWMIQuery: string): Integer;
var
props: TStrings;
begin
props := CreateStringList(AProperties);
try
Result := GetAllRecordsMultiplePropertiesValues(props, ARecords, AWMIQuery);
finally
props.Free;
end;
end;
{initialization
CoInitialize(nil);
finalization
CoUninitialize;}
end.
unit uWmiProcess;
interface
uses
Contnrs;
type
TProcessInfo = class
public
Name: string;
ProcessId: Cardinal;
ExecutablePath: string;
CommandLine: string;
SessionId: Integer;
CreationDate: string;
end;
function GetProcessList: TObjectList{<TProcessInfo>};
implementation
uses
SysUtils, Classes, uWmi;
function GetProcessList: TObjectList{<TProcessInfo>};
var
wmi: TWmi;
processInfo: TProcessInfo;
records: TObjectList;
values: TStringList;
i: Integer;
function CallWmi(const AProps: array of string): Boolean;
begin
Result := wmi.GetAllRecordsMultiplePropertiesValues('Win32_Process', AProps, records, False) = uWmi.WMI_RESULT_OK;
end;
begin
Result := TObjectList.Create(True);
try
records := TObjectList.Create(True);
try
wmi := TWmi.Create;
try
if not CallWmi(['Name', 'ProcessId', 'ExecutablePath', 'CommandLine', 'SessionId', 'CreationDate']) then
Exit;
for i := 0 to records.Count - 1 do
begin
processInfo := TProcessInfo.Create;
Result.Add(processInfo);
values := TStringList(records[i]);
processInfo.Name := values[0];
processInfo.ProcessId := StrToInt(values[1]);
processInfo.ExecutablePath := values[2];
processInfo.CommandLine := values[3];
processInfo.SessionId := StrToInt(values[4]);
processInfo.CreationDate := values[5];
end;
finally
wmi.Free;
end;
finally
records.Free;
end;
except
//FreeAndNil(Result);
Result.Free;
raise;
end;
end;
end.
unit uStringUtils;
interface
uses
Classes;
procedure SplitString(const ASource: string; const ASeparator: Char; AValues: TStrings); overload;
function SplitString(const ASource: string; const ASeparator: Char): TStringList; overload;
function JoinStrings(const ASeparator: string; AValues: TStrings): string;
function CopyRange(const ASource: string; const AIndexFrom, AIndexTo: Integer): string;
type
TStringsHelper = class //poor man's helper :) ToDo should be other way around, naked routines calling the static class?
public
class procedure SplitString(const ASource: string; const ASeparator: Char; AValues: TStrings); overload;
class function SplitString(const ASource: string; const ASeparator: Char): TStringList; overload;
class function JoinStrings(const ASeparator: string; AValues: TStrings): string;
end;
type
TStringArray = array of string;
procedure FillStringList(const AValues: array of string; AStrings: TStrings);
function CreateStringList(const AValues: array of string): TStringList;
function CreateStringArray(const AStrings: array of string): TStringArray;
implementation
function CopyRange(const ASource: string; const AIndexFrom, AIndexTo: Integer): string;
begin
Result := Copy(ASource, AIndexFrom, AIndexTo - AIndexFrom + 1);
end;
procedure SplitString(const ASource: string; const ASeparator: Char; AValues: TStrings);
var
i, lastDelimPos: Integer;
begin
AValues.Clear;
lastDelimPos := 0;
for i := 1 to Length(ASource) do
if ASource[i] = ASeparator then
begin
if lastDelimPos = 0 then
AValues.Add(CopyRange(ASource, 1, i - 1))
else
AValues.Add(CopyRange(ASource, lastDelimPos + 1, i - 1));
lastDelimPos := i;
end;
if lastDelimPos = 0 then
AValues.Add(ASource)
else
AValues.Add(CopyRange(ASource, lastDelimPos + 1, MaxInt));
end;
function SplitString(const ASource: string; const ASeparator: Char): TStringList;
begin
Result := TStringList.Create;
SplitString(ASource, ASeparator, Result);
end;
function JoinStrings(const ASeparator: string; AValues: TStrings): string;
var
s: string;
i, len: Integer;
p: PChar;
begin
case AValues.Count of
0:
Result := '';
1:
Result := AValues[0];
else
len := (AValues.Count - 1) * Length(ASeparator);
for i := 0 to AValues.Count - 1 do
Inc(len, Length(AValues[i]));
SetLength(Result, len);
p := PChar(Result);
for i := 0 to AValues.Count - 1 do
begin
if i = 0 then
s := AValues[i]
else
s := ASeparator + AValues[i];
Move(PChar(s)^, p^, Length(s));
Inc(p, Length(s));
end;
end;
end;
{ TStringsHelper }
class procedure TStringsHelper.SplitString(const ASource: string;
const ASeparator: Char; AValues: TStrings);
begin
uStringUtils.SplitString(ASource, ASeparator, AValues); //Note the explicit unit reference
end;
class function TStringsHelper.SplitString(const ASource: string;
const ASeparator: Char): TStringList;
begin
Result := uStringUtils.SplitString(ASource, ASeparator); //Note the explicit unit reference
end;
class function TStringsHelper.JoinStrings(const ASeparator: string;
AValues: TStrings): string;
begin
Result := uStringUtils.JoinStrings(ASeparator, AValues); //Note the explicit unit reference
end;
procedure FillStringList(const AValues: array of string; AStrings: TStrings);
var
i: Integer;
begin
AStrings.Clear;
AStrings.Capacity := Length(AValues);
for i := 0 to Length(AValues) - 1 do
AStrings.Add(AValues[i]);
end;
function CreateStringList(const AValues: array of string): TStringList;
begin
Result := TStringList.Create;
FillStringList(AValues, Result);
end;
function CreateStringArray(const AStrings: array of string): TStringArray;
var
i: Integer;
begin
SetLength(Result, Length(AStrings));
for i := 0 to Length(AStrings) - 1 do
Result[i] := AStrings[i];
end;
end.
I have a working debugger visualizer that helps visualize variables of type TIntTime.
TIntTime = type integer;
The visualizer replaces a number of seconds since midnight with a time string HH:MM:SS. This works fine on variables of type TIntTime during a debug session, but not on functions. For example if I place GetiTime in a watch
function GetiTime: TIntTime;
begin
Result:=30000;
end;
the watch will show 30000. The expected replaced value is '08:20:00'. The visualizer does not intercept function return values of type TIntTime and this is the problem.
I am using Delphi 10 Seattle. My visualizer is based on DateTimeVisualizer.pas found in Delphi 10\source\Visualizers. The DateTimeVisualizer suggests that function return values are intercepted by using the type name string 'function: TIntTime' in GetSupportedType. I have tried
'function: TIntTime'
'function:TIntTime'
'function::TIntTime'
without luck. I suspect it is a question of getting this type name string correct, but haven't been able to find information about the formatting on the internet.
If I instead place GetDateTime in a watch it shows '14-02-2018 13:20:30' as expected. If I switch the TDateTime/TDate/TTime visualizer off in options the watch shows 43145.5559... This tells me that it is possible to intercept function return values with a visualizer.
function GetDateTime: TDateTime;
begin
Result:=EncodeDateTime(2018,2,14,13,20,30,0);
end;
In my case it is not an option to use the TDateTime data type. So my question is: how can I get my visualizer to intercept function return values of type TIntTime?
Below is the source for the TIntTime visualizer
unit IntTimeVisualizer;
interface
procedure Register;
implementation
uses
Classes, Forms, SysUtils, ToolsAPI;
resourcestring
sIntTimeVisualizerName = 'TIntTime Visualizer for Delphi';
sIntTimeVisualizerDescription = 'Displays TIntTime instances in a human-readable time format rather than as an integer value';
type
TDebuggerIntTimeVisualizer = class(TInterfacedObject, IOTADebuggerVisualizer,
IOTADebuggerVisualizerValueReplacer, IOTAThreadNotifier, IOTAThreadNotifier160)
private
FCompleted: Boolean;
FDeferredResult: string;
public
{ IOTADebuggerVisualizer }
function GetSupportedTypeCount: Integer;
procedure GetSupportedType(Index: Integer; var TypeName: string;
var AllDescendants: Boolean);
function GetVisualizerIdentifier: string;
function GetVisualizerName: string;
function GetVisualizerDescription: string;
{ IOTADebuggerVisualizerValueReplacer }
function GetReplacementValue(const Expression, TypeName, EvalResult: string): string;
{ IOTAThreadNotifier }
procedure EvaluteComplete(const ExprStr: string; const ResultStr: string;
CanModify: Boolean; ResultAddress: Cardinal; ResultSize: Cardinal;
ReturnCode: Integer);
procedure ModifyComplete(const ExprStr: string; const ResultStr: string;
ReturnCode: Integer);
procedure ThreadNotify(Reason: TOTANotifyReason);
procedure AfterSave;
procedure BeforeSave;
procedure Destroyed;
procedure Modified;
{ IOTAThreadNotifier160 }
procedure EvaluateComplete(const ExprStr: string; const ResultStr: string;
CanModify: Boolean; ResultAddress: TOTAAddress; ResultSize: LongWord;
ReturnCode: Integer);
end;
TIntTimeType = (dttIntTime);
TIntTimeVisualizerType = record
TypeName: string;
TimeType: TIntTimeType;
end;
const
IntTimeVisualizerTypes: array[0..1] of TIntTimeVisualizerType =
(
(TypeName: 'TIntTime'; TimeType: dttIntTime;), //<-- This type is working fine
(TypeName: 'function: TIntTime'; TimeType: dttIntTime;) //<-- This type is not working
);
{ TDebuggerIntTimeVisualizer }
procedure TDebuggerIntTimeVisualizer.AfterSave;
begin
// don't care about this notification
end;
procedure TDebuggerIntTimeVisualizer.BeforeSave;
begin
// don't care about this notification
end;
procedure TDebuggerIntTimeVisualizer.Destroyed;
begin
// don't care about this notification
end;
procedure TDebuggerIntTimeVisualizer.Modified;
begin
// don't care about this notification
end;
procedure TDebuggerIntTimeVisualizer.ModifyComplete(const ExprStr,
ResultStr: string; ReturnCode: Integer);
begin
// don't care about this notification
end;
procedure TDebuggerIntTimeVisualizer.EvaluteComplete(const ExprStr,
ResultStr: string; CanModify: Boolean; ResultAddress, ResultSize: Cardinal;
ReturnCode: Integer);
begin
EvaluateComplete(ExprStr, ResultStr, CanModify, TOTAAddress(ResultAddress),
LongWord(ResultSize), ReturnCode);
end;
procedure TDebuggerIntTimeVisualizer.EvaluateComplete(const ExprStr,
ResultStr: string; CanModify: Boolean; ResultAddress: TOTAAddress; ResultSize: LongWord;
ReturnCode: Integer);
begin
FCompleted := True;
if ReturnCode = 0 then
FDeferredResult := ResultStr;
end;
procedure TDebuggerIntTimeVisualizer.ThreadNotify(Reason: TOTANotifyReason);
begin
// don't care about this notification
end;
function TDebuggerIntTimeVisualizer.GetReplacementValue(
const Expression, TypeName, EvalResult: string): string;
var
TimeType: TIntTimeType;
I: Integer;
function IntTimeToStr(s: Integer): string;
var
hh, mm, ss: integer;
begin
hh:=s div 3600;
mm:=(s div 60)-hh*60;
ss:=s mod 60;
Result:=Format('%.2d:%.2d:%.2d',[hh,mm,ss]);
end;
function FormatResult(const LEvalResult: string; DTType: TIntTimeType; out ResStr: string): Boolean;
var
IntValue: integer;
begin
Result := True;
try
if not TryStrToInt(LEvalResult, IntValue) then
Result:=false
else
case DTType of
dttIntTime: ResStr:=IntTimeToStr(IntValue);
end;
except
Result := False;
end;
end;
begin
TimeType := TIntTimeType(-1);
for I := Low(IntTimeVisualizerTypes) to High(IntTimeVisualizerTypes) do begin
if TypeName = IntTimeVisualizerTypes[I].TypeName then begin
TimeType:=IntTimeVisualizerTypes[I].TimeType;
Break;
end;
end;
if not FormatResult(EvalResult, TimeType, Result) then
Result := EvalResult;
end;
function TDebuggerIntTimeVisualizer.GetSupportedTypeCount: Integer;
begin
Result := Length(IntTimeVisualizerTypes);
end;
procedure TDebuggerIntTimeVisualizer.GetSupportedType(Index: Integer; var TypeName: string;
var AllDescendants: Boolean);
begin
AllDescendants := false;
TypeName := IntTimeVisualizerTypes[Index].TypeName;
end;
function TDebuggerIntTimeVisualizer.GetVisualizerDescription: string;
begin
Result := sIntTimeVisualizerDescription;
end;
function TDebuggerIntTimeVisualizer.GetVisualizerIdentifier: string;
begin
Result := ClassName;
end;
function TDebuggerIntTimeVisualizer.GetVisualizerName: string;
begin
Result := sIntTimeVisualizerName;
end;
var
IntTimeVis: IOTADebuggerVisualizer;
procedure Register;
begin
IntTimeVis:=TDebuggerIntTimeVisualizer.Create;
(BorlandIDEServices as IOTADebuggerServices).RegisterDebugVisualizer(IntTimeVis);
end;
procedure RemoveVisualizer;
var
DebuggerServices: IOTADebuggerServices;
begin
if Supports(BorlandIDEServices, IOTADebuggerServices, DebuggerServices) then begin
DebuggerServices.UnregisterDebugVisualizer(IntTimeVis);
IntTimeVis:=nil;
end;
end;
initialization
finalization
RemoveVisualizer;
end.
I use UploadObject in unit Data.Cloud.AmazonAPI to upload some files to the AWS S3,and it works,but how can I get the progress information while uploading or downloading? It's my code:
function TForm1.UploadFile(LocalFilePath: string; RemoteFileName: string; Bucket: string): Boolean;
var
Service: TAmazonStorageService;
ConAmazon: TAmazonConnectionInfo;
FS: TFileStream;
Content: TBytes;
begin
try
ConAmazon := TAmazonConnectionInfo.Create(nil);
ConAmazon.AccountKey := 'MtJqIM7WyjJA*********************';
ConAmazon.AccountName := 'AKIAIXVAH*********';
ConAmazon.QueueEndpoint := 'queue.amazonaws.com';
ConAmazon.StorageEndpoint := 's3-eu-west-1.amazonaws.com';
ConAmazon.TableEndpoint := 'sdb.amazonaws.com';
ConAmazon.UseDefaultEndpoints := True;
Service := TAmazonStorageService.Create(ConAmazon);
if FileExists(LocalFilePath) then
begin
FS := TFileStream.Create(LocalFilePath, fmOpenRead);
Content := ByteContent(FS);
FS.Free;
Result := Service.UploadObject(Bucket, RemoteFileName, Content, True, nil, nil, amzbaPrivate, nil, OnProgress);
end
else
Result := False;
finally
ConAmazon.Free;
Service.Free;
end;
end;
For downloading check this question Can I monitor the progress of an S3 download using the Cloud.AmazonAPI?
For uploading it is similar but you'll need to create TAmazonStorageService subclass as following
type
TProgressAmazonStorageService = class(TAmazonStorageService)
function IssuePutRequest(URL: string; Headers: TStringList;
QueryParameters: TStringList; const QueryPrefix: string;
ResponseInfo: TCloudResponseInfo;
Content: TStream; out ResponseString: string): TCloudHTTP; overload; override;
end;
function TProgressAmazonStorageService.IssuePutRequest(URL: string; Headers: TStringList;
QueryParameters: TStringList; const QueryPrefix: string;
ResponseInfo: TCloudResponseInfo;
Content: TStream; out ResponseString: string): TCloudHTTP;
var
ProgressStream: TProgressStream;
begin
Result := PrepareRequest('PUT', Headers, QueryParameters, QueryPrefix, URL);
try
ProgressStream := TProgressStream.Create(Content);
try
ProgressStream.OnProgress := Form1.OnProgress;
Form1.ProgressBar1.Max := Content.Size;
Form1.ProgressBar1.Value := 0;
if Content <> nil then
ResponseString := Result.Put(URL, ProgressStream)
else
ResponseString := Result.Put(URL);
finally
ProgressStream.Free;
end;
PopulateResponseInfo(Result, ResponseInfo);
except
on E: Exception do
begin
Result.Free;
Raise;
end;
end;
end;
progress function looks like this
procedure TForm1.OnProgress(const ACount: Int64);
begin
Form1.ProgressBar1.Value := Form1.ProgressBar1.Value + ACount;
Application.ProcessMessages;
end;
and TProgressStream like this
type
TOnProgressEvent = procedure(const ACount: Int64) of object;
TProgressStream = class(TStream)
strict private
FStream: TStream;
protected
function GetSize: Int64; override;
procedure SetSize(NewSize: Longint); overload; override;
procedure SetSize(const NewSize: Int64); overload; override;
public
OnProgress: TOnProgressEvent;
function Read(var Buffer; Count: Longint): Longint; overload; override;
function Write(const Buffer; Count: Longint): Longint; overload; override;
function Read(Buffer: TBytes; Offset, Count: Longint): Longint; overload; override;
function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; overload; override;
function Seek(Offset: Longint; Origin: Word): Longint; overload; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override;
constructor Create(const AStream: TStream);
end;
constructor TProgressStream.Create(const AStream: TStream);
begin
FStream := AStream;
end;
function TProgressStream.GetSize: Int64;
begin
Result := FStream.Size;
end;
procedure TProgressStream.SetSize(NewSize: Longint);
begin
FStream.Size := NewSize;
end;
procedure TProgressStream.SetSize(const NewSize: Int64);
begin
FStream.Size := NewSize;
end;
function TProgressStream.Read(var Buffer; Count: Longint): Longint;
begin
Result := FStream.Read(Buffer, Count);
end;
function TProgressStream.Write(const Buffer; Count: Longint): Longint;
begin
Result := FStream.Write(Buffer, Count);
end;
function TProgressStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint;
begin
if Assigned(OnProgress) then
begin
OnProgress(Count);
end;
Result := FStream.Read(Buffer, Offset, Count);
end;
function TProgressStream.Write(const Buffer: TBytes; Offset, Count: Longint): Longint;
begin
Result := FStream.Write(Buffer, Offset, Count);
end;
function TProgressStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
Result := FStream.Seek(Offset, Origin);
end;
function TProgressStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
Result := FStream.Seek(Offset, Origin);
end;
Is there a routine available in TAmazonStorageService to monitor the progress of a download of an object?
I read that it is possible using the AWS SDK hooking the WriteObjectProgressEvent, but I couldn't find anything related in the documentation of Embarcadero's AmazonAPI.
I don't think this is currently implemented in Delphi. What you can do is create a stream wrapper that will notify about progress of writing to it. So for example you can write following to monitor progress via ProgressBar
procedure TForm1.OnProgress(const ACount: Int64);
begin
ProgressBar1.Value := ProgressBar1.Value + ACount;
Application.ProcessMessages;
end;
procedure TForm1.DownloadFile(const ABucketName: string; const AFileName: TFileName);
var
ResponseInfo: TCloudResponseInfo;
StorageService: TAmazonStorageService;
ObjectName: string;
FileStream: TStream;
ProgressStream: TProgressStream;
MetaData: TStrings;
Properties: TStrings;
ContentLength: Int64;
begin
StorageService := TAmazonStorageService.Create(AmazonConnectionInfo1);
ResponseInfo := TCloudResponseInfo.Create;
try
ObjectName := ExtractFileName(AFileName);
if StorageService.GetObjectProperties(ABucketName, ObjectName, Properties, MetaData) then
begin
try
ContentLength := StrToInt(Properties.Values['Content-Length']);
finally
MetaData.Free;
Properties.Free;
end;
FileStream := TFileStream.Create(AFileName, fmCreate or fmOpenWrite);
ProgressStream := TProgressStream.Create(FileStream);
ProgressStream.OnProgress := OnProgress;
ProgressBar1.Max := ContentLength;
ProgressBar1.Value := 0;
try
StorageService.GetObject(CBucketName, ObjectName, ProgressStream);
finally
ProgressStream.Free;
FileStream.Free;
end;
end;
finally
StorageService.Free;
ResponseInfo.Free;
end;
end;
and TProgressStream implemented as following
type
TOnProgressEvent = procedure(const ACount: Int64) of object;
TProgressStream = class(TStream)
strict private
FStream: TStream;
protected
function GetSize: Int64; override;
procedure SetSize(NewSize: Longint); overload; override;
procedure SetSize(const NewSize: Int64); overload; override;
public
OnProgress: TOnProgressEvent;
function Read(var Buffer; Count: Longint): Longint; overload; override;
function Write(const Buffer; Count: Longint): Longint; overload; override;
function Read(Buffer: TBytes; Offset, Count: Longint): Longint; overload; override;
function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; overload; override;
function Seek(Offset: Longint; Origin: Word): Longint; overload; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override;
constructor Create(const AStream: TStream);
end;
constructor TProgressStream.Create(const AStream: TStream);
begin
FStream := AStream;
end;
function TProgressStream.GetSize: Int64;
begin
Result := FStream.Size;
end;
procedure TProgressStream.SetSize(NewSize: Longint);
begin
FStream.Size := NewSize;
end;
procedure TProgressStream.SetSize(const NewSize: Int64);
begin
FStream.Size := NewSize;
end;
function TProgressStream.Read(var Buffer; Count: Longint): Longint;
begin
Result := FStream.Read(Buffer, Count);
end;
function TProgressStream.Write(const Buffer; Count: Longint): Longint;
begin
Result := FStream.Write(Buffer, Count);
end;
function TProgressStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint;
begin
Result := FStream.Read(Buffer, Offset, Count);
end;
function TProgressStream.Write(const Buffer: TBytes; Offset, Count: Longint): Longint;
begin
Result := FStream.Write(Buffer, Offset, Count);
if Assigned(OnProgress) then
begin
OnProgress(Count);
end;
end;
function TProgressStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
Result := FStream.Seek(Offset, Origin);
end;
function TProgressStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
Result := FStream.Seek(Offset, Origin);
end;
I have custom form which is descendant from TForm. I used ToolApi to register custom module and add it to repository. So far so good. But when I click on File->New I can see my category with icon for my custom form but it is disabled. Icon is grayed and I cannot select it to create my custom form from menu and add it to project.
Do you have any suggestions and tips what is wrong and what should I try?
Click here to transfer my source code...
Thanks in advance.
Edit:
There is also listing of code for which I think it is important:
unit CustomFormFrame_Design;
interface
{$Include jvcl.inc}
uses Windows, Classes, ToolsAPI;
type
TPoCustomFormWizard = class(TNotifierObject, IOTAWizard, IOTARepositoryWizard,
IOTAFormWizard, IOTACreator, IOTAModuleCreator,
IOTARepositoryWizard60
{$IFDEF COMPILER8_UP}, IOTARepositoryWizard80 {$ENDIF COMPILER8_UP}
{$IFDEF COMPILER10_UP}, IOTAProjectWizard100 {$ENDIF COMPILER10_UP})
private
FUnitIdent: string;
FClassName: string;
FFileName: string;
protected
// IOTAWizard methods
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// IOTARepositoryWizard / IOTAFormWizard methods
function GetAuthor: string;
function GetComment: string;
function GetPage: string;
function GetGlyph: Cardinal;
// IOTACreator methods
function GetCreatorType: string;
function GetExisting: Boolean;
function GetFileSystem: string;
function GetOwner: IOTAModule;
function GetUnnamed: Boolean;
// IOTAModuleCreator methods
function GetAncestorName: string;
function GetImplFileName: string;
function GetIntfFileName: string;
function GetFormName: string;
function GetMainForm: Boolean;
function GetShowForm: Boolean;
function GetShowSource: Boolean;
function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile;
function NewImplSource(const ModuleIdent, FormIdent,
AncestorIdent: string): IOTAFile;
function NewIntfSource(const ModuleIdent, FormIdent,
AncestorIdent: string): IOTAFile;
procedure FormCreated(const FormEditor: IOTAFormEditor);
{ IOTARepositoryWizard60 }
function GetDesigner: string;
{$IFDEF COMPILER8_UP}
{ IOTARepositoryWizard80 }
function GetGalleryCategory: IOTAGalleryCategory; virtual;
function GetPersonality: string; virtual;
{$ENDIF COMPILER8_UP}
{$IFDEF COMPILER10_UP}
{ IOTAProjectWizard100 }
function IsVisible(Project: IOTAProject): Boolean;
{$ENDIF COMPILER10_UP}
{$IFDEF COMPILER8_UP}
property Personality: string read GetPersonality;
{$ENDIF}
end;
procedure Register;
implementation
uses Forms, PoCustomForm, SysUtils, DesignIntf, DesignEditors;
{$R *.res}
type
TBaseFile = class(TInterfacedObject)
private
FModuleName: string;
FFormName: string;
FAncestorName: string;
public
constructor Create(const ModuleName, FormName, AncestorName: string);
end;
TUnitFile = class(TBaseFile, IOTAFile)
protected
function GetSource: string;
function GetAge: TDateTime;
end;
TFormFile = class(TBaseFile, IOTAFile)
protected
function GetSource: string;
function GetAge: TDateTime;
end;
procedure Register;
begin
RegisterCustomModule(TPoCustomForm, TCustomModule);
RegisterPackageWizard(TPoCustomFormWizard.Create);
end;
{ TBaseFile }
constructor TBaseFile.Create(const ModuleName, FormName, AncestorName: string);
begin
inherited Create;
FModuleName := ModuleName;
FFormName := FormName;
FAncestorName := AncestorName;
end;
{ TUnitFile }
function TUnitFile.GetSource: string;
var
Text: string;
ResInstance: THandle;
HRes: HRSRC;
begin
ResInstance := FindResourceHInstance(HInstance);
HRes := FindResource(ResInstance, 'CODEGEN', RT_RCDATA);
Text := PChar(LockResource(LoadResource(ResInstance, HRes)));
SetLength(Text, SizeOfResource(ResInstance, HRes));
Result := Format(Text, [FModuleName, FFormName, FAncestorName]);
end;
function TUnitFile.GetAge: TDateTime;
begin
Result := -1;
end;
{ TFormFile }
function TFormFile.GetSource: string;
const FormText = 'object %0:s: T%0:s'#13#10'end';
begin
Result := Format(FormText, [FFormName]);
end;
function TFormFile.GetAge: TDateTime;
begin
Result := -1;
end;
{ TAppBarWizard }
{ TAppBarWizard.IOTAWizard }
function TPoCustomFormWizard.GetIDString: string;
begin
Result := 'XFORM.PoCustomForm';
end;
function TPoCustomFormWizard.GetName: string;
begin
Result := 'XFORM PoCustom Form Wizard';
end;
function TPoCustomFormWizard.GetState: TWizardState;
begin
Result := [wsEnabled];
end;
procedure TPoCustomFormWizard.Execute;
begin
(BorlandIDEServices as IOTAModuleServices).GetNewModuleAndClassName(
'PoCustomForm', FUnitIdent, FClassName, FFileName);
(BorlandIDEServices as IOTAModuleServices).CreateModule(Self);
end;
{ TPoCustomFormWizard.IOTARepositoryWizard / TPoCustomFormWizard.IOTAFormWizard }
function TPoCustomFormWizard.GetGlyph: Cardinal;
begin
Result := 0; // use standard icon
end;
function TPoCustomFormWizard.GetPage: string;
begin
Result := 'XFORM';
end;
function TPoCustomFormWizard.GetAuthor: string;
begin
Result := 'XFORM';
end;
function TPoCustomFormWizard.GetComment: string;
begin
Result := 'Creates a new PoCustom form.'
end;
{ TPoCustomFormWizard.IOTACreator }
function TPoCustomFormWizard.GetCreatorType: string;
begin
Result := '';
end;
function TPoCustomFormWizard.GetDesigner: string;
begin
Result := dVCL;
end;
{$IFDEF COMPILER8_UP}
function TPoCustomFormWizard.GetGalleryCategory: IOTAGalleryCategory;
begin
Result := (BorlandIDEServices as IOTAGalleryCategoryManager).FindCategory('Borland.Delphi.New.Expert');
end;
function TPoCustomFormWizard.GetPersonality: string;
begin
Result := sDelphiPersonality;
end;
{$ENDIF COMPILER8_UP}
{$IFDEF COMPILER10_UP}
function TPoCustomFormWizard.IsVisible(Project: IOTAProject): Boolean;
begin
Result := True;
end;
{$ENDIF COMPILER10_UP}
function TPoCustomFormWizard.GetExisting: Boolean;
begin
Result := False;
end;
function TPoCustomFormWizard.GetFileSystem: string;
begin
Result := '';
end;
function TPoCustomFormWizard.GetOwner: IOTAModule;
var
I: Integer;
ModServ: IOTAModuleServices;
Module: IOTAModule;
ProjGrp: IOTAProjectGroup;
begin
Result := nil;
ModServ := BorlandIDEServices as IOTAModuleServices;
for I := 0 to ModServ.ModuleCount - 1 do
begin
Module := ModSErv.Modules[I];
// find current project group
if CompareText(ExtractFileExt(Module.FileName), '.bpg') = 0 then
if Module.QueryInterface(IOTAProjectGroup, ProjGrp) = S_OK then
begin
// return active project of group
Result := ProjGrp.GetActiveProject;
Exit;
end;
end;
end;
function TPoCustomFormWizard.GetUnnamed: Boolean;
begin
Result := True;
end;
{ TPoCustomFormWizard.IOTAModuleCreator }
function TPoCustomFormWizard.GetAncestorName: string;
begin
Result := 'TPoCustomForm';
end;
function TPoCustomFormWizard.GetImplFileName: string;
var
CurrDir: array[0..MAX_PATH] of Char;
begin
// Note: full path name required!
GetCurrentDirectory(SizeOf(CurrDir), CurrDir);
Result := Format('%s\%s.pas', [CurrDir, FUnitIdent, '.pas']);
end;
function TPoCustomFormWizard.GetIntfFileName: string;
begin
Result := '';
end;
function TPoCustomFormWizard.GetFormName: string;
begin
Result := FClassName;
end;
function TPoCustomFormWizard.GetMainForm: Boolean;
begin
Result := False;
end;
function TPoCustomFormWizard.GetShowForm: Boolean;
begin
Result := True;
end;
function TPoCustomFormWizard.GetShowSource: Boolean;
begin
Result := True;
end;
function TPoCustomFormWizard.NewFormFile(const FormIdent,
AncestorIdent: string): IOTAFile;
begin
Result := TFormFile.Create('', FormIdent, AncestorIdent);
end;
function TPoCustomFormWizard.NewImplSource(const ModuleIdent, FormIdent,
AncestorIdent: string): IOTAFile;
begin
Result := TUnitFile.Create(ModuleIdent, FormIdent, AncestorIdent);
end;
function TPoCustomFormWizard.NewIntfSource(const ModuleIdent, FormIdent,
AncestorIdent: string): IOTAFile;
begin
Result := nil;
end;
procedure TPoCustomFormWizard.FormCreated(const FormEditor: IOTAFormEditor);
begin
// do nothing
end;
end.
A complete Repository Wizard (with custom form) is demonstrated in Bruno Fierens' whitepaper, which you can get from here: http://forms.embarcadero.com/forms/AMUSCA1104BrunoFierensOTAPIWhitepaper through Embarcadero.
The reason I'm giving you the link as opposed to just the answer is that I've noticed more than one issue with your code, you will benefit from reading through the whitepaper! It won't take you long, the demo applications come with it, and it will not only solve this one problem, but most issues you may face when playing with the OTAPI.