2

I need a Visual Studio C++ DLL be able to call a function in my Ada mainline. The Ada code has a function spec like

package offset is
    function GET_OFFSET return integer;
    pragma Export (Stdcall, GET_OFFSET, "fnAdaOffset");
end offset;

The C++ function then would call the Ada method as follows:

typedef int (*tdAdaOffset)(void);
tdAdaOffset _ptAdaOffset = NULL;

int AdaOffset()
{
   if (_ptAdaOffset == NULL)
   {
      _ptAdaOffset = (tdAdaOffset)GetProcAddress(GetModuleHandle(NULL), "fnAdaOffset@0");
      if (_ptAdaOffset == NULL)
         throw "Function not found";
   }
   return (*_ptAdaOffset)();
}

I believe this would work. The problem I have is that Ada refuses to mark the function GET_OFFSET as external in the executable, i.e. doing dumpbin /exports ada.exe shows me no exported functions.

I've read various solutions such as --version-script for the linker , but my linker seems too old to know about this switch.

Another promising option was to add -shared to the link step, but while this now exposes the functions, it also changes the output file to a DLL (with .EXE as its extension (!)), so that's not useful either.

Before I upgrade my toolchain, is there another linker switch I can try, or any other suggestions?

Zerte
  • 1,478
  • 10
  • 9
erict
  • 1,394
  • 2
  • 14
  • 28
  • I believe you need `-Wl,--export-dynamic` to export a function from an executable, but I have never done that. – flyx Sep 17 '20 at 21:28
  • 1
    Also you shouldn't use `Stdcall` in Ada if you don't specify `__stdcall` in C++. – flyx Sep 17 '20 at 21:30
  • One question I have that might affect answers for this question is how does initialization/finalization play a role in this. Normally when using an Ada DLL in C++ you have to bind and use an initialization routine before calling Ada routines and sometimes call a bound finalization routine after. I feel like this might apply here, but not sure how specifically since the Author didn't specify if this was Ada calling c++ DLL calling Ada routine vs C++ calling C++ DLL calling Ada routine – Jere Sep 20 '20 at 19:46
  • @flyx, you were almost there: the correct switch when creating an EXE was ```-Wl,-export-all-symbols```. I had to upgrade to a newer version of GNATGPL to use it. Submit as an answer and I'll mark it as the correct one. – erict Sep 21 '20 at 17:14
  • @Jere, since my mainline is written in Ada, all the elaboration will be done by the time I'm calling the Visual C++ routine which in turn will call back into Ada. Thanks for reminder though. – erict Sep 21 '20 at 17:17
  • @erict Great! Found some additional info and distilled it into an answer. – flyx Sep 21 '20 at 20:02

1 Answers1

2

You need to tell the linker to export the symbols from the executable. ld has an option --export-dynamic, however that only works on ELF targets:

Note that this option is specific to ELF targeted ports. PE targets support a similar function to export all symbols from a DLL or EXE ; see the description of --export-all-symbols below.

Therefore, on Windows, you need to use --export-all-symbols.

Depending on how you compile your Ada code, you may need to pass this option through the compiler command (gcc). To tell gcc that an option is to be consumed by the linker, you prefix it with -Wl and then give the linker options separated by commas. In this case, you would end up with -Wl,--export-all-symbols.

If you're using GPRBuild, the relevant part of your .gpr file would look like this:

package Linker is
  for Default_Switches ("Ada") use ("-Wl,--export-all-symbols");
end Linker;

Side notes:

  • Be aware that C++'s int is not necessarily the same as Ada's Integer and you should use Interfaces.C.int for the return type in Ada instead.
  • Calling conventions must match. Stdcall in Ada matches an explicit __stdcall in C++. If you don't have __stdcall in the C++ code, use the C calling convention in Ada instead.
flyx
  • 35,506
  • 7
  • 89
  • 126