Following code does not compile because my Delphi 7 complains about undeclared identifier PSYSTEM_THREAD = ^SYSTEM_THREAD. What UNIT am I missing from USES clause? ......................................................................................................................................................
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
private
{ Private declarations }
public
{ Public declarations }
end;
type
PSYSTEM_THREAD = ^SYSTEM_THREAD
var
Form1: TForm1;
function NtQuerySystemInformation(SystemInformationClass:DWORD; SystemInformation:pointer; SystemInformationLength:DWORD; ReturnLength:PDWORD):cardinal; stdcall; external 'ntdll';
implementation
{$R *.dfm}
function IsThreadSuspended:Boolean;
var spi:PSYSTEM_PROCESS_INFORMATION;
crt:PSYSTEM_PROCESS_INFORMATION;
PThreadInfo:PSYSTEM_THREAD;
Size:DWORD;
j:Integer;
LastProcess:Boolean;
begin
Result:=False; // Default result, will be also returned if any error arises.
// If process ID is 0 then we use current process ID
If AProcessID=0 then AProcessID:=GetCurrentProcessId;
if (NtQuerySystemInformation(5,nil,0,#Size)=STATUS_INFO_LENGTH_MISMATCH) and (Size>0) then
begin
GetMem(spi,Size);
try
if NtQuerySystemInformation(5,spi,Size,#Size)=0 then
begin
crt:=spi;
LastProcess:=False;
While not LastProcess do
begin
LastProcess:=crt^.NextEntryOffset = 0;
if crt^.ProcessID=AProcessID then
begin
for j := 0 to crt^.NumberOfThreads-1 do
begin
PThreadInfo:=PSYSTEM_THREAD(#crt^.ThreadInfo[j]);
if PThreadInfo^.UniqueThread = AThreadID then
begin
if PThreadInfo^.WaitReason = 5 then Exit(True) // the thread is suspended
else Exit(False); // the thread is not suspended
Break;
end;
end;
Break;
end;
crt := Pointer(DWORD(crt) + crt^.NextEntryOffset);
end;
end else Exit; // failed listing processes!
finally
FreeMem(spi);
end;
end
else Exit; // failed listing processes!
end;
end.
Declaration
type
PSYSTEM_THREADS = ^SYSTEM_THREADS;
SYSTEM_THREADS = packed record
KernelTime: LARGE_INTEGER;
UserTime: LARGE_INTEGER;
CreateTime: LARGE_INTEGER;
WaitTime: ULONG;
StartAddress: Pointer;
UniqueProcess: DWORD;
UniqueThread: DWORD;
Priority: Integer;
BasePriority: Integer;
ContextSwitchCount: ULONG;
State: Longint;
WaitReason: Longint;
end;
type
PSYSTEM_PROCESS_INFORMATION = ^SYSTEM_PROCESS_INFORMATION;
SYSTEM_PROCESS_INFORMATION = packed record
NextOffset: ULONG;
ThreadCount: ULONG;
Reserved1: array [0..5] of ULONG;
CreateTime: FILETIME;
UserTime: FILETIME;
KernelTime: FILETIME;
ModuleNameLength: WORD;
ModuleNameMaxLength: WORD;
ModuleName: PWideChar;
BasePriority: ULONG;
ProcessID: ULONG;
InheritedFromUniqueProcessID: ULONG;
HandleCount: ULONG;
Reserved2 : array[0..1] of ULONG;
PeakVirtualSize : ULONG;
VirtualSize : ULONG;
PageFaultCount : ULONG;
PeakWorkingSetSize : ULONG;
WorkingSetSize : ULONG;
QuotaPeakPagedPoolUsage : ULONG;
QuotaPagedPoolUsage : ULONG;
QuotaPeakNonPagedPoolUsage : ULONG;
QuotaNonPagedPoolUsage : ULONG;
PageFileUsage : ULONG;
PeakPageFileUsage : ULONG;
PrivatePageCount : ULONG;
ReadOperationCount : LARGE_INTEGER;
WriteOperationCount : LARGE_INTEGER;
OtherOperationCount : LARGE_INTEGER;
ReadTransferCount : LARGE_INTEGER;
WriteTransferCount : LARGE_INTEGER;
OtherTransferCount : LARGE_INTEGER;
ThreadInfo: array [0..0] of SYSTEM_THREADS;
end;
function NtQuerySystemInformation(SystemInformationClass:DWORD; SystemInformation:pointer; SystemInformationLength:DWORD; ReturnLength:PDWORD):cardinal; stdcall; external 'ntdll';
Function
function IsProcessSuspended(PID:Cardinal):Boolean;
var spi:PSYSTEM_PROCESS_INFORMATION;
crt:PSYSTEM_PROCESS_INFORMATION;
PThreadInfo:PSYSTEM_THREADS;
Size:DWORD;
LastProcess:Boolean;
const
STATUS_INFO_LENGTH_MISMATCH = $C0000004;
begin
Result:=False;
if (NtQuerySystemInformation(5,nil,0,#Size)=STATUS_INFO_LENGTH_MISMATCH) and (Size>0) then
begin
GetMem(spi,Size);
try
if NtQuerySystemInformation(5,spi,Size,#Size)=0 then
begin
crt:=spi;
LastProcess:=False;
While LastProcess=false do
begin
LastProcess:=crt^.NextOffset=0;
if crt^.ProcessID=PID then
begin
PThreadInfo:=PSYSTEM_THREADS(#crt^.ThreadInfo[0]); //check only main thread
if PThreadInfo^.WaitReason=5 then Result:=True else Result:=False;
Break;
end;
crt:=Pointer(DWORD(crt)+crt^.NextOffset);
end;
end
else Exit; // failed listing processes!
finally
FreeMem(spi);
end;
end
else Exit; // failed listing processes!
end;
Related
I need to get the output of the execution of my script and integrate it into the installer, everything works well, but I need to add a "cancel" button, it will not be difficult to add a button, but during the execution of the timer, the entire installer window is blocked, but I can not minimize to tray and press any button.
function SetTimer(
Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
external 'SetTimer#user32.dll stdcall';
function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
external 'KillTimer#user32.dll stdcall';
var
ProgressFileName: string;
function BufferToAnsi(const Buffer: string): AnsiString;
var
W: Word;
I: Integer;
begin
SetLength(Result, Length(Buffer) * 2);
for I := 1 to Length(Buffer) do
begin
W := Ord(Buffer[I]);
Result[(I * 2)] := Chr(W shr 8); { high byte }
Result[(I * 2) - 1] := Chr(Byte(W)); { low byte }
end;
end;
procedure UpdateProgress;
var
S: AnsiString;
I, L, Max: Integer;
Buffer: string;
Stream: TFileStream;
Lines: TStringList;
begin
if not FileExists(ProgressFileName) then
begin
Log(Format('Progress file %s does not exist', [ProgressFileName]));
end
else
begin
try
Stream := TFileStream.Create(ProgressFileName, fmOpenRead or fmShareDenyNone);
try
L := Stream.Size;
Max := 100*2014;
if L > Max then
begin
Stream.Position := L - Max;
L := Max;
end;
SetLength(Buffer, (L div 2) + (L mod 2));
Stream.ReadBuffer(Buffer, L);
S := BufferToAnsi(Buffer);
finally
Stream.Free;
end;
except
Log(Format('Failed to read progress from file %s - %s', [
ProgressFileName, GetExceptionMessage]));
end;
end;
if S <> '' then
begin
Log('Progress len = ' + IntToStr(Length(S)));
Lines := TStringList.Create();
Lines.Text := S;
for I := 0 to Lines.Count - 1 do
begin
if I < ProgressListBox.Items.Count then
begin
ProgressListBox.Items[I] := Lines[I];
end
else
begin
ProgressListBox.Items.Add(Lines[I]);
end
end;
ProgressListBox.ItemIndex := ProgressListBox.Items.Count - 1;
ProgressListBox.Selected[ProgressListBox.ItemIndex] := False;
Lines.Free;
end;
ProgressPage.SetProgress(0, 1);
end;
procedure UpdateProgressProc(
H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
begin
UpdateProgress;
end;
procedure RunInstallBatInsideProgress;
var
ResultCode: Integer;
Timer: LongWord;
AppPath: string;
AppError: string;
Command: string;
begin
ProgressPage :=
CreateOutputProgressPage(
'Installing something', 'Please wait until this finishes...');
ProgressPage.Show();
ProgressListBox := TNewListBox.Create(WizardForm);
ProgressListBox.Parent := ProgressPage.Surface;
ProgressListBox.Top := 0;
ProgressListBox.Left := 0;
ProgressListBox.Width := ProgressPage.SurfaceWidth;
ProgressListBox.Height := ProgressPage.SurfaceHeight;
ProgressPage.ProgressBar.Top := -100;
try
Timer := SetTimer(0, 0, 250, CreateCallback(#UpdateProgressProc));
AppPath := ExpandConstant('{app}\installer.py');
ProgressFileName := ExpandConstant('{app}\tmp\installer-progress.log');
Log(Format('Expecting progress in %s', [ProgressFileName]));
Command := Format('""%s" > "%s""', [AppPath, ProgressFileName]);
if not Exec(ExpandConstant('{cmd}'), '/c ' + Command + ExpandConstant(' {app}\PIPE'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
begin
AppError := 'Cannot start app';
end
else
if ResultCode <> 0 then
begin
AppError := Format('App failed with code %d', [ResultCode]);
end;
UpdateProgress;
finally
KillTimer(0, Timer);
ProgressPage.Hide;
DeleteFile(ProgressFileName);
ProgressPage.Free();
end;
if AppError <> '' then
begin
RaiseException(AppError);
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep=ssPostInstall then
begin
RunInstallBatInsideProgress;
end
end;
enter image description here
I'm new to delphi and am trying to start OOP. However I get an access violation when using public property to set a private field.
type
User = class;
TData = class
private
CurrUser: User;
Connection: TFDConnection;
Query: TFDQuery;
procedure SetUser(newUser: User);
procedure SetConnection(newConn: TFDConnection);
procedure SetQuery(newQry: TFDQuery);
public
property CUser: User read CurrUser write SetUser;
property Conn: TFDConnection read Connection write SetConnection;
property Qry: TFDQuery read Query write SetQuery;
class procedure Login(uID: integer); static;
class procedure Logout(uID: integer); static;
class procedure ExitApp(); static;
end;
implementation
{$R *.fmx}
procedure TData.SetUser(newUser: User);
begin
CurrUser := newUser;
end;
procedure TData.SetConnection(newConn: TFDConnection);
begin
Connection := newConn;
end;
procedure TData.SetQuery(newQry: TFDQuery);
begin
Query := newQry;
end;
I expect to be able to set the Connection using that property however it gives me the access violation with any code that uses the property write:
TData.Conn.LoginPrompt := False;
TData.Conn.Connected := True;
var
TData: frmData.TData;
LoginForm: TLoginForm;
ErrorCount : integer;
implementation
{$R *.fmx}
procedure TLoginForm.ExitAppButtonClick(Sender: TObject);
begin
TData.ExitApp;
end;
procedure TLoginForm.LoginButtonClick(Sender: TObject);
var
companyPath : string;
nurseID : integer;
begin
if(UsernameInput.Text = '') or (PasswordInput.Text = '') or (PincodeInput.Text = '') then
begin
ShowMessage('Please enter your login details.');
Exit;
end;
try
TData.Conn := TFDConnection.Create(nil);
TData.Conn.Params.DriverID := 'MSAcc';
TData.Conn.Params.Database := 'D:\PulseDB\AlfaPersonnel\Pulse.mdb';
TData.Conn.LoginPrompt := False;
TData.Conn.Connected := True;
if(TData.Conn.Connected <> True) then
begin
ShowMessage('Could not connect, try again');
Exit;
end
else //When Connection
begin
TData.Qry := TFDQuery.Create(TData.Conn);
try
TData.Qry.Connection := TData.Conn;
TData.Qry.SQL.Text := 'SELECT * FROM NurseLogin WHERE Username=:uname AND Password=:pword AND PinCode=:pin;';
TData.Qry.Params.ParamByName('uname').AsString := UsernameInput.Text;
TData.Qry.Params.ParamByName('pword').AsString := PasswordInput.Text;
TData.Qry.Params.ParamByName('pin').AsString := PincodeInput.Text;
TData.Qry.Active := True;
if TData.Qry.RecordCount = 0 then ShowMessage('Details not recognised.')
else if TData.Qry.RecordCount = 1 then
begin
if TData.Qry.FieldByName('IsActive').AsBoolean then //If the user is active
begin
try
//Connect to the path
companyPath := TData.Qry.FieldByName('CompanyName').AsString;
TData.Conn.Params.Database := 'D\PulseDB\' + companyPath + '\Pulse.mdb';
TData.Conn.Connected := True;
ShowMessage('Connected to ' + companyPath);
finally
end;
end;
end;
finally
end;
end;
finally
end;
end;
You don't ever create an instance of your TData class. At some point you need to write:
TData := frmData.TData.Create;
Which is how you instantiate an instance. And you need to destroy it when you are finished also. Like this:
TData.Free;
That you did not instantiate an instance is the explanation for your access violation.
Some other issues:
Use the T prefix for types. Your variable should be named Data rather than TData.
Don't use global variables if at all possible.
I have this:
ShellExecute(Application.Handle, nil, PWideChar('explorer.exe'), PWideChar(ImagesDir), nil, SW_SHOWNORMAL);
where the variable ImagesDir is the directory of Images that I want to show by the Windows Explorer...
How can I run the Windows Explorer beside my application at a specified Bounds, for exemple like this?
when you open any File Explorer window (such as going to C:\ ), File Explorer has a specific saved window size that it opens with. So when you resize it, either horizontally and/or vertically, close it and re-open it again, it saves the size of the window, and the location within the Registry where this information is saved is this:
On my system, HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags\AllFolders\Shell\WinPos1366x768x96(1)..position, where position is left, right, top or bottom, gives the position of the window border in pixels.
I assume the name of the key depends on the screen resolution.here
and the code will be like that:
.....
const
AMainKey = '\Software\Classes\Local
Settings\Software\Microsoft\Windows\Shell\Bags\AllFolders\Shell\';
var
FrmMain: TFrmMain;
ImagesDir: string;
AWinPos_left, AWinPos_Top,
AWinPos_Right, AWinPos_Bottom: string;
implementation
Uses
ShellApi, Registry;
{$R *.dfm}
procedure ExploreDir_With_Bounds(AFile_Dir: string;ALeft, ATop, AWidth, AHieght: DWORD);
FUNCTION ExploreDirectory(CONST Dir : STRING) : BOOLEAN;
BEGIN
Result :=(ShellExecute(GetDesktopWindow,'open',PWideChar(Dir),'','',SW_SHOW)>32)
END;
var
ListNames, ListPosition: TStringList;
I, AScreen_Width, AScreen_Hieght, APixelPI: Integer;
AWinPos_Uses: string;
begin
ListNames := TStringList.Create;
ListPosition := TStringList.Create;
With TRegistry.Create Do
Try
RootKey := HKEY_CURRENT_USER;
OpenKey(AMainKey,FALSE);
GetValueNames(ListNames);
AScreen_Width := Screen.Width;
AScreen_Hieght := Screen.Height;
APixelPI := Screen.PixelsPerInch;
AWinPos_Uses := 'WinPos'+AScreen_Width.ToString+'x'+AScreen_Hieght.ToString+'x'+APixelPI.ToString;
for I := 0 to ListNames.Count - 1 do
begin
if Pos(AWinPos_Uses, ListNames[I]) <> 0 then
begin
ListPosition.Add(ListNames[I]);
end;
end;
for I := 0 to ListPosition.Count - 1 do
begin
if (Pos('left', ListPosition[I]) <> 0) then
begin
AWinPos_left := ListPosition[I];
Lbl_Left.Caption := AWinPos_left;
Continue;
end else
if (Pos('top', ListPosition[I]) <> 0) then
begin
AWinPos_Top := ListPosition[I];
Lbl_Top.Caption := AWinPos_Top;
Continue;
end else
if (Pos('right', ListPosition[I]) <> 0) then
begin
AWinPos_Right := ListPosition[I];
Lbl_Right.Caption := AWinPos_Right;
Continue;
end else
if (Pos('bottom', ListPosition[I]) <> 0) then
begin
AWinPos_Bottom := ListPosition[I];
Lbl_Bottom.Caption := AWinPos_Bottom;
end;
end;
if (AWinPos_left <> '')and(AWinPos_Top <> '')and
(AWinPos_Right <> '')and(AWinPos_Bottom <> '') then
begin
WriteInteger(AWinPos_left, ALeft);
WriteInteger(AWinPos_Top, ATop);
WriteInteger(AWinPos_Right, ALeft + AWidth);
WriteInteger(AWinPos_Bottom, ATop + AHieght);
end;
CloseKey;
Finally
Free;
ListNames.Free;
ListPosition.Free;
End;
ExploreDirectory(AFile_Dir);
end;
procedure TFrmMain.FormCreate(Sender: TObject);
begin
ImagesDir := TDirectory.GetParent(TDirectory.GetParent(ExtractFileDir(ParamStr(0))))+ '\My Images To Test';
ExploreDir_With_Bounds(ImagesDir, (50 + Width)+10{Left}, 50{TOP},
Screen.Width - (Left + Width +20){width},
Screen.Height - 150{hieght});
end;
procedure TFrmMain.FormShow(Sender: TObject);
begin
Left := 0;
Top := (Screen.WorkAreaHeight div 2)-(Height div 2);
end;
end.
the Result here
You can use the following function to open an explorer window and have it point to a specific directory.
USES Windows,ShellAPI;
FUNCTION ExploreDirectory(CONST Dir : STRING) : BOOLEAN;
BEGIN
Result:=(ShellExecute(GetDesktopWindow,'open',PChar(Dir),'','',SW_SHOW)>32)
END;
Note, however, that you can't (with this code) make the Explorer window "follow" your program, ie. the opened window is a completely autonomous window that has no link to your program, just as if the user had browsed to the directory himself. If you call this function again with a new directory, Explorer will open a new window with that directory (and keep the old one opened).
UPDATE:
If you want to be able to manipulate the explorer window after it is opened, you need to use the various interfaces that Explorer exposes. I have made a UNIT that allows you to do what you seek as well as returning the interface needed to be able to manipulate the window afterwards. It is heavily based on the code found in this answer:
Check if windows explorer already opened on given path
by Victoria
UNIT WindowsExplorer;
INTERFACE
USES Types,ShDocVw;
FUNCTION ExploreDirectory(CONST Dir : STRING) : BOOLEAN;
FUNCTION OpenFolder(CONST Dir : STRING) : IWebBrowserApp; OVERLOAD;
FUNCTION OpenFolderAt(CONST Dir : STRING ; Left,Top,Width,Height : INTEGER) : IWebBrowserApp; OVERLOAD;
FUNCTION OpenFolderAt(CONST Dir : STRING ; CONST Rect : TRect) : IWebBrowserApp; OVERLOAD; INLINE;
IMPLEMENTATION
USES Windows,Variants,ShlObj,Ole2,OleAuto,ShellAPI,ActiveX,SysUtils;
FUNCTION ExploreDirectory(CONST Dir : STRING) : BOOLEAN;
BEGIN
Result:=(ShellExecute(GetDesktopWindow,'open',PChar(Dir),'','',SW_SHOW)>32)
END;
FUNCTION GetFolderIDList(CONST Dir : STRING) : PItemIDList;
VAR
ShellFolder : IShellFolder;
Attributes : ULONG;
Count : ULONG;
BEGIN
OleCheck(SHGetDesktopFolder(ShellFolder));
Attributes:=SFGAO_FOLDER or SFGAO_STREAM;
OleCheck(ShellFolder.ParseDisplayName(0,NIL,PWideChar(WideString(Dir)),Count,Result,Attributes));
IF NOT ((Attributes AND SFGAO_FOLDER=SFGAO_FOLDER) AND (Attributes AND SFGAO_STREAM<>SFGAO_STREAM)) THEN BEGIN
CoTaskMemFree(Result);
Result:=NIL
END
END;
FUNCTION OpenFolder(CONST Dir : STRING ; OpenIfNotFound : BOOLEAN) : IWebBrowserApp; OVERLOAD;
CONST
IID_IServiceProvider: System.TGUID = '{6D5140C1-7436-11CE-8034-00AA006009FA}';
VAR
FolderID : PItemIDList;
ShellWindows : IShellWindows;
I : INTEGER;
WndIFace : System.IDispatch;
WebBrowserApp : IWebBrowserApp;
ServiceProvider : IServiceProvider;
ShellBrowser : IShellBrowser;
ShellView : IShellView;
FolderView : IFolderView;
PersistFolder : IPersistFolder2;
CurFolderID : PItemIDList;
BEGIN
FolderID:=GetFolderIDList(Dir);
IF Assigned(FolderID) THEN TRY
OleCheck(CoCreateInstance(CLASS_ShellWindows,NIL,CLSCTX_LOCAL_SERVER,IID_IShellWindows,ShellWindows));
FOR I:=0 TO PRED(ShellWindows.Count) DO BEGIN
WndIface:=ShellWindows.Item(VarAsType(I,VT_I4));
IF Assigned(WndIface) AND
Succeeded(WndIface.QueryInterface(IID_IWebBrowserApp,WebBrowserApp)) AND
Succeeded(WebBrowserApp.QueryInterface(IID_IServiceProvider,ServiceProvider)) AND
Succeeded(ServiceProvider.QueryService(SID_STopLevelBrowser,IID_IShellBrowser,ShellBrowser)) AND
Succeeded(ShellBrowser.QueryActiveShellView(ShellView)) AND
Succeeded(ShellView.QueryInterface(IID_IFolderView,FolderView)) AND
Succeeded(FolderView.GetFolder(IID_IPersistFolder2,PersistFolder)) AND
Succeeded(PersistFolder.GetCurFolder(CurFolderID)) AND
ILIsEqual(FolderID,CurFolderID) THEN BEGIN
IF IsIconic(WebBrowserApp.HWnd) THEN Win32Check(ShowWindow(WebBrowserApp.HWnd,SW_RESTORE));
Win32Check(SetForegroundWindow(WebBrowserApp.HWnd));
Exit(WebBrowserApp)
END
END
FINALLY
CoTaskMemFree(FolderID)
END;
Result:=NIL;
IF OpenIfNotFound THEN BEGIN
IF NOT ExploreDirectory(Dir) THEN EXIT;
FOR I:=1 TO 20 DO BEGIN
Result:=OpenFolder(Dir,FALSE);
IF Assigned(Result) THEN EXIT;
Sleep(100)
END
END
END;
FUNCTION OpenFolder(CONST Dir : STRING) : IWebBrowserApp;
BEGIN
Result:=OpenFolder(Dir,TRUE)
END;
FUNCTION OpenFolderAt(CONST Dir : STRING ; Left,Top,Width,Height : INTEGER) : IWebBrowserApp;
BEGIN
Result:=OpenFolder(Dir);
IF Assigned(Result) THEN BEGIN
Result.Left:=Left; Result.Top:=Top; Result.Width:=Width; Result.Height:=Height
END
END;
FUNCTION OpenFolderAt(CONST Dir : STRING ; CONST Rect : TRect) : IWebBrowserApp;
BEGIN
Result:=OpenFolderAt(Dir,Rect.Left,Rect.Top,Rect.Width,Rect.Height)
END;
END.
It is made for use with Delphi Tokyo 10.2.3 so if you use an earlier version (you didn't specify Delphi version in your question), you may need to adapt the USES list to match.
I'm creating a program with Ada 95 and I have a problem. Specifically, I'm trying to implement a class which executes functors given as parameters.
The behavior I want to achieve is:
Declare an interface IF with a procedure Execute.
Derive from IF with a class C and implement Execute.
Create a class D which has a field that is an array of IF. Since IF cannot be instantiated, I use an array of access IF.
Instantiate an object of class D giving it several instances of C as parameters.
Call Execute on every instance of C contained in the array of D.
I've been able to implement the above and compile it, but when I execute it I obtain the error accessibility check failed when trying to assign an object of class C to a component of the array in D.
I know that the error I obtain is because the assignment I'm doing can lead to a danging pointer error according to the Ada policies, so my question is what is the proper way to implement this in Ada 95?
The source code is below. The error is raised in the file elevators.adb, in the procedure Add_Event_Handler, I have commented the statement that causes it.
Functors.ads
package Functors is
type IFunctor is abstract tagged null record;
procedure Execute(Self : in out IFunctor) is abstract;
end Functors;
Elevators.ads
with Functors; use Functors;
package Elevators is
NOT_A_FLOOR : constant := -1;
MAX_EVENT_HANDLERS : constant := 255;
type Floor is new Integer range NOT_A_FLOOR .. 4;
type Elevator is private;
subtype Event_Handler is IFunctor'Class; --'
type Event_Handler_Index is new Integer range 0 .. MAX_EVENT_HANDLERS;
type Event_Handers is array(Event_Handler_Index) of access Event_Handler;
function Create_Elevator return Elevator;
procedure Add_Stop_Handler(Self : in out Elevator; Handler : access Event_Handler);
procedure Add_Moving_Handler(Self : in out Elevator; Handler : access Event_Handler);
procedure Add_Called_Handler(Self : in out Elevator; Handler : access Event_Handler);
procedure Add_Button_Pressed_Handler(Self : in out Elevator; Handler : access Event_Handler);
procedure Run_Simulation(Self : in out Elevator);
private
type Elevator is
record
Current_Floor : Floor := 0;
Is_Moving : Boolean := False;
Next_Floor : Floor := NOT_A_FLOOR;
Stop : Event_Handers := (others => null);
Moving : Event_Handers := (others => null);
Called : Event_Handers := (others => null);
Button_Pressed : Event_Handers := (others => null);
end record;
procedure On_Stop(Self : in out Elevator);
procedure On_Moving(Self : in out Elevator);
procedure On_Called(Self : in out Elevator);
procedure On_Button_Pressed(Self : in out Elevator);
procedure Add_Event_Handler(Self : out Event_Handers; Handler : access Event_Handler);
procedure Exec_All_Events(Self : in out Elevator; EH : in Event_Handers);
end Elevators;
Elevators.adb
with Ada.Text_IO; use Ada.Text_IO;
package body Elevators is
function Create_Elevator return Elevator is
elev : Elevator;
begin
return elev;
end;
procedure Add_Stop_Handler(self : in out Elevator; Handler : access Event_Handler) is
begin
Add_Event_Handler(self.Stop, Handler);
end;
procedure Add_Moving_Handler(self : in out Elevator; Handler : access Event_Handler) is
begin
Add_Event_Handler(self.Moving, Handler);
end;
procedure Add_Called_Handler(self : in out Elevator; Handler : access Event_Handler) is
begin
Add_Event_Handler(self.Called, Handler);
end;
procedure Add_Button_Pressed_Handler(self : in out Elevator; Handler : access Event_Handler) is
begin
Add_Event_Handler(self.Button_Pressed, Handler);
end;
procedure Run_Simulation(self : in out Elevator) is
begin
Put_Line("Floor: " & Floor'Image(self.Current_Floor)); --'
self.Next_Floor := 3;
On_Called(self);
On_Moving(self);
On_Stop(self);
end;
procedure On_Stop(self : in out Elevator) is
begin
self.Current_Floor := self.Next_Floor;
self.Is_Moving := False;
self.Next_Floor := NOT_A_FLOOR;
Put_Line("Stopped. Current floor = " & Floor'Image(self.Current_Floor)); --'
Exec_All_Events(self, self.Stop);
end;
procedure On_Moving(self : in out Elevator) is
begin
self.Is_Moving := True;
self.Current_Floor := NOT_A_FLOOR;
Put_Line("Moving to floor " & Floor'Image(self.Next_Floor)); --'
Exec_All_Events(self, self.Moving);
end;
procedure On_Called(self : in out Elevator) is
begin
Put_Line("Calling button pressed (" & Floor'Image(self.Next_Floor) & ")..."); --'
Exec_All_Events(self, self.Moving);
end;
procedure On_Button_Pressed(self : in out Elevator) is
begin
null;
end;
procedure Add_Event_Handler(Self : out Event_Handers; Handler : access Event_Handler) is
I : Event_Handler_Index := Event_Handler_Index'First; --'
begin
while I < Event_Handler_Index'Last loop --'
if Self(I) = null then
Self(I) := Handler; -- ======> The error is raised here <======
exit;
end if;
I := I + 1;
end loop;
end;
procedure Exec_All_Events(self : in out Elevator; EH : in Event_Handers) is
I : Event_Handler_Index := Event_Handler_Index'First; --'
begin
while I < Event_Handler_Index'Last loop --'
if EH(I) /= null then
EH(I).Execute;
end if;
I := I + 1;
end loop;
end;
end Elevators;
main.adb
with Ada.Text_IO; use Ada.Text_IO;
with Functors; use Functors;
with Elevators; use Elevators;
procedure Main is
type My_Functor is new IFunctor with
record
I : Integer := 0;
end record;
overriding
procedure Execute(Self : in out My_Functor) is
begin
Put_Line("Executing functor, I is " & Integer'Image(Self.I)); --'
Self.I := Self.I + 1;
end;
Generic_Functor : aliased My_Functor;
Elev : Elevator := Create_Elevator;
begin
Add_Stop_Handler(elev, Generic_Functor'Access); --'
Add_Moving_Handler(elev, Generic_Functor'Access); --'
Add_Called_Handler(elev, Generic_Functor'Access); --'
Run_Simulation(Elev);
end;
EDIT
I have done the following changes in order to fix the mentioned runtime error, but I still obtain the accessibility check failed.
elevators.ads
...
type Event_Handler_Generic_Ptr is access all Event_Handler;
type Event_Handers is array(Event_Handler_Index) of Event_Handler_Generic_Ptr;
...
elevators.adb
procedure Add_Event_Handler(Self : out Event_Handers; Handler : access Event_Handler) is
I : Event_Handler_Index := Event_Handler_Index'First; --'
begin
while I < Event_Handler_Index'Last loop --'
if Self(I) = null then
-- Notice the casting here
Self(I) := Event_Handler_Generic_Ptr(Handler); -- ======> The error is raised here <======
exit;
end if;
I := I + 1;
end loop;
end;
Since you store a pointer generated with 'Access in Event_Handlers, you must declare it with access all, so that it is a general access type:
type Event_Handers is array(Event_Handler_Index) of access all Event_Handler;
If you miss all, it is a pool-specific access type. See Ada 95 RM, 3.10 Access Types, (8) and (10). pool-specific access types may only hold pointers to objects allocated in a storage pool, which your object is not.
This weekend, I updated my code base from DWScript SVN. I used Preview 2.7 and now I'm using up-to-date trunk version.
I recompile my application and now the OnAfterInitUnitTable is no more triggered. Actually TdwsUnit.InitUnitTable is not called at all.
BTW: TDWSunit is created at runtime by code and then two classes are exposed using ExposeRTTI. In need to expose one instance of each class.
What are - now - the prerequisites to have OnAfterInitUnitTable triggered?
Any help appreciated.
EDIT: Sample code to reproduce:
program ExposeTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, Classes, TypInfo,
dwsRTTIExposer, dwsExprs, dwsComp;
type
TScriptApplication = class(TPersistent)
end;
TTestClass = class(TThread)
private
FScript : IdwsProgram;
FDelphiWebScript : TDelphiWebScript;
FUnit : TdwsUnit;
FScriptApplication : TScriptApplication;
FSuccess : Boolean;
procedure ExposeInstancesAfterInitTable(Sender: TObject);
public
constructor Create;
destructor Destroy; override;
procedure Execute; override;
end;
var
Test : TTestClass;
{ TTestClass }
constructor TTestClass.Create;
begin
inherited Create(TRUE);
FScriptApplication := TScriptApplication.Create;
FDelphiWebScript := TDelphiWebScript.Create(nil);
FUnit := TdwsUnit.Create(nil);
FUnit.UnitName := 'Test';
FUnit.Script := FDelphiWebScript;
FUnit.ExposeRTTI(TypeInfo(TScriptApplication), [eoNoFreeOnCleanup]);
FUnit.OnAfterInitUnitTable := ExposeInstancesAfterInitTable;
end;
destructor TTestClass.Destroy;
begin
FreeAndNil(FScriptApplication);
FreeAndNil(FUnit);
FreeAndNil(FDelphiWebScript);
inherited;
end;
procedure TTestClass.Execute;
begin
WriteLn('Test 1');
FSuccess := FALSE;
FScript := FDelphiWebScript.Compile('Unit Test; var I: Integer; I := 0;');
if FSuccess then
WriteLn(' Success')
else
WriteLn(' Failure');
WriteLn('Test 2');
FSuccess := FALSE;
FScript := FDelphiWebScript.Compile('var I: Integer; I := 0;');
if FSuccess then
WriteLn(' Success')
else
WriteLn(' Failure');
WriteLn('Test Done');
end;
procedure TTestClass.ExposeInstancesAfterInitTable(Sender: TObject);
begin
FUnit.ExposeInstanceToUnit('Application', 'TScriptApplication', FScriptApplication);
WriteLn('OnAfterInitUnitTable called');
FSuccess := TRUE;
end;
begin
Test := TTestClass.Create;
Test.Start;
Sleep(1000);
WriteLn('Hit enter to quit');
ReadLn;
Test.Free;
end.
EDIt2: Other version to show the new issue using suggestion by Eric Grange in answer 1 below;
program ExposeTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, Classes, TypInfo,
dwsRTTIExposer, dwsFunctions, dwsExprs, dwsComp;
type
TScriptApplication = class(TPersistent)
published
procedure Demo;
end;
TTestClass = class(TThread)
private
FScript : IdwsProgram;
FDelphiWebScript : TDelphiWebScript;
FUnit : TdwsUnit;
FScriptApplication : TScriptApplication;
FSuccess : Boolean;
procedure ExposeInstancesAfterInitTable(Sender: TObject);
function NeedUnitHandler(const UnitName : UnicodeString;
var UnitSource : UnicodeString): IdwsUnit;
public
constructor Create;
destructor Destroy; override;
procedure Execute; override;
end;
var
Test : TTestClass;
{ TTestClass }
constructor TTestClass.Create;
begin
inherited Create(TRUE);
FScriptApplication := TScriptApplication.Create;
FDelphiWebScript := TDelphiWebScript.Create(nil);
FDelphiWebScript.OnNeedUnit := NeedUnitHandler;
FUnit := TdwsUnit.Create(nil);
FUnit.UnitName := 'Test';
FUnit.Script := FDelphiWebScript;
FUnit.ExposeRTTI(TypeInfo(TScriptApplication), [eoNoFreeOnCleanup]);
FUnit.OnAfterInitUnitTable := ExposeInstancesAfterInitTable;
end;
destructor TTestClass.Destroy;
begin
FreeAndNil(FScriptApplication);
FreeAndNil(FUnit);
FreeAndNil(FDelphiWebScript);
inherited;
end;
procedure TTestClass.Execute;
begin
WriteLn('Test 1');
FSuccess := FALSE;
FScript := FDelphiWebScript.Compile('Unit Test; var I: Integer; I := 0;');
WriteLn(FScript.Msgs.AsInfo);
if FSuccess then
WriteLn(' Success')
else
WriteLn(' Failure');
WriteLn('Test 2');
FSuccess := FALSE;
FScript := FDelphiWebScript.Compile('uses Other;');
WriteLn(FScript.Msgs.AsInfo);
if FSuccess then
WriteLn(' Success')
else
WriteLn(' Failure');
WriteLn('Test Done');
end;
procedure TTestClass.ExposeInstancesAfterInitTable(Sender: TObject);
begin
FUnit.ExposeInstanceToUnit('Application', 'TScriptApplication', FScriptApplication);
WriteLn('OnAfterInitUnitTable called');
FSuccess := TRUE;
end;
function TTestClass.NeedUnitHandler(
const UnitName : UnicodeString;
var UnitSource : UnicodeString): IdwsUnit;
begin
Result := nil;
if SameText(UnitName, 'Other') then
UnitSource := 'unit Other;' + #13#10 +
'procedure Func;' + #13#10 +
'begin' + #13#10 +
' Application.Demo;' + #13#10 +
'end;' + #13#10
else
UnitSource := '';
end;
{ TScriptApplication }
procedure TScriptApplication.Demo;
begin
end;
begin
Test := TTestClass.Create;
Test.Start;
Sleep(1000);
WriteLn('Hit enter to quit');
ReadLn;
Test.Free;
end.
When encountering a "unit" as main program, the compiler currently assumes it's just a compilation for IDE purposes, ie. to check for syntax errors, build a symbol map, provide suggestions, etc. and the resulting program isn't fully initialized as a consequence.
So if you want to compile the unit and make an executable program, you can have a main program that'll just be something like:
uses Test;
This will compile a program comprised of your unit, for which executions can be created and where functions can be called though exec.Info, classes can be instantiated, etc.
Edit2: For the second test case, it works if "uses Test;" is added. For full cross-compilability with Delphi, you'll also need interface/implementation sections (when targeting script only, they are not necessary)
unit Other;
interface
uses Test;
procedure Func;
implementation
procedure Func;
begin
Application.Demo;
end;
and if RTTI is generated for the methods with the $RTTI directive, at least with
{$RTTI EXPLICIT METHODS([vcPublished])}
TScriptApplication = class(TPersistent)
published
procedure Demo;
end;
otherwise you get an error about "Demo" not being found.