The final set of tools at the Ada
language syntax level that we will learn about are the parallel task definition syntax and the syntax that supports direct integration with the C
language.
Multi-tasking
A task
is essentially another Main
program, which will be run in parallel with the original Main
program’s code execution. So we can expect that the code that defines the task
will be a procedure
. However, sometimes managing task
by logic code is also necessary, and so the task
needs to be a data type. And so we have the concept of task type
.
1 2 3 4 5 6 | package Async is task type Extra_Task is entry Start (From_Caller : in out Integer); end Extra_Task; end Async; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | with Ada.Text_IO; use Ada.Text_IO; package body Async is task body Extra_Task is In_Task : Integer; begin accept Start (From_Caller : in out Integer) do In_Task := From_Caller; From_Caller := 9; end Start; delay 1.2; Put_Line ("In Extra Task : " & Integer'Image (In_Task)); end Extra_Task; end Async; |
Here we are declaring a simple task type
named Extra_Task
. The special point here is that the Extra_Task
itself is defined with the begin ... end Extra_Task;
is similar to a procedure
, but is declared and defined like a package
containing a procedure Start
.
As such, we can expect that external code will both be able to view Extra_Task
as a type
for declaring task
variables, and also be able to, each of these variable names will be the same as a package
reference name and can point to the procedure Start
to launch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | with Ada.Text_IO; use Ada.Text_IO; with Async; use Async; procedure Main is T : Extra_Task; V : Integer; begin V := 1; Put_Line ("In Main : " & Integer'Image (V)); T.Start (V); Put_Line ("In Main : " & Integer'Image (V)); end Main; |
1 2 3 4 5 | In Main : 1 In Main : 9 ... delay 1.2s In Extra Task : 1 |
The operation result describes that the execution of Main
‘s code continues even after the T
task is triggered on the T.Start (V);
. In addition, the code defined inside the procedure Start
of Extra_Task
is run synchronously between the main process of running the main code task Main
and the task T : Extra_Task
.
After the code defined inside the procedure Start
finishes, the delay 1.2;
is used to slow down T
‘s code by 1.2s
and show that task T
is running asynchronously from the main task Main
.
Both the task Main
and T : Extra_Task
are procedure
that start the processes running the code and can be seen as the starting point of different programs, which can declare their own resource variables like V : Integer
and In_Task : Integer
to store information to be worked on during each task
‘s execution.
Importing C from Ada
As introduced earlier in the introductory article of this Sub-Series, we will be able to easily integrate C
code into an project Ada
. We can even write parallel logic on C
files and then compile all the project
‘s C
and Ada
code files with gprbuild
.
To use multiple languages in an project Ada
, we need to list the names of the languages we want to use in the project
‘s configuration file. Languages other than C
eg Fortran
, Assembler
, etc. may require additional compiler information to be declared so that gprbuild
can handle parallel compilation of the code files. Ada
.
1 2 3 4 5 | project Learn_Ada is for Languages use ("Ada", "C"); -- for ... end Learn_Ada; |
Then we can create C
code files in the project
‘s src
folder.
1 2 3 | int triple (int value); void print (int value); |
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token macro property"><span class="token directive-hash">#</span> <span class="token directive keyword">include</span> <span class="token string">"stdio.h"</span></span> <span class="token macro property"><span class="token directive-hash">#</span> <span class="token directive keyword">include</span> <span class="token string">"./foreign.h"</span></span> <span class="token keyword">int</span> <span class="token function">triple</span> <span class="token punctuation">(</span> <span class="token keyword">int</span> value <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> value <span class="token operator">*</span> <span class="token number">3</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">void</span> <span class="token function">print</span> <span class="token punctuation">(</span> <span class="token keyword">int</span> value <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">printf</span> <span class="token punctuation">(</span> <span class="token string">"Printing from C... n"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">printf</span> <span class="token punctuation">(</span> <span class="token string">"Value : %i n"</span> <span class="token punctuation">,</span> value <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Here we have a header.h
file that defines a struct
and declares a triple
function defined in the body.c
file. To use these elements in Ada
‘s code , we need to generate the corresponding declaration code to refer to the elements defined in C
code .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | with Ada.Text_IO; use Ada.Text_IO; with Interfaces.C; procedure Main is subtype C_int is Interfaces.C.int; function C_triple (Value : C_int) return C_int with Convention => C, Import => True, External_Name => "triple"; procedure C_print (Value : in C_int) with Convention => C, Import => True, External_Name => "print"; Value : C_int; begin Value := 9; Value := C_triple (Value); C_print (Value); end Main; |
1 2 3 | Printing from C... Value : 27 |
Exporting Ada to C
In the opposite direction, when we want to call a sub-program
written in Ada
code in a C
code file of the same project
, the action to be performed is Export
through Interfaces.C
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | with Interfaces.C; use Interfaces.C; package Export is subtype C_int is Interfaces.C.int; function Add ( A : C_int , B : C_int ) return C_int with Convention => C, Export => True, External_Name => "add"; end Export; |
1 2 3 4 5 6 7 8 9 10 11 12 | package body Export is function Add ( A : C_int , B : C_int ) return C_int is begin return A + B ; end Add; end Export; |
So in any
C
code file, we will be able to see that the add
function has been defined in the global
scope and declared with the extern
keyword before using it.1 2 3 4 5 6 7 | <span class="token macro property"><span class="token directive-hash">#</span> <span class="token directive keyword">include</span> <span class="token string"><stdio.h></span></span> <span class="token keyword">extern</span> <span class="token keyword">int</span> <span class="token function">add</span> <span class="token punctuation">(</span> <span class="token keyword">int</span> a <span class="token punctuation">,</span> <span class="token keyword">int</span> b <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// bây giờ `add` đã có thể được gọi</span> <span class="token comment">// trong một `sub-program` nào đó .</span> |