It is not possible to correctly write functions from MSDN in Delphi - pointers

I want to create my own filter and use it in the DirectShow library. The filter seemed to be able to write, but there was a problem with creating a graph. I based my code on the "Creating an Audio Capture Graph" article. At the very end it says that the functions from the articles "Add a Filter by CLSID" and "Connect Two Filters" are also used. I rewrote the most identical, but the code does not work.
I indicated the location of the error with many "!".
Project Project1.exe raised exception class AEccessViolation with message 'Access violation at address 0045AAA8 in module 'Project1.exe'. Read of address 00000000'. Process stopped. Use step or Run to continue.
In general, I have the following code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, DirectShow9,ActiveX,BaseClass, DirectInput,
StdCtrls,DirectSound, DirectSetup, DirectPlay8, DirectMusic,
Dialogs;
type
TForm1 = class(TForm)
Button1: TButton;
ListBox2: TListBox;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
function AddFilterByCLSID(pGraphA: IGraphBuilder; clsid: TGUID; wazName: PWideChar; ppF: IBaseFilter): HRESULT;
function ConnectFilter( pGraph: IGraphBuilder; pSrc: IBaseFilter; pdest: IBaseFilter): HRESULT;
function GetUnconnectedPin(pFilter: IBaseFilter; PinDir: PIN_DIRECTION; ppPin: IPin): HRESULT;
function ConnectFilterPin( pGraph: IGraphBuilder; pOut: IPin; pdest: IBaseFilter): HRESULT;
end;
var
PropertyName:IPropertyBag;
pSrc, pWaveDest, pWriter: IBaseFilter;
pSink: IFileSinkFilter;
pGraph: IGraphBuilder;
FMediaControl: IMediaControl;
pDevEnum: ICreateDevEnum;
pEnum: IEnumMoniker;
pMoniker: IMoniker;
MArray1,MArray2: array of IMoniker;
hr: HRESULT;
DeviceName:OleVariant;
FAudioCaptureFilter: IBaseFilter;
const
CLSID_WavDest : TGUID = '{3C78B8E2-6C4D-11d1-ADE2-0000F8754B99}';
CLSID_CRleFilter: TGUID = '{BEBCF0A3-2673-42A7-82F2-5D4FC3126171}'; //My Filter.
IID_ICRleFilter: TGUID = '{35C0AC80-C3E4-4EEA-A1F5-049401E29400}'; //Myfilter
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.AddFilterByCLSID(pGraphA: IGraphBuilder; clsid: TCLSID;
wazName: PWideChar; ppF: IBaseFilter): HRESULT;
var
pF: IBaseFilter;
begin
CoCreateInstance(clsid, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, pF);
hr:=pGraph.AddFilter(pF, WazName);
if hr<> S_OK then
begin
ShowMessage('фильтр вавдеста не добавился');
end;
PPf:= pF;
// pF._Release;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
pOut: IPin;
begin
HR:= CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, pGraph);
if hr<> S_OK then
begin
ShowMessage('Граф не создался');
end;
HR:= CoCreateInstance(CLSID_SystemDeviceEnum, NIL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, pDevEnum);
if hr<> S_OK then
begin
ShowMessage('перечеслитель не создался');
Exit;
end;
HR:=pDevEnum.CreateClassEnumerator(CLSID_AudioInputDeviceCategory, pEnum, 0);
if HR<>S_OK then EXIT;
//Обнуляем массив в списке моникеров
setlength(MArray2,0);
//Пускаем массив по списку устройств
while (S_OK=pEnum.Next(1,pMoniker,Nil)) do
begin
setlength(MArray2,length(MArray2)+1); //Увеличиваем массив на единицу
MArray2[length(MArray2)-1]:=pMoniker; //Запоминаем моникер в масиве
HR:=pMoniker.BindToStorage(NIL, NIL, IPropertyBag, PropertyName); //Линкуем моникер устройства к формату хранения IPropertyBag
if FAILED(HR) then Continue;
HR:=PropertyName.Read('FriendlyName', DeviceName, NIL); //Получаем имя устройства
if FAILED(HR) then Continue;
//Добавляем имя устройства в списки
Listbox2.Items.Add(DeviceName);
end;
Listbox2.ItemIndex:=0;
MArray2[Listbox2.ItemIndex].BindToObject(NIL, NIL, IID_IBaseFilter, FAudioCaptureFilter);
//добавляем устройство в граф фильтров
Pgraph.AddFilter(FAudioCaptureFilter, 'AudioCaptureFilter');
// pGraph.AddFilter(pSrc, 'Capture');
AddfilterByCLSID(pGraph, CLSID_CRleFilter, '_CRleFilter', pWaveDest);
ConnectFilter(pGraph, pWaveDest, pWriter); // This is where the mistakes start !!!!!!!!!!!!!!!!!
pGraph.QueryInterface(IID_IMediaControl, FMediaControl);
FMediaControl.Run();
end;
{
There is no function overloading in Delphi, so I named the functions differently
}
function TForm1.ConnectFilterPin(pGraph: IGraphBuilder; pOut: IPin;
pdest: IBaseFilter): HRESULT;
var
pIn : IPin;
begin
pIn:= nil;
GetUnconnectedPin(pdest, PINDIR_OUTPUT, pIn);
pGraph.Connect(pOut, pin);
end;
function TForm1.ConnectFilter(pGraph: IGraphBuilder; pSrc: IBaseFilter;
pdest: IBaseFilter): HRESULT;
var
pOut: IPin;
begin
//pOut:= 0;
GetUnconnectedPin(pSrc, PINDIR_OUTPUT, pOut);
ConnectFilterPin(pGraph, pOut, pdest);
end;
function TForm1.GetUnconnectedPin(pFilter: IBaseFilter;
PinDir: PIN_DIRECTION; ppPin: IPin): HRESULT;
var
pEnum: IEnumPins;
pPin: IPin;
hr: HRESULT;
ThisPinDir : PIN_DIRECTION;
pTmp: IPin;
begin
pTmp:=nil;
ppPin:= nil;
// pEnum:= nil;
pPin:= nil;
hr:= pFilter.EnumPins(pEnum); // This is where the error occurs !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
if hr<> S_OK then
begin
ShowMessage('перечесление пинов: не равно S_OK');
end;
while pEnum.Next(1, pPin, nil) = S_OK do
begin
pPin.QueryDirection(ThisPinDir);
if ThisPinDir = PinDir then
begin
hr:= pPin.ConnectedTo(pTmp);
if Succeeded(hr) then
begin
pTmp._Release;
end else
begin
pEnum._Release;
ppPin:= pPin;
Result := S_OK;
Exit;
end;
end;
end;
pPin._Release;
ShowMessage('ошибка: не правильный код');
Result:= E_FAIL;
// ShowMessage('ошибка: не правильный код');
end;
end.
I was hoping that the error was caused by pointers, or rather their absence. I tried to put them in absolutely all combinations, but this did not lead to the desired result. Besides, everywhere in Delphi pointers are not used at all. Perhaps somewhere the parameters in the functions are not correctly passed. I reviewed all the functions 5 times, tried to find errors, but this did not work. Using pointers didn't work either.
I know the error is small and easy to fix, but I can't figure out where it is.

The error was not in the pointers. The purpose of the Connect Filter function is to connect two filters. The structure looks like this ConnectFilter(Graph, Filter 1, Filter 2);. I added only one filter to the project. As a result, nothing was sent to the function instead of the second filter. There was an address reading error that was very hard to catch. Perhaps it would be easy to identify the error if I used all sorts of checks. Due to the fact that C ++ uses pointers, but Delphi does not, I thought that the error was in pointers, because I did not use them. But the fix turned out to be simpler: you need to add a second filter, and send it as the third parameter.
From these errors we can conclude:
Even in experimental code, it is worth doing as many checks as possible.
Watch and read carefully.
Functions that are called from other functions should be above these functions. There was no error, but it is indicated in the book Flenov M.E. The Bible of Delphi (third edition), chapter 5.4: Procedures and functions in Delphi. Solutions to other errors are also described there.
Unlike C++, Delphi does not need to assign nil to new variables, Delphi does it on its own.
You also don't need to call Release yourself.
I rewrote the code with the correction of all the errors I noticed. I'll leave it below:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, DirectShow9,ActiveX,BaseClass, DirectInput,
StdCtrls,DirectSound, DirectSetup, DirectPlay8, DirectMusic,
Dialogs;
type
TForm1 = class(TForm)
Button1: TButton;
ListBox2: TListBox;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
function AddFilterByCLSID(pGraphA: IGraphBuilder; clsid: TGUID; wazName: PWideChar; var ppF: IBaseFilter): HRESULT;
function ConnectFilter( pGraph: IGraphBuilder; pSrc: IBaseFilter; pdest: IBaseFilter): HRESULT;
function GetUnconnectedPin(pFilter: IBaseFilter; PinDir: PIN_DIRECTION; var ppPin: IPin): HRESULT;
function ConnectFilterPin( pGraph: IGraphBuilder; pOut: IPin; pdest: IBaseFilter): HRESULT;
end;
var
PropertyName:IPropertyBag;
pSrc, pWaveDest, pWriter: IBaseFilter;
pSink: IFileSinkFilter;
pGraph: IGraphBuilder;
FMediaControl: IMediaControl;
pDevEnum: ICreateDevEnum;
pEnum: IEnumMoniker;
pMoniker: IMoniker;
MArray1,MArray2: array of IMoniker;
hr: HRESULT;
DeviceName:OleVariant;
FAudioCaptureFilter: IBaseFilter;
const
CLSID_WavDest : TGUID = '{3C78B8E2-6C4D-11d1-ADE2-0000F8754B99}';
CLSID_CRleFilter: TGUID = '{BEBCF0A3-2673-42A7-82F2-5D4FC3126171}';
IID_ICRleFilter: TGUID = '{35C0AC80-C3E4-4EEA-A1F5-049401E29400}';
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.AddFilterByCLSID(pGraphA: IGraphBuilder; clsid: TCLSID;
wazName: PWideChar; var ppF: IBaseFilter): HRESULT;
{The last parameter of the function is returned,
for this you need to add [ var ] before the declaration.
Возвращается последний параметр функции,
для этого вам нужно добавить [ var ] перед объявлением.}
var
pf: IBaseFilter;
begin
hr:= CoCreateInstance(clsid, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, pF);
if Succeeded(hr) then // Added full error checking
begin
hr:=pGraph.AddFilter(pF, WazName);
if Succeeded(hr) then
begin
ppf:= pf;
end else
ShowMessage('фильтр добавился / Filter not added');
end else
ShowMessage('фильтр добавился / Filter not added 2');
Result:= hr;
end;
function TForm1.GetUnconnectedPin(pFilter: IBaseFilter;
PinDir: PIN_DIRECTION; var ppPin: IPin): HRESULT;
var
pEnum: IEnumPins;
pPin: IPin;
hr: HRESULT;
ThisPinDir : PIN_DIRECTION;
pTmp: IPin;
begin
//you don't need to assign [ nil ]; Delphi does it by itself.
//Не нужно явно указывать [ nil ]; Delphi делает это самостоятельно.
hr:= pFilter.EnumPins(pEnum);
if Failed(hr) then
begin
ShowMessage('перечесление пинов: ошибка / pin listing: error');
end;
while pEnum.Next(1, pPin, nil) = S_OK do
begin
pPin.QueryDirection(ThisPinDir);
if ThisPinDir = PinDir then
begin
hr:= pPin.ConnectedTo(pTmp);
if Succeeded(hr) then
begin
end else
begin
ppPin:= pPin;
Result := S_OK;
Exit;
end;
end;
end;
ShowMessage('ошибка: не правильный код / error: invalid code');
Result:= E_FAIL;
end;
function TForm1.ConnectFilterPin(pGraph: IGraphBuilder; pOut: IPin;
pdest: IBaseFilter): HRESULT;
var
pIn : IPin;
begin
GetUnconnectedPin(pdest, PINDIR_OUTPUT, pIn);
pGraph.Connect(pOut, pin);
end;
function TForm1.ConnectFilter(pGraph: IGraphBuilder; pSrc: IBaseFilter;
pdest: IBaseFilter): HRESULT;
var
pOut: IPin;
begin
GetUnconnectedPin(pSrc, PINDIR_OUTPUT, pOut);
ConnectFilterPin(pGraph, pOut, pdest);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
pOut: IPin;
begin
HR:= CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, pGraph);
if hr<> S_OK then
begin
ShowMessage('Ошибка создания графа / Graph creation error');
end;
HR:= CoCreateInstance(CLSID_SystemDeviceEnum, NIL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, pDevEnum);
if hr<> S_OK then
begin
ShowMessage('Ошибка создания графа / Graph creation error');
Exit;
end;
HR:=pDevEnum.CreateClassEnumerator(CLSID_AudioInputDeviceCategory, pEnum, 0);
if HR<>S_OK then EXIT;
//Обнуляем массив в списке моникеров / Resetting the array in the list of monikers
setlength(MArray2,0);
//Пускаем массив по списку устройств / Let's run the array through the list of devices
while (S_OK=pEnum.Next(1,pMoniker,Nil)) do
begin
setlength(MArray2,length(MArray2)+1); //Увеличиваем массив на единицу / Incrementing the array by one
MArray2[length(MArray2)-1]:=pMoniker; //Запоминаем моникер в масиве / Remembering the moniker in the array
HR:=pMoniker.BindToStorage(NIL, NIL, IPropertyBag, PropertyName); //Линкуем моникер устройства к формату хранения IPropertyBag
// Link Device Monitor to IPropertyBag Storage Format
if FAILED(HR) then Continue;
HR:=PropertyName.Read('FriendlyName', DeviceName, NIL); //Получаем имя устройства / Getting the device name
if FAILED(HR) then Continue;
//Добавляем имя устройства в списки / Adding the device name to the lists
Listbox2.Items.Add(DeviceName);
end;
Listbox2.ItemIndex:=0;
MArray2[Listbox2.ItemIndex].BindToObject(NIL, NIL, IID_IBaseFilter, FAudioCaptureFilter);
//добавляем устройство в граф фильтров / adding a device to the filter graph
Pgraph.AddFilter(FAudioCaptureFilter, 'AudioCaptureFilter');
AddfilterByCLSID(pGraph, CLSID_FileWriter, 'File Writer', pWriter);
AddfilterByCLSID(pGraph, CLSID_CRleFilter, '_CRleFilter', pWaveDest);
{The error was that [ ConnectFilter ] connects two filters, and only one was specified.
The first filter is specified in the second parameter; the second filter is
specified in the third parameter; In order for the function to execute correctly,
you must first add two filters, then use [ ConnectFilter ] }
{Ошибка заключалась в том что [ ConnectFilter ] соединяет два фильтра,
а задавался только один. Первый фильтр указан во втором параметре;
второй фильтр указывается в третьем параметре; Что бы функция выполнилась правильно,
нужно предварительно добавить два фильтра, затем использовать [ ConnectFilter ] }
ConnectFilter(pGraph, FAudioCaptureFilter, pWaveDest);
ConnectFilter(pGraph, pWaveDest, pWriter);
pGraph.QueryInterface(IID_IMediaControl, FMediaControl);
FMediaControl.Run();
end;
end.

Related

Delphi: Setting timestamp of file wrong by one hour for some files

I want to correct the last-access time of some files.
Doing that programmatically fails - by one hour - for some files.
Mysterious, as I do correct the date according to the timezone.
Here is an example, tested for Delphi2010 (that's where I would need code for) and Delphi10.3
function GetTempDir : string;
var path : array[0..MAX_PATH] of char;
PathStr : string;
begin
GetTempPath(MAX_PATH, path);
PathStr:=path;
result:=IncludeTrailingPathDelimiter(PathStr);
end;
function UTCDateTimeFromLocalDateTime(const LocalDateTime: TDateTime): TDateTime;
var
LocalSystemTime: TSystemTime;
UTCSystemTime: TSystemTime;
LocalFileTime: TFileTime;
UTCFileTime: TFileTime;
begin
DateTimeToSystemTime(LocalDateTime, UTCSystemTime);
SystemTimeToFileTime(UTCSystemTime, UTCFileTime);
if LocalFileTimeToFileTime(UTCFileTime, LocalFileTime)
and FileTimeToSystemTime(LocalFileTime, LocalSystemTime) then begin
Result := SystemTimeToDateTime(LocalSystemTime);
end else begin
Result := LocalDateTime;
end;
end;
function SetFileTimesHelper(const FileName: string; DateTime: TDateTime): Boolean;
var
Handle: THandle;
FileTime: TFileTime;
SystemTime: TSystemTime;
begin
Result := False;
Handle := CreateFile(PChar(FileName), GENERIC_WRITE, FILE_SHARE_READ, nil,
OPEN_EXISTING, 0, 0);
if Handle <> INVALID_HANDLE_VALUE then
try
DateTime:=UTCDateTimeFromLocalDateTime(DateTime);
DateTimeToSystemTime(DateTime, SystemTime);
FileTime.dwLowDateTime := 0;
FileTime.dwHighDateTime := 0;
if SystemTimeToFileTime(SystemTime, FileTime) then
begin
Result := SetFileTime(Handle, nil, nil, #FileTime);
end;
finally
CloseHandle(Handle);
end;
end;
procedure TForm1.CreateDateFile(Dat : TDateTime);
var FileName : string;
begin
FileName:=GetTempDir+FormatDateTime('yyyymmdd hhnnss', Dat)+'.txt';
Memo1.Lines.Add(FileName);
with TStringList.Create do
begin
text:='1';
SaveToFile(FileName);
Free;
end;
SetFileTimesHelper(FileName, Dat);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
CreateDateFile(StrToDateTime('23.02.2013 11:11:11'));
CreateDateFile(StrToDateTime('06.05.2014 22:22:22')); // file dated 23:22:22
end;
The first file is correctly dated, the 2nd is displayed in the windows explorer with 23:22:22.
What do I miss?
Thanks brian for the comment.
Digging that way I found a solution:
GetTimeZoneInformation(tz);
SystemTimeToTzSpecificLocalTime(#tz, UTCSystemTime, LtSystemTime);
I didn't find the inverse function of SystemTimeToTzSpecificLocalTime, but I used this function and then see the resulting offset. Then I invert the offset and the time is properly corrected.

using TfrxRect and GetRealBounds in a script

i am tying to use the script
procedure BarCodeResize(Bc : TfrxBarCodeView);
var
r : TfrxRect;
begin
bc.Text := '1234567890 test 1234567890 asdf 1234567890';
r := bc.GetRealBounds;
bc.Zoom := bc.Width/(r.Right - r.Left);
frxReport1.ShowReport();
end;
but it shows me an error at line 'r : TfrxRect;'
Explanation:
If I understand your question correctly, you may consider the following:
If you want to use a type, you need to call AddType() method of TfsScript class.
In FastScript, only published methods of added classes are implemented. If you want to use public method (GetRealBounds for example), you need to use AddMethod() method of TfsScript.
The problem with your script (even if you add type TfrxRect) is, that TfrxRect is packed record, and FastScript do not support records. So, you may define your own TfrxBarCodeView class with appropriate methods.
Тхе example белов demonstrates one possible way to solve your problem (it uses Delphi, but I was able to reproduce and solve your error).
Delphi part:
unit FMainBarcode;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, frxClass, fs_iinterpreter, frxBarcode;
type
TForm1 = class(TForm)
Button1: TButton;
frxReport1: TfrxReport;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TfrxMyBarCodeView = class(TfrxBarCodeView)
public
constructor Create(AOwner: TComponent); override;
function GetRealBoundsRight: double;
function GetRealBoundsLeft: double;
end;
type
TFunctions = class(TfsRTTIModule)
private
function CallMethod(Instance: TObject; ClassType: TClass; const MethodName: String; Caller: TfsMethodHelper): Variant;
public
constructor Create(AScript: TfsScript); override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
// Show report
frxReport1.ShowReport;
end;
{ TMyfrxBarCodeView }
constructor TfrxMyBarCodeView.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
function TfrxMyBarCodeView.GetRealBoundsLeft: double;
begin
Result := GetRealBounds.Left;
end;
function TfrxMyBarCodeView.GetRealBoundsRight: double;
begin
Result := GetRealBounds.Right;
end;
{ TFunctions }
constructor TFunctions.Create(AScript: TfsScript);
begin
inherited Create(AScript);
// Add type
AScript.AddType('TfrxRect', fvtVariant);
// Add public method
with AScript.FindClass('TfrxBarCodeView') do begin
AddMethod('function GetRealBounds: TfrxRect', CallMethod);
end{with};
// Add class and public methods
with AScript.AddClass(TfrxMyBarCodeView, 'TfrxBarCodeView') do begin
AddMethod('function GetRealBoundsRight: double', CallMethod);
AddMethod('function GetRealBoundsLeft: double', CallMethod);
end{with};
end;
function TFunctions.CallMethod(Instance: TObject; ClassType: TClass; const MethodName: String; Caller: TfsMethodHelper): Variant;
begin
Result := 0;
if (ClassType = TfrxBarCodeView) and (MethodName = 'GETREALBOUNDS') then begin
//Result := TfrxBarCodeView(Instance).GetRealBounds;
end{if};
if (ClassType = TfrxMyBarCodeView) and (MethodName = 'GETREALBOUNDSRIGHT') then begin
Result := TfrxMyBarCodeView(Instance).GetRealBoundsRight;
end{if};
if (ClassType = TfrxMyBarCodeView) and (MethodName = 'GETREALBOUNDSLEFT') then begin
Result := TfrxMyBarCodeView(Instance).GetRealBoundsLeft;
end{if};
end;
initialization
fsRTTIModules.Add(TFunctions);
end.
Fast report code:
procedure BarCodeResize(Bc : TfrxMyBarCodeView);
var
R, L : double;
begin
Bc.Text := '1234567890 test 1234567890 asdf 1234567890';
R := Bc.GetRealBoundsRight * fr1CharX / fr01cm;
L := Bc.GetRealBoundsLeft * fr1CharX / fr01cm;
Bc.Zoom := Bc.Width / (R - L);
ShowMessage('Right: ' + FloatToStr(R) + ', Left: ' + FloatToStr(L));
end;
procedure Page1OnBeforePrint(Sender: TfrxComponent);
begin
BarCodeResize(TfrxMyBarCodeView(BarCode1));
end;
begin
end.
Notes:
Tested with Delphi 7, FastReport 4.7, FastScript 1.7.

How can I find the `'First` and `'Last` expressions for a string field in a record using ASIS

I'm using ASIS to analyse a big Ada project. One of the things I need to do is to find the 'First and 'Last expressions for a string field in a record variable.
My problem occurs when I have a Discrete_Range, which is not A_Discrete_Simple_Expression_Range (for which one can use the functions Lower_Bound and Upper_Bound directly), but instead A_Discrete_Range_Attribute_Reference.
The source example which I'm analysing basically looks like this:
with Ada.Text_IO;
procedure Minimal_Example is
type R is
record
F : String (1 .. 5);
end record;
V : R;
subtype S is String (V.F'Range); -- It would have been nice if they didn't do like this.
function F return S is ("12345");
begin
Ada.Text_IO.Put_Line (F);
end Minimal_Example;
Here is a minimised version of the program I use to perform the analysis:
-- Standard library packages:
with Ada.Wide_Text_IO;
-- ASIS packages:
with Asis;
with Asis.Ada_Environments;
with Asis.Compilation_Units;
with Asis.Declarations;
with Asis.Definitions;
with Asis.Elements;
with Asis.Expressions;
with Asis.Implementation;
with Asis.Iterator;
with Asis.Statements;
with Asis.Text;
procedure Minimal_Analyzer is
procedure Pre_Operation (Element : in Asis.Element;
Control : in out Asis.Traverse_Control;
State : in out Boolean) is
pragma Unreferenced (Control, State);
use all type Asis.Element_Kinds;
use all type Asis.Statement_Kinds;
begin
if Asis.Elements.Element_Kind (Element) = A_Statement and then
Asis.Elements.Statement_Kind (Element) = A_Procedure_Call_Statement
then
for Parameter_Association of Asis.Statements.Call_Statement_Parameters (Statement => Element,
Normalized => True) loop
declare
Actual_Parameter : Asis.Element;
Type_Of_Expression : Asis.Element;
Type_Definition : Asis.Definition;
Constraint : Asis.Constraint;
begin
Actual_Parameter := Asis.Expressions.Actual_Parameter (Parameter_Association);
Type_Of_Expression := Asis.Expressions.Corresponding_Expression_Type (Actual_Parameter);
Type_Definition := Asis.Declarations.Type_Declaration_View (Declaration => Type_Of_Expression);
Constraint := Asis.Definitions.Subtype_Constraint (Type_Definition);
for Index_Range of Asis.Definitions.Discrete_Ranges (Constraint) loop
declare
Range_Attribute : Asis.Definition;
Range_Prefix : Asis.Element;
begin
Range_Attribute := Asis.Definitions.Range_Attribute (Index_Range);
Range_Prefix := Asis.Expressions.Prefix (Range_Attribute);
Ada.Wide_Text_IO.Put_Line (Asis.Elements.Debug_Image (Range_Prefix));
Ada.Wide_Text_IO.Put_Line (Asis.Text.Element_Image (Range_Prefix));
end;
end loop;
end;
end loop;
end if;
end Pre_Operation;
procedure Post_Operation (Element : in Asis.Element;
Control : in out Asis.Traverse_Control;
State : in out Boolean) is null;
procedure Traverse_Declaration is
new Asis.Iterator.Traverse_Element (State_Information => Boolean,
Pre_Operation => Pre_Operation,
Post_Operation => Post_Operation);
Context : Asis.Context;
begin
Asis.Implementation.Initialize ("");
Asis.Ada_Environments.Associate (The_Context => Context,
Name => "CLPG",
Parameters => "-CA -FM");
Asis.Ada_Environments.Open (The_Context => Context);
Analyze :
declare
Complation_Unit_Body : Asis.Compilation_Unit;
Complation_Unit_Body_Declaration : Asis.Declaration;
Process_Control : Asis.Traverse_Control := Asis.Continue;
State : Boolean := False;
begin
Complation_Unit_Body := Asis.Compilation_Units.Compilation_Unit_Body (Name => "Minimal_Example",
The_Context => Context);
Complation_Unit_Body_Declaration := Asis.Elements.Unit_Declaration (Compilation_Unit => Complation_Unit_Body);
Traverse_Declaration (Element => Complation_Unit_Body_Declaration,
Control => Process_Control,
State => State);
end Analyze;
Asis.Ada_Environments.Close (The_Context => Context);
Asis.Ada_Environments.Dissociate (The_Context => Context);
Asis.Implementation.Finalize (Parameters => "");
end Minimal_Analyzer;
Project file:
with "asis";
project Build is
for Main use ("minimal_analyzer.adb",
"minimal_example.adb");
for Source_Dirs use (".");
for Object_Dir use "obj";
for Exec_Dir use "bin";
package Builder is
for Default_Switches ("Ada") use ("-m", -- Do not recompile if only comments have changed
"-s", -- Recompile if switches change
"-j0"); -- Build concurrently
end Builder;
package Compiler is
for Default_Switches ("Ada") use ("-gnatoU",
"-gnat2012",
"-funwind-tables",
"-fstack-check",
"-gnata");
end Compiler;
end Build;
Build command:
gprbuild -j0 -p -P build.gpr
You need to have ASIS installed to build the tool. If you run minimal_analyzer from the directory where minimal_example.adb is located, you get the output:
Element Debug_Image:
A_SELECTED_COMPONENT
located in Minimal_Example (body, Unit_Id = 2, Context_Id = 1)
text position : minimal_example.adb:8:40
Nodes:
Node : 2332 - N_SELECTED_COMPONENT
R_Node : 2332 - N_SELECTED_COMPONENT
Node_Field_1 : 0 - N_EMPTY
Node_Field_2 : 0 - N_EMPTY
Rel_Sloc : 157
obtained from the tree /tmp/minimal_example.adt (Tree_Id = 1)
V.F
... but how can I get to the definition of V.F, so I can extract the Discrete_Simple_Expression_Range 1 .. 5?
I found a solution:
The trick is to know when to use ASIS.Expressions.Corresponding_Name_Declaration...
-- Standard library packages:
with Ada.Wide_Text_IO;
-- ASIS packages:
with ASIS;
with ASIS.Ada_Environments;
with ASIS.Compilation_Units;
with ASIS.Declarations;
with ASIS.Definitions;
with ASIS.Elements;
with ASIS.Expressions;
with ASIS.Implementation;
with ASIS.Iterator;
with ASIS.Statements;
with ASIS.Text;
procedure Minimal_Analyzer is
procedure Pre_Operation (Element : in ASIS.Element;
Control : in out ASIS.Traverse_Control;
State : in out Boolean) is
pragma Unreferenced (Control, State);
use all type ASIS.Element_Kinds;
use all type ASIS.Statement_Kinds;
begin
if ASIS.Elements.Element_Kind (Element) = A_Statement and then
ASIS.Elements.Statement_Kind (Element) = A_Procedure_Call_Statement
then
for Parameter_Association of ASIS.Statements.Call_Statement_Parameters (Statement => Element,
Normalized => True) loop
declare
Actual_Parameter : ASIS.Element;
Type_Of_Expression : ASIS.Element;
Type_Definition : ASIS.Definition;
Constraint : ASIS.Constraint;
begin
Actual_Parameter := ASIS.Expressions.Actual_Parameter (Parameter_Association);
Type_Of_Expression := ASIS.Expressions.Corresponding_Expression_Type (Actual_Parameter);
Type_Definition := ASIS.Declarations.Type_Declaration_View (Declaration => Type_Of_Expression);
Constraint := ASIS.Definitions.Subtype_Constraint (Type_Definition);
for Index_Range of ASIS.Definitions.Discrete_Ranges (Constraint) loop
declare
Range_Attribute : ASIS.Definition;
Range_Prefix : ASIS.Element;
Field_Name : ASIS.Defining_Name;
Field_Declaration : ASIS.Element;
Field_Definition : ASIS.Definition;
Field_Type_Definition : ASIS.Definition;
Constraint : ASIS.Constraint;
begin
Range_Attribute := ASIS.Definitions.Range_Attribute (Index_Range);
Range_Prefix := ASIS.Expressions.Prefix (Range_Attribute);
Field_Name := ASIS.Expressions.Selector (Range_Prefix);
Field_Declaration := ASIS.Expressions.Corresponding_Name_Declaration (Field_Name);
Field_Definition := ASIS.Declarations.Object_Declaration_View (Field_Declaration);
Field_Type_Definition := ASIS.Definitions.Component_Definition_View (Component_Definition => Field_Definition);
Constraint := ASIS.Definitions.Subtype_Constraint (Field_Type_Definition);
for Index_Range of ASIS.Definitions.Discrete_Ranges (Constraint) loop
declare
First, Last : ASIS.Expression;
begin
First := ASIS.Definitions.Lower_Bound (Index_Range);
Last := ASIS.Definitions.Upper_Bound (Index_Range);
Ada.Wide_Text_IO.Put_Line (ASIS.Elements.Debug_Image (First));
Ada.Wide_Text_IO.Put_Line (ASIS.Text.Element_Image (First));
Ada.Wide_Text_IO.Put_Line (ASIS.Elements.Debug_Image (Last));
Ada.Wide_Text_IO.Put_Line (ASIS.Text.Element_Image (Last));
end;
end loop;
end;
end loop;
end;
end loop;
end if;
end Pre_Operation;
procedure Post_Operation (Element : in ASIS.Element;
Control : in out ASIS.Traverse_Control;
State : in out Boolean) is null;
procedure Traverse_Declaration is
new ASIS.Iterator.Traverse_Element (State_Information => Boolean,
Pre_Operation => Pre_Operation,
Post_Operation => Post_Operation);
Context : ASIS.Context;
begin
ASIS.Implementation.Initialize ("");
ASIS.Ada_Environments.Associate (The_Context => Context,
Name => "CLPG",
Parameters => "-CA -FM");
ASIS.Ada_Environments.Open (The_Context => Context);
Analyze :
declare
Complation_Unit_Body : ASIS.Compilation_Unit;
Complation_Unit_Body_Declaration : ASIS.Declaration;
Process_Control : ASIS.Traverse_Control := ASIS.Continue;
State : Boolean := False;
begin
Complation_Unit_Body := ASIS.Compilation_Units.Compilation_Unit_Body (Name => "Minimal_Example",
The_Context => Context);
Complation_Unit_Body_Declaration := ASIS.Elements.Unit_Declaration (Compilation_Unit => Complation_Unit_Body);
Traverse_Declaration (Element => Complation_Unit_Body_Declaration,
Control => Process_Control,
State => State);
end Analyze;
ASIS.Ada_Environments.Close (The_Context => Context);
ASIS.Ada_Environments.Dissociate (The_Context => Context);
ASIS.Implementation.Finalize (Parameters => "");
end Minimal_Analyzer;

what is the best approach to make an http request

what is the best method to do this: i have a txt file filled with web addresses, i have to check all of them using idHTTP component,only a simple check from a web server, downloading the html and finding a match, i want it to to be fast, there is different types of threads and i am not sure what is the best to use, a TParallel for or Task threads or regular threads?
i tried before TParallel for and i got stuck at AV, also i've tried Task threads but its not fast, the http request becomes slower by time, i tried also the regular threads and i didnt know how to use it because its complicated to use.
note: Please do NOT downvote i just need advice from the experts. Thank you
First advice: do not use Indy. Use THTTPClient (unit System.Net.HttpClient) -- native for Delphi XE?+
I am still using old TThreads. I could make suggestion only with TThread.
Workflow:
Main thread -- reading your TXT file line by line.
After line was readed, you create NEW thread, which are downloading information from WWW.
Sample of application:
unit ufmMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants,
{ TThread }
System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TLoad = class(TThread)
protected
FURL,
FOutputFileName: String;
procedure Execute; override;
public
constructor Create(const AURL, AOutputFileName: String); overload;
end;
HTTP = class
public
class procedure Get(const AURL: String; out AOutputStream: TMemoryStream);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
{ THTTPClient }
System.Net.HttpClient;
procedure TForm1.Button1Click(Sender: TObject);
var
LLoad: TLoad;
LFile: TextFile;
LCycle: Integer;
LUrl: String;
begin
LCycle := 0;
AssignFile(LFile, 'urls.txt');
try
Reset(LFile);
while not Eof(LFile) do
begin
{ Using for generate file name. All file names must be unique }
Inc(LCycle);
{ Read next URL }
ReadLn(LFile, LUrl);
{ Create new thread }
LLoad := TLoad.Create(LUrl, 'Output file No ' + LCycle.ToString + '.htm');
LLoad.FreeOnTerminate := True;
LLoad.Start;
end;
finally
CloseFile(LFile);
end;
end;
{ TLoad }
constructor TLoad.Create(const AURL, AOutputFileName: String);
begin
inherited Create(True);
FURL := AURL;
FOutputFileName := AOutputFileName;
end;
procedure TLoad.Execute;
var
LResponse: TMemoryStream;
begin
inherited;
LResponse := TStringStream.Create;
try
HTTP.Get(FURL, LResponse);
{ Save result to file }
LResponse.SaveToFile(GetCurrentDir + PathDelim + FOutputFileName);
finally
LResponse.Free;
end;
end;
{ HTTP }
class procedure HTTP.Get(const AURL: String; out AOutputStream: TMemoryStream);
var
LStream: TStream;
LHTTPClient: THTTPClient;
begin
LHTTPClient := THTTPClient.Create;
try
LStream := LHTTPClient.Get(AURL).ContentStream;
AOutputStream.CopyFrom(LStream, LStream.Size);
finally
LHTTPClient.Free;
end;
end;
end.
Why I against Indy:
1) THTTPClient do not required additional DLL for works with SSL protocol
2) THTTPClient is modern from Delphi XE8
3) My subjective opinion: THTTPClient works much more smoothly (with less issues) then Indy library. I used Indy for last 10 years, but now all my supported project moved to THTTPClient.
You can use TTask and Indy (TIdHTTP). Example:
function GetUrl(const aUrl: string): ITask;
begin
Result := TTask.Run(
procedure
var
FOutput: string;
FHTTP: TIdHTTP;
begin
FHTTP:=TIdHTTP.Create(nil);
try
try
FOutput:=FHTTP.Get(aUrl);
except
// handle errors
end;
finally
FHTTP.Free;
end;
TThread.Synchronize(nil,
procedure
begin
ProcessOutput(FOutput); // send your output/result to main thread
end);
end );
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
list: TStringList;
begin
list:=TStringList.Create;
try
list.LoadFromFile('yourfile.txt');
// get all your urls
// you should control how many threads run at the same time
for i := 0 to list.Count-1 do
GetUrl(list[i]);
finally
list.Free;
end;
end;

Could not convert variant of type (Unknown) into type (Dispatch)

I´m trying to port the following javascript code to inno-setup pascal script:
var adminManager = new ActiveXObject('Microsoft.ApplicationHost.AdminManager');
var appPoolsSection = adminManager.GetAdminSection('system.applicationHost/applicationPools', 'MACHINE/WEBROOT/APPHOST');
var appPoolsCollection = applicationPoolsSection.Collection;
for (var i = 0; i < appPoolsCollection.Count; i++)
{
var appPool = appPoolsCollection.Item(i);
// doing someting with the application pool
}
This code has been translated to this:
var AdminManager, AppPoolsSection, AppPoolsCollection, AppPool: Variant;
i: Integer;
begin
AdminManager := CreateOleObject('Microsoft.ApplicationHost.AdminManager');
AppPoolsSection := AdminManager.GetAdminSection('system.applicationHost/applicationPools', 'MACHINE/WEBROOT/APPHOST');
AppPoolsCollection := AppPoolsSection.Collection;
for i := 0 to AppPoolsCollection.Count-1 do begin
AppPool := AppPoolsCollection.Item(i);
// doing someting with the application pool
end;
end;
But it is raising the following error on line AppPoolsCollection := AppPoolsSection.Collection:
Exception: Could not convert variant of type (Unknown) into type (Dispatch).
There are any thing that I can do to inform to pascal scritp that the AppPoolsSection object is an IDispach and not only an IUnknown?
I found a solution that works and is simpler than "import" the interfaces definition.
All the COM components used in this code implements IDispatch (it is required to use on VBScript or JScript), then I imported the VariantChangeType function to cast the IUnknown reference to an IDispatch reference (as it appears not to be supported on pascal script).
Follow the final code:
function VariantChangeType(out Dest: Variant; Source: Variant; Flags, vt: Word): HRESULT; external 'VariantChangeType#oleaut32.dll stdcall';
function VarToDisp(Source: Variant): Variant;
begin
Result := Unassigned;
OleCheck(VariantChangeType(Result, Source, 0, varDispatch));
end;
procedure EnumerateAppPools(AppPools: TStrings);
var AdminManager, Section, Collection, Item, Properties: Variant;
i: Integer;
begin
AdminManager := CreateOleObject('Microsoft.ApplicationHost.AdminManager');
Section := VarToDisp(AdminManager.GetAdminSection('system.applicationHost/applicationPools', 'MACHINE/WEBROOT/APPHOST'));
Collection := VarToDisp(Section.Collection);
for i := 0 to Collection.Count-1 do begin
Item := VarToDisp(Collection.Item(i));
Properties := VarToDisp(Item.Properties);
if (VarToDisp(Properties.Item('managedPipelineMode')).Value = 1) and
(VarToDisp(Properties.Item('managedRuntimeVersion')).Value = 'v4.0') then
AppPools.Add(VarToDisp(Properties.Item('name')).Value);
end;
end;

Resources