Gem #110: Ada Plug-ins and Shared Libraries — Part 2

by Pascal Obry—EDF R&D

Let's get started...

In the first part of this two-part series we saw that stand-alone shared libraries have the required properties to be used as dynamic plug-ins. In this Gem we explore how to load and unload code dynamically within an Ada application. In essence, the dynamically loaded code is compiled into a shared library, and when this shared library is modified it is reloaded automatically.

To accomplish this we need three modules:

  • Registry -- A module handles plug-in loading, unloading, and service registration
  • Computer -- A plug-in doing a simple computation using two integers and returning the result
  • Main -- The main application that uses the computer plug-in

Registry

Each plug-in will inherit from a common type named Any. The associated access type Ref is used to record all loaded plug-ins in a hashed map:

<b>package</b> Plugins <b>is</b>   
   <b>type</b> Any <b>is</b> <b>abstract</b> <b>tagged</b> <b>null</b> <b>record</b>;
   <b>type</b> Ref <b>is</b> <b>access</b> <b>all</b> Any'Class;
   <em>--  A reference to any loaded plug-in</em>
<b>end</b> Plugins;

Our Computer plug-in is described by:

<b>package</b> Plugins.Computer <b>is</b>
   
   Service_Name : <b>constant</b> String := "COMPUTER";
   <em>--  Name of the service provided by this plug-in</em>
   <b>type</b> Handle <b>is</b> <b>abstract</b> <b>new</b> Any <b>with</b> <b>null</b> <b>record</b>;
   <b>type</b> Ref <b>is</b> <b>access</b> <b>all</b> Handle'Class;
   
   <b>function</b> Call
     (H : <b>not</b> <b>null</b> <b>access</b> Handle; A, B : Integer) <b>return</b> Integer <b>is</b> <b>abstract</b>;   
<b>end</b> Plugins.Computer;

Note that this is only an abstract view that is shared by all the modules. This view describes all the routines supported by the plug-in. A concrete implementation of the computer plug-in will be given in the computer module.

The Registry spec is:

<b>with</b> Plugins;
<b>package</b> Registry <b>is</b>
   
   <b>procedure</b> Discover_Plugins;
   
   <b>procedure</b> Register
     (Service_Name : String; Handle : <b>not</b> <b>null</b> <b>access</b> Plugins.Any'Class);
   
   <b>procedure</b> Unregister (Service_Name : String);
   
   <b>function</b> Get (Service_Name : String) <b>return</b> <b>access</b> Plugins.Any'Class;
   
<b>end</b> Registry;

The Register, Unregister, and Get routines are trivial. The reference to the plug-in service is recorded in a hashed map by Register, removed by Unregister, and retrieved by Get. This part does not need further discussion.

The Discover_Plugins routine is the tricky one. Here is how it works. This routine scans the plug-ins directory for shared libraries prefixed by "libplugin_". If such a name is found, it is renamed by removing the "plugin" substring and loaded by the dynamic linker (see Shared_Lib.Load routine in the registry sources of the Gem's zip file). When the shared library is loaded, the elaboration code is used to register itself (that is, register the service name associated with the object reference) in the registry.

At this point the service is available and can be used by the main application.

Note that a map with all loaded shared libraries is kept. If a shared library is found to be loaded already, it is unloaded first. This calls the finalization code, which is used by the plug-in to unregister itself.

   <b>procedure</b> Discover_Plugins <b>is</b>
      
      <b>function</b> Plugin_Name (Name : String) <b>return</b> String <b>is</b>
         K : Integer := Strings.Fixed.Index (Name, "plugin_");
      <b>begin</b>
         <b>return</b> Name (Name'First .. K - 1) & Name (K + 7 .. Name'Last);
      <b>end</b> Plugin_Name;
      
      <b>use</b> Directories;
      <b>use</b> <b>type</b> Calendar.Time;
      
      S          : Search_Type;
      D          : Directory_Entry_Type;
      Only_Files : <b>constant</b> Filter_Type :=
                     (Ordinary_File => True, <b>others</b> => False);
      Any_Plugin : <b>constant</b> String :=
                     "libplugin_*." & Shared_Lib.File_Extension;
   <b>begin</b>
      Start_Search (S, "plugins/", Any_Plugin, Only_Files);
      <b>while</b> More_Entries (S) <b>loop</b>
         Get_Next_Entry (S, D);
         
         <b>declare</b>
            P     : Shared_Lib.Handle;
            Name  : <b>constant</b> String := Simple_Name (D);
            Fname : <b>constant</b> String := Full_Name (D);
            Pname : <b>constant</b> String := Plugin_Name (Fname);
         <b>begin</b>
            <em>--  Proceed if plug-in file is older than 5 seconds (we do not want to try</em>
            <em>--  loading a plug-in not yet fully compiled/linked).</em>
            <b>if</b> Modification_Time (D) < Calendar.Clock - 5.0 <b>then</b>
               Text_IO.Put_Line ("Plug-in " & Name);
               <b>if</b> Loaded_Plugins.Contains (Pname) <b>then</b>
                  Text_IO.Put_Line ("... already loaded, unload now");
                  P := Loaded_Plugins.Element (Pname);
                  Shared_Lib.Unload (P);
               <b>end</b> <b>if</b>;
               <em>--  Rename plug-in (first removing any existing plug-in)</em>
               <b>if</b> Exists (Pname) <b>then</b>
                  Delete_File (Pname);
               <b>end</b> <b>if</b>;
               Rename (Fname, Pname);
               <em>--  Load it</em>
               P := Shared_Lib.Load (Pname);
               Loaded_Plugins.Include (Pname, P);
            <b>end</b> <b>if</b>;
         <b>end</b>;
      <b>end</b> <b>loop</b>;
   <b>end</b> Discover_Plugins;

The Shared_Lib spec comes with two bodies, one for Windows and one for GNU/Linux. The proper body is selected automatically.

Note that the renaming of the plug-in is required on Windows, as it is not possible to write to a shared library which is in use.

Computer

Here is the specification of the Computer module that is an implementation of the abstract type described above:

<b>with</b> Plugins.Computer;
<b>package</b> Computer <b>is</b>
   <b>type</b> Handle <b>is</b> <b>new</b> Plugins.Computer.Handle <b>with</b> <b>null</b> <b>record</b>;
   <b>overriding</b> <b>function</b> Call 
     (H : <b>not</b> <b>null</b> <b>access</b> Handle; A, B : Integer) <b>return</b> Integer;
<b>end</b> Computer;

The body is straightforward. The Life_Controller is used to control the Computer object's life. The Initialize procedure (called when the plug-in is loaded) registers the service name and the Finalize procedure (called when the plug-in is unloaded) unregisters the service name.

<b>with</b> Ada.Finalization;
<b>with</b> Registry;
<b>package</b> <b>body</b> Computer <b>is</b>
   
   <b>use</b> Ada;
   
   <b>type</b> Life_Controller <b>is</b> <b>new</b> Finalization.Limited_Controlled <b>with</b> <b>null</b> <b>record</b>;
   <b>overriding</b> <b>procedure</b> Initialize (LC : <b>in</b> <b>out</b> Life_Controller);
   <b>overriding</b> <b>procedure</b> Finalize (LC : <b>in</b> <b>out</b> Life_Controller);
   
   H : <b>aliased</b> Handle;
   
   <b>overriding</b> <b>function</b> Call 
     (H : <b>not</b> <b>null</b> <b>access</b> Handle; A, B : Integer) <b>return</b> Integer <b>is</b>
   <b>begin</b>
      <b>return</b> A + B;
   <b>end</b> Call;
   
   <b>overriding</b> <b>procedure</b> Finalize (LC : <b>in</b> <b>out</b> Life_Controller) <b>is</b>
   <b>begin</b>
      Registry.Unregister (Plugins.Computer.Service_Name);
   <b>end</b> Finalize;
   
   <b>overriding</b> <b>procedure</b> Initialize (LC : <b>in</b> <b>out</b> Life_Controller) <b>is</b>
   <b>begin</b>
      Registry.Register (Plugins.Computer.Service_Name, H'<b>Access</b>);
   <b>end</b> Initialize;
   LC : Life_Controller;
   
<b>end</b> Computer;

Main

The main is the easy part. We just loop and once every second we discover plug-ins and call the Computer service if found.

<b>with</b> Ada.Text_IO;
<b>with</b> Plugins.Computer;
<b>with</b> Registry;
<b>procedure</b> Run <b>is</b>
   <b>use</b> Ada;
   <b>use</b> <b>type</b> Plugins.Ref;
   
   H      : Plugins.Ref;
   Result : Integer;
   
<b>begin</b>
   <b>loop</b>
      Text_IO.Put_Line ("loop...");
      Registry.Discover_Plugins;
      H := Plugins.Ref (Registry.Get (Plugins.Computer.Service_Name));
      <b>if</b> H /= <b>null</b> <b>then</b>
         Result := Plugins.Computer.Ref (H).Call (5, 7);
         Text_IO.Put_Line ("Result : " & Integer'Image (Result));
      <b>end</b> <b>if</b>;
      <b>delay</b> 1.0;
   <b>end</b> <b>loop</b>;
<b>end</b> Run;

To build and run the program, unpack the zip file attached to this Gem and execute the following commands:

$ gnat make -p -Pmain

$ gnat make -p -Pcomputer

$ ./run

Now, while the application is running, edit computer.adb and replace the addition in Call by a multiplication. Then recompile the computer plug-in:

$ gnat make -p -Pcomputer

After some time you'll see that the new plug-in code has been loaded automatically.

Note that an extended example illustrating dynamic loading and unloading is included as part of the GNAT examples.

plug-in.zip


Last Updated: 10/26/2017
Posted on: 9/21/2011