View on libraries with in Ada vs. bindings to C

Hi all,

One thing that has been on my mind lately is the usage of libraries in Ada. There are a lot of libraries that are bindings to C. Given the fact that Ada applications are critical, doesn’t it make more sense to rewrite these libraries in Ada so that all the benefits of it can be realized? Not that it’s a trivial thing to rewrite these C libraries.

What’s the general view here / best practice? Rewrite in Ada if it’s that critical, everything else just make sure that the C library is high quality enough?

I do understand that there are certain things that can only be done in C because the things being utilized only have C interfaces.

TIA

I never use a C library if I can implement it in Ada. Here is the full list of C stuff I use:

  • Gtk (via GtkAda)
  • OpenSSL
  • GNUTLS
  • ODBC

These are too large and complicated to be rewritten. Then there is complex stuff like numeric or image processing software which requires expertise I do not have.

The rest is 100% Ada, e.g. network protocols

  • HTTP
  • MQTT
  • ModBus
  • LDAP
  • SMTP
  • NTP

Data description nonsense:

  • JSON
  • ASN.1

The problem is that in Ada community there is a trend rather to write a binding than to search for Ada implementations. There seems to be much more trust in C code than in Ada code. Furthermore there is some strange love to esoteric and useless stuff (e.g. Curses) that produces a huge number of short-lived binding projects which are complete waste of time. I am afraid that Alire aggravated this on top because it makes people think that there is nothing outside it.

4 Likes

This is less true than you really think it is: even APIs that are published “as C” [or C compatible] are often hiding something that is not C. Take, for example, OpenGL, what it describes is an interface that abstracts over a state-machine (or is it finite automata? it’s been a while since I touched OpenGL) where the correct usage of the API is contained with certain calls occurring only between glBegin and glEnd — such an interface could be achieved in Ada with two types one representing the device, one representing the scene which has the manipulation operations (the things between glBegin and glEnd); where the second type is put into an operation of the first type whic does the glBegin and glEnd appropriately.

I bring up OpenGL specifically because despite the “common knowledge” that OpenGL is C, it is not, and was designed to be used by multiple languages — it is regrettable that the documentation/definition is in C-ish syntax, misleading people into that conclusion.

There are reasons where you might want to do a binding to C, even if your “end goal” is C-free.
Consider, for a moment, the Andor camera API; it is currently set up as a DLL with C entries. You could make a system that is end-to-end Ada, bare metal… but doing so means that you forego the benefits of having an OS: memory management, files, etc and would have to implement your own. — You would have to implement the USB protocol, and management thereof and devices, you would have to implement the DB9 com-port [IIRC], and you would have to implement the specialized connecter some of the cameras have, then you would have to implement the camera-control protocol, and over each of them. (You could do this as generic though, so not quite as bad as it may seem.)

But, sometimes you can’t be afforded the time to [essentially] clean-room implement —say that your org needs to keep producing data with the cameras to get funding— so then, you do it incrementally:

  1. First import the API into Ada, ultra-thin binding.
  2. Then use types/subtypes to restrict the return values to the API-spec allowed.
    (e.g. Post => Fn'Result in API_SUCCESS | API_FAILURE | API_UNINITALIZED.)
  3. Then “hoist” the above constraints into more appropriate types:
    (e.g. Function Fn return Boolean; -- May raise UNINITALIZED..)
  4. [Optional] Implement a package implementing the actual model in the API, like the above OpenGL example, but with Andor’s cameras.
  5. Replace SW using the old API with SW using this superthick binding, if you did that; or else rewrite/refactor the old code so that you’re simplifying it, allowing the now Ada API to handle things like the error return-values. (i.e. if you export your Ada subprograms, with Convention C, the errors that pre/post/subtypes catch can have their handlers removed from the client C-code, cleaning & simplifying that.)

And, depending on how far you go, you can remove more dependence on C until there is none, perhaps delving into direct HW control (or direct camera-protocol pushed via USB) rather than using the DLLs/drivers from Andor.

Ada’s easy foreign function interfacing makes it possible to do this with minimal disruption, and in a [generally] cost-effective manner.

This really depends on the situation; if it’s modernizing/maintenance of old tech/SW, you may have to do it as above just to keep the managerial caste happy. If it’s a new project, 100% Ada might be possible… or you might have constraints with “all the pieces” being C-ish APIs like above, with a tight schedule, in which case points 1 through 3 will give you a HUGE advantage (because you’re removing error-states and moving detection and some handling to the point-of-origin, or close to it).

But perhaps the best way to handle in that situation would be to do a good model of the problem-space/model abstracted by the API, then do the above, so that you can (if needed) swap out the C-API/bindings transparently if you need to. (Remember Ada has excellent low-level control.)

2 Likes

What alternatives are there to curses available in Ada?

Bindings are seen as the easy-going solution: “you can just use the pragma Import (C, …)”

  • Interfacing requires significant development effort.

  • Imports C strings and pointers mess, if binding is too thin.

  • Imports the conditional programming mess (#include, #ifdef, #ifndef, #undef, …)

  • Reduces portability to that of the foreign library.

  • Requires maintenance to follow the foreign API’s evolution and maintenance.

(excerpt of the Ada for Business Applications presentation :slight_smile: )
Additionally, it give the false impression that C is needed for those libraries.

4 Likes

Ada has bindings to Gtk and Qt.

P.S. Curses was always useless. Back in the time of alphanumeric serial terminals we just used ESC-sequences directly. If you happen to own an old VT100 do not bother with Curses!

Rewrite in Ada if it’s that critical, everything else just make sure that the C library is high quality enough?

If it’s not part of the program’s moat and unique selling point, I don’t see value in rewriting it. Even if you stack a thing Ada binding with Convention => C on top, you limit parameter mistakes with compatible Ada subtypes and still get Constraint_Error for issues.

What alternatives are there to curses available in Ada?

I wrote Trendy Terminal to handle command line interaction in Septum on Windows/Mac/Linux. It’s mostly Ada with a few bindings to hit some platform specific functions (things like termios, or the Windows Console API). @sbenitezb you can look at Septum to see how it is used, but it also probably needs some work.

The problem is that in Ada community there is a trend rather to write a binding than to search for Ada implementations. There seems to be much more trust in C code than in Ada code. Furthermore there is some strange love to esoteric and useless stuff (e.g. Curses) that produces a huge number of short-lived binding projects which are complete waste of time.

The goal is to get the project done, not rewrite it all in Ada. I trust widely used C library code to work on the big three desktop platforms and to have reasonable documentation.

Binding is unless you use it for something. I’ll often find an Ada implementation for something, and find that it is either (1) no longer maintained and out of date, and/or (2) were never used for a project so they have weird ergonomics or are incomplete, and/or (3) is unfinished. This is why I always promote writing shipped Ada applications, not just binding a library. If Ada is as good as people claim it is, the only way to prove it is to ship applications that people can see the quality in. Libraries are a byproduct of solving problems of shipping programs.

  • Interfacing requires significant development effort.

It depends on how much interfacing you want. Most of the things I’ve done only required a fraction of each library I’ve used. A thin C binding can be used to discover the common use cases before extracting and sharing it from the project after shipping.

I am afraid that Alire aggravated this on top because it makes people think that there is nothing outside it.

Modern software development expects easy usage of dependencies like cargo add regex (or pip install numpy, or with npm, etc.). The only appealing system I’ve seen which doesn’t do this is Odin, which ships with a bunch of pre-packaged vendored libraries which works right out of the box.

3 Likes

I never had a project that required a specific C library. Even if the customer requires some garbage like OPC UA, nobody forces you to buy a garbage C implementation for that. Sometimes you need a hardware that is vendor locked then you must write bindings to. But in general what people refer as project “done” with bindings is some hobby stuff.

Crate systems are opposite to that goal.

I won’t speak to embedded work, since I don’t work in that area. To do some some forms of application work, writing your own libraries in some areas isn’t an option. For serious 3D graphics work, you’re going to have to use DirectX or Vulkan. You’re not going to rewrite FreeType in Ada just to get the project finished.

Don’t look down on “some hobby stuff”. No one cares about Septum and people laugh at me because I wrote it in Ada, but I’ve use it professionally on code bases with tens of millions of lines (it’s actually really, really good for what I need it for). Open source is the gateway of many new developers, solving real problems or curiosities for themselves. What are they picking? Easy to install languages with package managers and plentiful libraries which have been used to ship software, like TypeScript and Rust.

I have no idea how you came to that conclusion. Standardized distribution and build tooling makes integrating libraries much easier than it has been in the past.

I don’t see why people are resistant to this as Ada’s spec/body separation should actually give it best-in-class crate semantic versioning, since the information you need to prevent API and ABI (type size and layout) breakages is in the spec files.

6 Likes

Are you sure that DirectX is not written in Basic? :rofl: Anyway, it falls into the case of complex vendor locked stuff. It is not different from drivers.

What is so difficult about installing Ada? It is basically a single line using dnf or apt.

Last time I tried Rust it failed to install the crates referred in the source code I wanted to test. It looks even worse than Alire. Which is quite expectable since crates do not scale. Alire is small and maintained by a single body. Once it grows like Rust crates do, the thing will explode, like an outhouse when you drop yeast into it.

And this exactly what crates are not. The standard distribution tool. e.g. under Debian is a Debian package. Under Fedora it is RPM etc. Crates return us back to the mess Windows has. Not to mention interoperability. It is an utter nightmare when each language would have its own crate system!

That’s great if you’re (1) on Linux and (2) the exact version of GNAT you want is available. With Alire I can give the same install instructions to Windows, Mac and Linux folks. I can also change GNAT versions if I want.

Every Rust project I’ve ever tried to build locally always works.

The npm, cargo, NuGet, PyPI and rubygems ecosystems would disagree with you here. What is your proposed alternative?

3 Likes

Once you ask a Windows user to open cmd or PowerShell you are immediately lost. It is not the way people work under Windows. Actually gpr files is an excellent cross system tool for the purpose. If you want to make it Windows friendly you can additionally provide a msi-file. What Windows lacks is an Ada policy we have under Linux, so that you would find the compiler, libraries, ads-files, gpr-files, documentation, tests in the same place.

What for? When I want to test with a legacy GNAT, e.g. 32-bit Windows or 32-bit CentOS, I do it in a virtual machine.

Keep on using package management system native to the host. As for Ada reusable libraries gpr-files is all you need. The user can simply “with” it from his project and then select the scenario. It is trivial to create a project for an externally built library if you need it, e.g. for OpenSSL or Atk and so on.

It is a pity that AdaCore dropped gpr development. It would be quite easy to make gprbuild to define the target system variables for the given compiler, so that the scenario could be set automatically. But Alire seems unable to do this either, so you lose both control and flexibility.

1 Like

Gtk or Qt is a huge code base where a binding will be far easily available as a binding than with a 100% implementation (which would fall back to a binding to Win32GUI or X11 and so on).

ODBC is a driver framework… then a binding will be unavoidable. Ok Java made a reimplementation in Java (JDBC), but Sun (and now Oracle) has enough mindset to make sure that any serious database is shipped with a JDBC driver. I don’t think an ADBC framework would have enough success. (In OCaml, we have a pure OCaml generic library, but very few drivers : MySQL, PostgresQL, SQLite… that’s it !)

OpenSSL, GNUTLS… interesting cases. Less complex than Gtk/Qt, but quite complex. Note that the OCaml world made such a thing (Mirage-TLS), which avoid some C-ish vulnerabilities (heartblead…). See Introducing transport layer security (TLS) in pure OCaml | MirageOS

I do a lot of development on Windows, and it is much more common than it used to be. It’s nice to be able to develop on a platform used by many people, even if you’re shipping a final binary through some other manner.

This is exactly what Alire does for us by stitching together the environment now. All of my Ada projects, build, test and run with exactly the same Alire commands on my Windows, Mac and Linux computers.

When you run into what might be a compiler error (which I have with GNAT in the past). Also, my Debian Bookworm server only has GNAT 12 in apt, and I can roll forward to a more recent GNAT like 14.

3 Likes

In my case it is calling gprbuild, or clicking an icon in GPS. I really see no point in whatever “commands.”

If you want a bleeding edge compiler under Debian universe then use Ubuntu.

I usually do not care about new fancy stuff in Ada. To me most changes since Ada 2005 go in a wrong direction anyway, IMO. So newly introduced bugs do not bother me. AdaCore is quite careful not to break everything. In any case I never have several native compilers on the same box. Virtual machines is the way.

An interesting analogy, BTW. Braindead crate systems aim to destroy reasonable packet management. Braindead docker aim to destroy virtualization. It looks like a plan! :grinning:

Yes, I found some. There hasn’t been a week in recent months when I haven’t come across a GNAT bug. :frowning:

As I said, I do not use new stuff where bugs are introduced. Ada is fine without square brackets, expression statements and other questionable stuff. It was a long time since I had any problem with compiling my quite large code base. GNAT 4 was a mess.

Does this mean you have an Ada implementation of OPC-UA?

I have an old Ada OPC-DA binding I want to retire (uses a very old gnatcom version) so even if I dislike it, I see we need some kind of OPC interface.

An Ada native OPC-UA client would be really nice

No. OPC UA is a total mess. One part of our business was to replace OPC UA with the middleware written in Ada. Apart from being awfully slow, OPC UA is client-server which makes it incredibly difficult in automation. The Ada middleware is a logical bus.

OPC DA is an absolutely different thing,

OPC UA is a huge overblown monster. Then there are legal issues. If I correctly remember you must be a paying member of OPC foundation.

Normally, you could use a better alternative. For example I/O terminals usually support ModBus couplers. Siemens SPS has an efficient proprietary protocol. All SPS vendors support ModBus etc.

Right, I forgot about that. But that makes me wonder about
[Python OPC-UA Documentation — Python OPC-UA 1.0 documentation]

Yes, but we have an old american site with Alan Bradley PLC which only speaks OPC-DA. We were told it was OPC-UA. And we see other customers insist on using OPC-UA. It is always a struggle to talk them out of it.
So I foresee we will need to use OPC-UA at some time and if we must, then an Ada client would have been nice. But it is a monster as you say.