comp.lang.ada
 help / color / mirror / Atom feed
* "Plugin°-based code
@ 2018-06-04 10:29 mockturtle
  2018-06-04 11:59 ` Dmitry A. Kazakov
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: mockturtle @ 2018-06-04 10:29 UTC (permalink / raw)


Dear.all, 
I have a doubt about what it could be the best solution for some code that I am writing.  I am afraid that the introduction before the actual question is a bit long.  

** The problem **

Let me use an example.  Suppose I am writing a program to plot, say, a GANTT chart.  I want the user to be able to choose the output format (say, PDF, SVG or even LaTeX) by giving a suitable option on the command line and I want to be able to add new output format easily, maybe even load the "format handler" as a dynamic library at run-time (so I do not need recompilation).  (Incidentally, I did some experiments with the dynamic loading and it works)

The structure I give to the code is as follows: first I define an interface for an "abstract plotter," for example

  package Plotters is 
    type Abstract_Plotter is interface;

    procedure Line(P: Abstract_Plotter;
                   X1,Y1, X2, Y2:Float) 
    is abstract;

    -- And so on...
  end Plotters;

Every actual plotter will be a descendant of Abstract_Plotters, for example

  package Plotters.SVG is
     type SVG_Plotter is 
     new Abstract_Plotter with private;

     -- Blah, blah ...
  end Plotters.SVG;

In order to allow the user to select the actual plotter via the command line I use a generic package Plugin_Table that implements a kind of "map" from names to descendant of Abstract_Plotter.  Plugin_Table provides two operations:

  (1) Register_Plugin(T: Tag; Name: String)  

         used by a concrete plotter to "register" itself to the table.  Usually it is called from the "initialization" part of the body of the package definynig the concrete plotter.  For example, plotters-svg.adb could call

     Register_Plugin(SVG_Plotter'Tag, "svg");

  (2) Get_Plugin (Name:String) return Abstract_Plotter'Class

       to be called to generate a concrete plotter.

In a future version Plugin_Table will be able to search for some dynamic library to be loaded if a plugin with the given name is not registered.

   ** The Question ** 

In order to have all the built-in plugins registered in my executable, I need to "with" them, so that they are linked to my code and the initialization part of their bodies is elaborated. However, no part of my code call explicitly, say, SVG_Plotter.  Therefore, my solution is to add something like

  with Plotters.SVG;
  pragma Warnings(Off, Plotter.SVG);

in the "main." It works, but it leaves me a bit unsatisfied since it seems an "hack".  Also, since no actual code is called directly I am not sure if the binder could take the liberty of not including them (maybe it cannot, but I am not sure).

Do you have any better solution?

Also, I am not sure if I risk getting in trouble with Elaboration order.  I think not, but I am not 100% sure.  What do you think?


Thanks,

Riccardo

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: "Plugin°-based code
  2018-06-04 10:29 "Plugin°-based code mockturtle
@ 2018-06-04 11:59 ` Dmitry A. Kazakov
  2018-06-04 14:03 ` Dan'l Miller
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Dmitry A. Kazakov @ 2018-06-04 11:59 UTC (permalink / raw)


On 2018-06-04 12:29 PM, mockturtle wrote:

> I have a doubt about what it could be the best solution for some code that I am writing.  I am afraid that the introduction before the actual question is a bit long.
> 
> ** The problem **
> 
> Let me use an example.  Suppose I am writing a program to plot, say, a GANTT chart.  I want the user to be able to choose the output format (say, PDF, SVG or even LaTeX) by giving a suitable option on the command line and I want to be able to add new output format easily, maybe even load the "format handler" as a dynamic library at run-time (so I do not need recompilation).  (Incidentally, I did some experiments with the dynamic loading and it works)
> 
> The structure I give to the code is as follows: first I define an interface for an "abstract plotter," for example
> 
>    package Plotters is
>      type Abstract_Plotter is interface;
> 
>      procedure Line(P: Abstract_Plotter;
>                     X1,Y1, X2, Y2:Float)
>      is abstract;
> 
>      -- And so on...
>    end Plotters;
> 
> Every actual plotter will be a descendant of Abstract_Plotters, for example
> 
>    package Plotters.SVG is
>       type SVG_Plotter is
>       new Abstract_Plotter with private;
> 
>       -- Blah, blah ...
>    end Plotters.SVG;
> 
> In order to allow the user to select the actual plotter via the command line I use a generic package Plugin_Table that implements a kind of "map" from names to descendant of Abstract_Plotter.  Plugin_Table provides two operations:
> 
>    (1) Register_Plugin(T: Tag; Name: String)
> 
>           used by a concrete plotter to "register" itself to the table.  Usually it is called from the "initialization" part of the body of the package definynig the concrete plotter.  For example, plotters-svg.adb could call
> 
>       Register_Plugin(SVG_Plotter'Tag, "svg");

type Abstract_Plotter is interface;
function Get_Name (Plotter : Abstract_Plotter)
    return String is abstract;

type Abstract_Plotter_Ptr is access all Abstract_Plotter'Class;
procedure Register_Plugin (SVG_Plotter_Ptr);

>    (2) Get_Plugin (Name:String) return Abstract_Plotter'Class
> 
>         to be called to generate a concrete plotter.

If you need a factory, OK. But it easier to provide ready-to-use 
instance, then you simply pass its pointer as above. If you need a 
factory you can register a constructing function instead of the tag. I 
understand that you want to use a generic constructor but that is not 
really necessary here unless you want to use pools etc.

> In a future version Plugin_Table will be able to search for some dynamic library to be loaded if a plugin with the given name is not registered.
> 
>     ** The Question **
> 
> In order to have all the built-in plugins registered in my executable, I need to "with" them, so that they are linked to my code and the initialization part of their bodies is elaborated. However, no part of my code call explicitly, say, SVG_Plotter.  Therefore, my solution is to add something like
> 
>    with Plotters.SVG;
>    pragma Warnings(Off, Plotter.SVG);

No. You simply put a call to Register_Plugin in the body of the library 
package:

    package Plotters.SVG is
       Plotter : Abstract_Plotter_Ptr;
    private
      type SVG_Plotter is
        new Abstract_Plotter with private;
    end Plotters.SVG;

    package body Plotters.SVG is
       Plotter := new SVG_Plotter (...);
    begin
       Register_Plotter (Plotter);
    end Plotters.SVG;

When the library is loaded you check if it is the first load. If so, you 
call Ada_Initialize to elaborate it. That is when manual elaboration is 
used. GNAT supports automatic elaboration as well, but it is broken 
under Windows if you are going to use tasking. Elaboration will register 
the plotter and all is done. The rest need not to know anything.

> Also, I am not sure if I risk getting in trouble with Elaboration order.  I think not, but I am not 100% sure.

You must ensure that you elaborate the library strictly once if you load 
it several times.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: "Plugin°-based code
  2018-06-04 10:29 "Plugin°-based code mockturtle
  2018-06-04 11:59 ` Dmitry A. Kazakov
@ 2018-06-04 14:03 ` Dan'l Miller
  2018-06-04 22:08 ` Plugin-based code Randy Brukardt
  2018-06-05  8:42 ` "Plugin°-based code Brian Drummond
  3 siblings, 0 replies; 5+ messages in thread
From: Dan'l Miller @ 2018-06-04 14:03 UTC (permalink / raw)


On Monday, June 4, 2018 at 5:29:26 AM UTC-5, mockturtle wrote:
> Dear.all, 
> I have a doubt about what it could be the best solution for some code that I am writing.  I am afraid that the introduction before the actual question is a bit long.  
> 
> ** The problem **
> 
> Let me use an example.  Suppose I am writing a program to plot, say, a GANTT chart.  I want the user to be able to choose the output format (say, PDF, SVG or even LaTeX) by giving a suitable option on the command line and I want to be able to add new output format easily, maybe even load the "format handler" as a dynamic library at run-time (so I do not need recompilation).  (Incidentally, I did some experiments with the dynamic loading and it works)
> 
> The structure I give to the code is as follows: first I define an interface for an "abstract plotter," for example
> 
>   package Plotters is 
>     type Abstract_Plotter is interface;
> 
>     procedure Line(P: Abstract_Plotter;
>                    X1,Y1, X2, Y2:Float) 
>     is abstract;
> 
>     -- And so on...
>   end Plotters;
> 
> Every actual plotter will be a descendant of Abstract_Plotters, for example
> 
>   package Plotters.SVG is
>      type SVG_Plotter is 
>      new Abstract_Plotter with private;
> 
>      -- Blah, blah ...
>   end Plotters.SVG;
> 
> In order to allow the user to select the actual plotter via the command line I use a generic package Plugin_Table that implements a kind of "map" from names to descendant of Abstract_Plotter.  Plugin_Table provides two operations:
> 
>   (1) Register_Plugin(T: Tag; Name: String)  
> 
>          used by a concrete plotter to "register" itself to the table.  Usually it is called from the "initialization" part of the body of the package definynig the concrete plotter.  For example, plotters-svg.adb could call
> 
>      Register_Plugin(SVG_Plotter'Tag, "svg");
> 
>   (2) Get_Plugin (Name:String) return Abstract_Plotter'Class
> 
>        to be called to generate a concrete plotter.
> 
> In a future version Plugin_Table will be able to search for some dynamic library to be loaded if a plugin with the given name is not registered.
> 
>    ** The Question ** 
> 
> In order to have all the built-in plugins registered in my executable, I need to "with" them,

No, that would make them elaborated too soon.  You want to delay the elaboration to run-time, so that the DLL's link-load/elaboration occurs far after the link-load/elaboration of the executable.

Although your intuition is correct regarding what needs to happen:

> so that they are linked to my code and the initialization part of their bodies is elaborated. However, no part of my code call explicitly, say, SVG_Plotter.  Therefore, my solution is to add something like
> 
>   with Plotters.SVG;
>   pragma Warnings(Off, Plotter.SVG);
> 
> in the "main." It works, but it leaves me a bit unsatisfied since it seems an "hack".  Also, since no actual code is called directly I am not sure if the binder could take the liberty of not including them (maybe it cannot, but I am not sure).
> 
> Do you have any better solution?

https://stackoverflow.com/questions/18141643/create-an-dll-written-in-ada

The StackOverflow question above points in the correct direction regarding the creation of a DLL (e.g., .so file) with GNAT*.

Next, link-load the DLL at run-time.  For GNAT, that is covered for Microsoft Windows at:

https://gcc.gnu.org/onlinedocs/gnat_ugn/Building-DLLs-with-gnatdll.html

… or for operating systems with .so shared-libraries (e.g., Linux, Unix, MacOS X**), that is covered for GNAT* at:

https://www.adacore.com/gems/gem-109-ada-plug-ins-and-shared-libraries-part-1
and
https://www.adacore.com/gems/gem-110-ada-plug-ins-and-shared-libraries-part-2

* If you are using some other vendor's nonGNAT Ada compiler, the technique would be somewhat similar.

** MacOS X has both .so shared libraries from BSD Unix heritage and .dylib (Dylan libraries) from MachO heritage.  Presumably the .dylib approach would strongly resemble much of the .so approach, but .dylib is probably interesting only if mixing Ada with Objective-C or with Swift (which you did not mention), and then the .dylib would need be supplemented Objective-C runtime framework API.  If Ada-to-Ada strictly ‘on the backend processing’ not invocable by UI/UX from Swift or from Objective-C, then stick with .so shared libraries in MacOS if at all possible.  And there is no debugger support on iDevices for GNAT (due to lack of LLDB support, due in turn to lack of modern LLVM backend for GNAT), so don't even think about iOS for the foreseeable future, which might be foreshadowing an increasing problem when/if MacOS too goes to ARM:
https://www.TheInquirer.net/inquirer/news/3033362/apple-reportedly-poaches-senior-intel-staff-for-secret-oregon-hardware-lab

> Also, I am not sure if I risk getting in trouble with Elaboration order.  I think not, but I am not 100% sure.
> What do you think?

The FSF & AdaCore references above seem to thoroughly cover elaboration order.  The topic gets impractically complex in GNAT if the DLL/.so creates its own Ada tasks, so avoid doing that by creating any needed Ada tasks in the parent executable instead.


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Plugin-based code
  2018-06-04 10:29 "Plugin°-based code mockturtle
  2018-06-04 11:59 ` Dmitry A. Kazakov
  2018-06-04 14:03 ` Dan'l Miller
@ 2018-06-04 22:08 ` Randy Brukardt
  2018-06-05  8:42 ` "Plugin°-based code Brian Drummond
  3 siblings, 0 replies; 5+ messages in thread
From: Randy Brukardt @ 2018-06-04 22:08 UTC (permalink / raw)


"mockturtle" <framefritti@gmail.com> wrote in message
news:564cec8c-8da9-4e59-a687-5408fd3a429f@googlegroups.com...
...
>In order to have all the built-in plugins registered in my executable, I
>need to
>"with" them, so that they are linked to my code and the initialization part
>of
>their bodies is elaborated. However, no part of my code call explicitly,
>say,
>SVG_Plotter.  Therefore, my solution is to add something like
>
>  with Plotters.SVG;
>  pragma Warnings(Off, Plotter.SVG);
>
>in the "main." It works, but it leaves me a bit unsatisfied since it seems
>an "hack".

Definitely.

>Also, since no actual code is called directly I am not sure if the binder
>could
>take the liberty of not including them (maybe it cannot, but I am not
>sure).

If the code could be called indirectly (via dispatching, say) it should
remain in any case (at least in a correct compilation system!). Otherwise,
it could get removed. (Janus/Ada does this automatically - it reduces the
size of Claw programs from 2.5 MB to 0.5 MB; GNAT does not to such removal,
to my knowledge - there's some tool you could use to do it.)

I'd expect from your program structure that you are registering routines to
be called via dispatching calls. That should work properly, just with a
"with", even with no direct calls. I used a similar structure in the Claw
builder for registering the supported entities (buttons, edit boxes,
dialogs, main windows, etc.), and didn't have any problems. (Well, I needed
one giant case statement to emulate a factory, 'cause Ada didn't have
anything like that at the time. So the constructor for each type is called
explicitly. But all of the other operations are called only by dispatching.)

                                                         Randy.

Do you have any better solution?

Also, I am not sure if I risk getting in trouble with Elaboration order.  I
think not, but I am not 100% sure.  What do you think?


Thanks,

Riccardo




^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: "Plugin°-based code
  2018-06-04 10:29 "Plugin°-based code mockturtle
                   ` (2 preceding siblings ...)
  2018-06-04 22:08 ` Plugin-based code Randy Brukardt
@ 2018-06-05  8:42 ` Brian Drummond
  3 siblings, 0 replies; 5+ messages in thread
From: Brian Drummond @ 2018-06-05  8:42 UTC (permalink / raw)


On Mon, 04 Jun 2018 03:29:24 -0700, mockturtle wrote:

> Dear.all,

> Let me use an example.  Suppose I am writing a program to plot, say, a
> GANTT chart.  I want the user to be able to choose the output format
> (say, PDF, SVG or even LaTeX) by giving a suitable option on the command
> line and I want to be able to add new output format easily, maybe even
> load the "format handler" as a dynamic library at run-time 

Not relevant to the actual questior, but a Cairo drawing surface (e.g. as 
in GTKAda) provides rendering to PDF and SVG directly, which may make 
life simpler. 

I don't know how easy it would be to add other formats under Cairo. It 
may be easier to add other formats above Cairo, and use the same (Cairo) 
plotter for both those.

-- Brian


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2018-06-05  8:42 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-06-04 10:29 "Plugin°-based code mockturtle
2018-06-04 11:59 ` Dmitry A. Kazakov
2018-06-04 14:03 ` Dan'l Miller
2018-06-04 22:08 ` Plugin-based code Randy Brukardt
2018-06-05  8:42 ` "Plugin°-based code Brian Drummond

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox