Ada: Array of tasks - arrays

What is a good way to link an indexed task to a corresponding indexed protected type in SPARK?
For specifics, consider this setup:
subtype Thread_Range is Natural range 1..n;
protected type P is ... end P;
p: array(Thread_Range) of P;
For each p(i) I would like a task t(i) that monitors p(i) and, when it's ready, processes it. I can make this work pretty easily in Ada, but SPARK w/Ravenscar is more demanding. I've tried two approaches that appear to work fine when I run them:
Give T an Integer discriminant, then instantiate a T(i); for each i, but this grows burdensome with not-very-large i.
task type T(which: Integer);
t1: T(1);
t2: T(2);
...
Add an is_not_monitored function and a set_monitor procedure to P. Create an array of tasks without discriminant. When t(i) begins, it assigns itself to monitor the first p(j) it finds that hasn't already been assigned a monitor.
task type T;
task body T is
which: Integer;
available: Boolean;
begin
for i in Thread_Range loop
available := p(i).is_not_monitored;
if available then
p(i).set_monitor;
which := i;
end if;
end loop;
-- what the task does with p(i) follows
end T;
t: array(Thread_Range) of T;
I like the second one better, but not by much. In any case, SPARK "Prove" grumbles about potential data races, and I can see why (though I'm not sure it's actually due to this).
Hence the question.

This doesn’t cause gnatprove to choke.
And I think the main difference from your option 2 is that Claim checks whether the claim is possible and, if so, performs the claim in one protected call.
But I don’t quite see how to prove that the loop Claim in T exits with Ps (J) being claimed. I tried putting an assertion after the loop, but couldn’t get it to prove.
protected type P is
procedure Claim (Succeeded : out Boolean);
private
Claimed : Boolean := False;
end P;
subtype Thread_Range is Integer range 1 .. 2;
Ps : array (Thread_Range) of P;
Ts : array (Thread_Range) of T;
task body T is
Which : Integer;
begin
Claim:
for J in Thread_Range loop
declare
Claimed : Boolean;
begin
Ps (J).Claim (Succeeded => Claimed);
if Claimed then
Which := J;
exit Claim;
end if;
end;
end loop Claim;
loop -- having a loop keeps gnatprove quiet
delay until Ada.Real_Time.Time_Last;
end loop;
end T;
protected body P is
procedure Claim (Succeeded : out Boolean) is
begin
if not Claimed then
Claimed := True;
Succeeded := True;
else
Succeeded := False;
end if;
end Claim;
end P;
After out-of-band discussions with John, we’ve found that this postcondition can be proved:
procedure Claim (Succeeded : out Boolean)
with
Post =>
(Is_Claimed'Old or (Succeeded and Is_Claimed))
or
(not Succeeded and Is_Claimed);
Note that it’s not P’Old.Is_Claimed, mainly because ’Old requires a copy, and P is limited (because it’s a protected type).
We also found several alternative formulations that prove in GPL 2017 but not in CE 2018: for example,
(Is_Claimed
and
(Is_Claimed'Old xor Succeeded)

I'm not an expert in this, but it seems that you cannot show SPARK that there's a one-to-one relation between a task instance and a protected object instance unless you reference that protected object instance explicitly from a task instance. This is in particular to make SPARK prove that only one task will queue on the entry of a protected object; the Wait entry in the code below). Therefore (and while this might not be exactly what you're looking for), I could only solve the problem of connecting tasks and protected objects, and at the same time having a monitor functionality, by using a generic package that can be instantiated multiple times. This proves in GNAT CE 2018:
generic
package Generic_Worker with SPARK_Mode is
task T;
protected P is
entry Wait;
procedure Trigger;
private
Triggered : Boolean := False;
end P;
end Generic_Worker;
with body:
package body Generic_Worker with SPARK_Mode is
task body T is
begin
loop -- Ravenscar: Tasks must not terminate.
P.Wait;
end loop;
end T;
protected body P is
entry Wait when Triggered is
begin
Triggered := False;
-- Do some work.
end Wait;
procedure Trigger is
begin
Triggered := True;
end Trigger;
end P;
end Generic_Worker;
and instantiations:
with Generic_Worker;
pragma Elaborate_All (Generic_Worker);
package Workers with SPARK_Mode is
package Worker_0 is new Generic_Worker;
package Worker_1 is new Generic_Worker;
package Worker_2 is new Generic_Worker;
package Worker_3 is new Generic_Worker;
package Worker_4 is new Generic_Worker;
end Workers;

Related

How can lock array of records for adding and deleting element in thread

I want to get some query from server time by time and list this URL query on the array of records.
For freeing of memory must free the elements of an array that procced.
This process is on the thread to prevent freezing of App.
If the array is not locked, maybe deleting of element rise exception because that other element is in process or adding or deleting an index of element changed.
My question is: ’How can I lock an array of records for adding and deleting an element in the thread?’
This sample code is simplified for understanding my actual App code:
uses IdHTTP;
type
tMyRecAra=record
sta:integer;
url:string;
// ...
// ...
end;
var MyRecAra: array of tMyRecAra;
procedure TfoTestAra.btAddClick(Sender: TObject);
var cou:integer;
begin
//+ start lock array MyRecAra ?
cou:=length(MyRecAra);
SetLength(MyRecAra, length(MyRecAra)+1);
MyRecAra[cou].sta:=0;
MyRecAra[cou].url:='http:/......';
//- stop lock array MyRecAra ?
end;
procedure TfoTestAra.btStartClick(Sender: TObject);
var
IdHTTP1:TIdHTTP;
mainThr,curThr : TThread;
cStream:TMemoryStream;
begin
mainThr := TThread.CreateAnonymousThread(
procedure
begin
while true {or other condition} do
begin
curThr := TThread.CreateAnonymousThread(
procedure
var i:integer;
begin
//+ start lock array MyRecAra ?
for i := 0 to (length(MyRecAra)-1) do
begin
if (MyRecAra[i].sta=0) then
begin
MyRecAra[i].sta:=1;
//...
//..
{for example : IdHTTP1.Get(MyRecAra[i].url,cStream)};
//...
//..
end;
end;
//- stop lock array MyRecAra ?
end);
curThr.Start;
sleep(5000);
end;
end);
mainThr.start;
end;
procedure TfoTestAra.Timer1Timer(Sender: TObject);
var
sumFee:integer;
i, j:integer;
begin
// timer.interval=10000;
//+ start lock array MyRecAra?
sumFee:=0;
for i := 0 to (length(MyRecAra)-1) do
begin
if (MyRecAra[i].sta=1) then
begin
inc(sumFee);
for j := (i+1) to sumFee-1 do
begin
if (MyRecAra[j].sta <> 1) then
MyRecAra[i]:=MyRecAra[j]
end;
end;
end;
if sumFee<>0 then
SetLength(MyRecAra, (length(MyRecAra)-sumFee));
//+ stop lock array MyRecAra ?
end;
End.
You can use lock to protect access to shared data and then general pattern in all places where you access the data would be:
Lock.Enter;
try
// procected code
finally
Lock.Leave;
end;
You need to declare lock variable in the same scope as the data that needs protection and you need to initialize that lock before it is used for the first time and free it when it is no longer needed.
For instance, if your MyRecAra is global data in unit, then Lock also needs to be global and initialized in initialization section of the unit and released in finalization section.
If MyRecAra is field in form or some other class, then Lock would also be a field in that class, initialized in constructor and released in destructor.
Commonly used lock is TCriticalSection. There are other types of locks, but for start this one will do just fine.
var
Lock: TCriticalSection;
MyRecAra: TMyRecAra;
initialization
Lock := TCriticalSection.Create;
finalization
Lock.Free;
end.

Cannot use MSSQL Timestamp as a parameter in Delphi XE8

We are in the process of upgrading one of our projects from Delphi XE to XE8. Our audit code makes use of a TIMESTAMP field in a MSSQL (2012 in this instance) database and selects from a table using this as a parameter in the WHERE clause.
We now are no longer getting any results running the following code:
procedure TForm2.Button1Click(Sender: TObject);
begin
ADODataset1.CommandText := 'SELECT * FROM CURRENCYAUDIT';
ADODataset2.CommandText := 'SELECT * FROM CURRENCYAUDIT WHERE Audit_Timestamp = :Timestamp';
ADODataset2.Parameters.Refresh;
ADODataset1.Open;
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
begin
showmessage('nothing to compare');
end;
ADODataset2.Parameters[0].Value := ADODataset1.FieldByName('audit_timestamp').Value;
ADODataset2.Open;
caption := inttostr(ADODataset2.RecordCount);
end;
Where CurrencyAudit is any old MSSQL table containing an notnull timestamp audit_timestamp field.
The caption of the form is 0 with no message shown.
Any idea how I can get this to work? Tried AsString (nonsense string, 0 results), AsSQLTimestamp (parameter doesn't accept) and AsBytes (0 return). Unfortunately the return of the .Value only evalates as 'variant array of byte' which isn't helpful to visualise/see what it is.
Edit: Running it as .AsBytes and viewing that in the debugger I can see that the XE verison is returning 0,0,0,0,0,8,177,22 whereas the XE8 is returning 17,32,0,0,0,0,0,0. Checking other fields of the (real) database shows the record is the same. Looks like a bug in reading TIMESTAMPs from the DB
I'm using two AdoQueries. The following works fine for me in D7, correctly returning 1 row in AdoQuery2, but 0 records in XE8, so obviously has the same XE8 problem as you've run into.
var
S : String;
V : Variant;
begin
AdoQuery1.Open;
S := AdoQuery1.FieldByName('ATimeStamp').AsString;
V := AdoQuery1.FieldByName('ATimeStamp').AsVariant;
Caption := S;
AdoQuery2.Parameters.ParamByName('ATimeStamp').Value := V;
AdoQuery2.Open;
Just for testing, I'm running my AdoQuery1 and AdoQuery2 against the same server table.
Update : I've got a similar method to the one in your answer working that avoids the need for your Int64ToByteArray, at the expense of some slightly messier (and less efficient) Sql, which may not be to your taste.
In my source AdoQuery, I have this Sql
select *, convert(int, atimestamp) as inttimestamp from timestamps
and in the destination one
select * from timestamps where convert(int, atimestamp) = :inttimestamp
which of course avoids the need for a varBytes parameter on the second AdoQuery, since one can pick up the integer version of the timestamp column value and assign it to the inttimestamp param.
Btw, in your original q
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
the two expressions would better be written the other way around. Unless ADODataset1 has persistent fields, if it contains no records when opened, referring to the audit_timestamp should raise a "Field not found" exception.
It appears that EMBT have broken the conversion of a TIMESTAMP into a byte array. The XE version of the bytearray is correct and manually pulling down the data as an int64 and then building the bytearray by hand (is there an out-of-the-box function for this?) and using that as the parameter works in XE8.
I've no idea whether this is a similar issue with other binary data types. Hope not!
Working code:
procedure TForm2.Button1Click(Sender: TObject);
var
TestArray: TArray<Byte>;
j: integer;
function Int64ToByteArray(const inInt: uint64): TArray<Byte>;
var
i: integer;
lInt: int64;
begin
SetLength(result, 8);
lInt := inint;
for i := low(result) to high(result) do
begin
result[high(result)-i] := lInt and $FF;
lInt := lInt shr 8;
end;
end;
begin
ADODataset1.CommandText := 'SELECT *, cast(audit_timestamp as bigint) tmp FROM CURRENCYAUDIT';
ADODataset2.CommandText := 'SELECT * FROM CURRENCYAUDIT WHERE Audit_Timestamp = :Timestamp';
ADODataset2.Parameters.Refresh;
ADODataset1.Open;
if ADODataset1.FieldByName('audit_timestamp').IsNull or ADODataset1.IsEmpty then
begin
showmessage('nothing to compare');
end;
ADODataset2.Parameters[0].Value := Int64ToByteArray(ADODataset1.FieldByName('tmp').asInteger);
ADODataset2.Open;
caption := inttostr(ADODataset2.RecordCount);
end;
I've also checked this going down my entire (real) table and ensuring all other fields match to make sure it's not a one-off!
I'll raise a ticket with EMBT for them to sit on and ignore for 5 years ;-)
https://quality.embarcadero.com/browse/RSP-11644

Ada - try to call entry of element of array of protected type, if busy - try next element

I'm working on a multithreading project and i have one array of tasks, let's call it A and one array of protected, let's call it B.
Now every element of A at some point will want to access entry from one of the elements of B. Is there a way to iterate quickly through B's and find accesible one?
I was browsing some resources avalaible online and found only
select
call entry;
or
delay;
which won't do the job.
Is there a way to do that that i'm not aware of?
Thanks in advance!
EDIT
So i got really excited about that select else statements and tried it out, unfortunately it still doesnt work, if I try it this way - using Mr. Simon Wright's code sample to simulate effect i need, the
else
part of code never gets executed, they (the taskArray elements) all think they can acces Call_Me and get stuck in the queue.
How can i solve this issue? I also tried using 'Count thing, but it somehow always ended up being 0.
with Ada.Text_IO; use Ada.Text_IO;
procedure Array_Of_POs is
protected type PO is
entry Call_Me;
private
Callable : Boolean := True; --changed
end PO;
protected body PO is
entry Call_Me when Callable is
begin
Callable := False; --added
Put_Line("Doin stuff");
delay 1.0;
Callable := True;
end Call_Me;
end PO;
POs : array (1 .. 3) of PO;
Success : Boolean := False;
task type Test; --added
task body Test is
begin
for J in POs'Range loop -- '
select
POs (J).Call_Me;
Success := True;
else
Put_Line("I never get displayed!"); --added
end select;
end loop;
Put_Line ((if Success
then "succeeded, oh dear"
else "didn't succeed, good"));
end Test;
testArray : array (1..3) of Test;
end Array_Of_POs;
You could try finding an entry E whose Count attribute (E’Count, ARM 9.9(5)) is zero; but this is going to result in race conditions, since another task could sneak in between your checking the count and your making the call. And see the note at (7), ibid.
You need a conditional entry call, as in ARM 9.7.3.
It would look something like
with Ada.Text_IO; use Ada.Text_IO;
procedure Array_Of_POs is
protected type PO is
entry Call_Me;
private
Callable : Boolean := False;
end PO;
protected body PO is
entry Call_Me when Callable is
begin
null;
end Call_Me;
end PO;
POs : array (1 .. 3) of PO;
Success : Boolean := False;
begin
for J in POs'Range loop -- '
select
POs (J).Call_Me;
Success := True;
else
null;
end select;
end loop;
Put_Line ((if Success
then "succeeded, oh dear"
else "didn't succeed, good"));
end Array_Of_POs;
In your case, I suspect this construct would be in an outer loop, so you’d need to do something to avoid busy-waiting (on the lines of putting in a delay if not Success, perhaps).
EDIT
This is a response to your edited question.
ARM 9.5.1(8) states that your PO.Call_Me contains a bounded error. In the next paragraph,
If the bounded error is detected, Program_Error is raised. If not detected, the bounded error might result in deadlock or a (nested) protected action on the same target object.
At least GNAT hasn’t done either of those.
The issue is that the body of the entry is executed in the context (thread) of the caller. So what happens is
Test calls POs(1).Call_Me
The call is accepted
Callable is set False
Put_Line and delay are called (still in the context of Test)
Callable is set True
POs(1).Call_Me returns to Test
...
The approach you’ve adopted might work if instead of using an array of POs you used an array of tasks.

Database Record Processing [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
How can I split the workload of records across multiple threads specifically Accuracer DB that has 169 records with 7 threads for example.
Because I could just split the number of records in ranges and let each thread process the range. But if user deletes or adds record it will not work good.
You can use OmniThreadLibrary to process records from a database in parallel without much hassle.
I wrote an example using the Pipeline abstraction. The pipeline consts of 3 stages:
The first stage reads data from the database, creates a instance of the container object to represent that data for the next stage of the pipeline.
The second stage processes the incoming data.
calls the DoSomethingWith procedure that simply wastes around 100 ms. to simulate the processing of the data
frees the memory of the container instance.
Then adds the literal value 1 to the output queue to inform the final stage that another record has been processed.
This stage is configured to run in parallel in 7 threads.
The last stage just counts how many records has been completed from the previous stage
The example is a console application to allow you just copy/paste to see it working live in your machine.
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
OtlCommon,
OtlCollections,
OtlParallel,
System.Diagnostics,
DB, DBClient;
type
//auxiliar container, used to copy the database data
//to avoid synchronization. remember TDataSet "current record"
//may cause conflicts if changed from different threads.
TContainer = class
private
FName: string;
FID: Int64;
public
property ID: Int64 read FID write FID;
property Name: string read FName write FName;
end;
//does nothing, but wastes around 100ms. "processing" each record
procedure DoSomethingWith(const AValue: TContainer);
begin
Sleep(100);
end;
//creates a DataSet on the fly with a random number of records
function CreateDataSet: TClientDataSet;
var
I: Integer;
begin
Result := TClientDataSet.Create(nil);
with Result.FieldDefs.AddFieldDef do
begin
Name := 'ID';
DataType := ftLargeint;
end;
with Result.FieldDefs.AddFieldDef do
begin
Name := 'NAME';
DataType := ftString;
end;
Result.CreateDataSet;
for I := 1 to Random(1000) do
Result.InsertRecord([I, 'Test']);
end;
var
RecordsProcessed: Integer;
SW: TStopwatch;
Data: TDataSet;
begin
IsMultiThread := True;
Randomize;
Writeln('wait while processing...');
SW := TStopwatch.Create;
SW.Start;
try
Data := CreateDataSet;
try
RecordsProcessed := Parallel.Pipeline
.Stage(
procedure (const Input, Output: IOmniBlockingCollection)
var
RecData: TContainer;
begin
Data.First;
while not Data.Eof do
begin
RecData := TContainer.Create;
RecData.ID := Data.Fields[0].AsLargeInt;
RecData.Name := Data.Fields[1].AsString;
Output.Add(RecData);
Data.Next;
end;
end)
.Stage(
procedure (const Input: TOmniValue; var Output: TOmniValue)
begin
//process the real thing here
DoSomethingWith(Input);
Input.AsObject.Free;
Output := 1; //another record
end)
.NumTasks(7) //this stage is processed by 7 parallel tasks
.Stage(
procedure (const Input, Output: IOmniBlockingCollection)
var
Recs: Integer;
Value: TOmniValue;
begin
Recs := 0;
for Value in Input do
Inc(Recs, Value);
Output.Add(Recs);
end)
.Run.Output.Next;
SW.Stop;
Writeln(RecordsProcessed, ' records processed in ', SW.ElapsedMilliseconds, 'ms.');
Writeln('Avg. ', (SW.ElapsedMilliseconds/RecordsProcessed):0:3, 'ms./record');
finally
Data.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
The main advantages of doing it this way, IMHO, are:
you have a flexible mechanism to distribute the job between the multiple workers. If some record takes more time to process, the library takes care of the situation and you can reasonably expect to finish the total work in the less possible time.
You'r first processing thread starts as soon as you finish reading the first record from the database.
You can easily adapt it if you have to wait for more incoming records in the base table. The output queue of the stage will not be marked as finished until the code in the stage procedure ends. If at some time there's no more work to do, all the upcoming stages would just block waiting for more data to process.
You change the number of worker threads just by changing a parameter value!

Converting Ada closures to C callbacks (function + void*)

Most clean C APIs declare callback as a combination of callback function and a user data. User data is usually void*. WinAPI uses pointer-sized integer (lParam). During making a thick binding a natural desire is to allow Ada 2005 closures to be used in place of C callbacks.
I have a code. It works like a charm on GNAT (GPL 2012, x86-windows is tested at least), but generally there is no guarantee that Run_Closure_Adapter.X variable and Run_Closure.X argument will have the same internal structure.
The question is: is there a proper (standards-compliant) way to do this? Maybe a trick involving tagged types, interfaces or generics. There is at least one way of doing this: running closure executor and closures in different tasks and using rendezvous. But that's too slow.
Closure_Test.adb:
with Closure_Lib; use Closure_Lib;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
procedure Closure_Test is
procedure Closure_Tester is
Local_String : String := "Hello, world!";
procedure Closure is
begin
Put_Line (Local_String);
end Closure;
begin
Run_Closure (Closure'Access);
end Closure_Tester;
procedure Ada_Run_Closure (X : access procedure) is
begin
X.all;
end Ada_Run_Closure;
-- Nested_Closure fills the execution stack with
-- several activation records of Nested_Closure_Tester
-- Having done so (local I = 0) we start a Fibonacci
-- algorithm using Print_Closure access values of
-- different dynamic nesting levels
procedure Nested_Closure_Tester
(I : Integer;
Closure_Runner: access procedure (X : access procedure);
Prev_Closure, Prev_Closure2: access procedure)
is
procedure Print_Closure is
begin
if Prev_Closure /= null and Prev_Closure2 /= null then
Closure_Runner (Prev_Closure);
Closure_Runner (Prev_Closure2);
else
Put (".");
end if;
end Print_Closure;
procedure Nested_Closure is
begin
if I > 0 then
Nested_Closure_Tester (I - 1, Closure_Runner,
Print_Closure'Access, Prev_Closure);
else
Print_Closure;
end if;
end Nested_Closure;
begin
Closure_Runner (Nested_Closure'Access);
end Nested_Closure_Tester;
begin
-- Closure_Tester;
-- I = 6 gives 13 dots
Nested_Closure_Tester(6, Ada_Run_Closure'Access, null, null);
New_Line;
Nested_Closure_Tester(6, Run_Closure'Access, null, null);
end Closure_Test;
Closure_Lib.ads:
with Interfaces.C;
with System;
package Closure_Lib is
procedure Run_Closure (X : access procedure);
private
type Simple_Callback is access procedure(Data : in System.Address);
pragma Convention (C, Simple_Callback);
procedure Run_Callback (X : in Simple_Callback; Data : in System.Address);
pragma Import (C, Run_Callback, "Run_Callback");
procedure Sample_Callback (Data : in System.Address);
pragma Convention (C, Sample_Callback);
end Closure_Lib;
Closure_Lib.adb:
with Interfaces.C;
with System;
with System.Storage_Elements; use System.Storage_Elements;
with Ada.Text_IO; use Ada.Text_IO;
package body Closure_Lib is
procedure Sample_Callback (Data : in System.Address) is
begin
Ada.Text_IO.Put_Line ("Simple_Callback");
end Sample_Callback;
procedure Run_Closure_Adapter (Data : in System.Address);
pragma Convention (C, Run_Closure_Adapter);
procedure Run_Closure_Adapter (Data : in System.Address) is
X : access procedure;
for X'Address use Data;
pragma Import (Ada, X);
X_Size : constant Storage_Count := X'Size / System.Storage_Unit;
begin
-- Put_Line ("Variable access procedure size:" & Storage_Count'Image (X_Size));
X.all;
end Run_Closure_Adapter;
procedure Run_Closure (X : access procedure) is
X_Size : constant Storage_Count := X'Size / System.Storage_Unit;
X_Address : constant System.Address := X'Address;
begin
-- Put_Line ("Anonymous access procedure size:" & Storage_Count'Image (X_Size));
Run_Callback (Run_Closure_Adapter'Access, X_Address);
end Run_Closure;
end Closure_Lib;
closure_executor.c:
typedef void (*Simple_Callback)(void* Data);
void Run_Callback (Simple_Callback X, void* Data) {
(*X)(Data);
}
I think what you're looking for might be met by using a generic (by the way, I don't see how using a task can ensure that data types match?)
Maybe something like
generic
type Client_Data is private;
package Closure_G is
type Closure (<>) is private;
function Create (Proc : access procedure (Parameter : Client_Data);
And_Parameter : Client_Data) return Closure;
procedure Execute (The_Closure : Closure);
private
type Procedure_P is access procedure (Parameter : Client_Data);
type Closure is record
The_Procedure : Procedure_P;
And_Parameter : Client_Data;
end record;
end Closure_G;
When a user calls Execute (A_Closure), the Proc supplied to Create is called with the And_Parameter that was supplied then.
(The type Closure (<>) is private; makes sure tht users can only create a Closure object using the supplied Create.)
The main trouble with this, in your scenario of passing to a C library to be called-back when an event occurs, is that the Closure object is actually maintained by the C library.
Aside from the fact that you don't really need this Ada Closure, there's a potential problem caused by anonymous access-to-subprogram values, which is that the subprogram could be locally declared and have gone out of scope by the time the C library gets round to calling it. This would be Bad News.
In the Ada world, the compiler copes with this problem in two ways. First, you're not allowed to store anonymous access-to-subprogram values (hence the type Procedure_P above). Second, even if you work round this as in
function Create (Proc : access procedure (Parameter : Client_Data);
And_Parameter : Client_Data) return Closure is
begin
return (The_Procedure => Procedure_P'(Proc),
And_Parameter => And_Parameter);
end Create;
the actual 'accessibility levels' are checked at run time; if you get it wrong you'll get a Program_Error.
As an alternative, you might look at how GtkAda handles callbacks from GTK+. As shown in the GtkAda User’s Guide, and discussed in §4.2.2. Connecting via the Gtk.Handlers package,
The Gtk.Marshallers package provides a set of functions that can be used as callbacks directly for GtkAda…A set of To_Marshaller functions is found in every generic package in Gtk.Handlers. They take a single argument, the name of the function you want to call, and return a handler that can be used directly in Connect.
Interaction is an example that instantiates several such handlers and connects the corresponding callback using an access-to-subprogram parameter.

Resources