I am having trouble correctly accessing a variable in a Fortran DLL from a Fortran EXE when the variable is part of a COMMON
block.
I have a trivial code simple.f90 which I compile into a DLL using MSYS64/MinGW-w64 gfortran 9.2 as
x86_64-w64-mingw32-gfortran simple.f90 -o simple.dll -shared
! simple.f90
module m
implicit none
integer :: a, b
!common /numbers/ a, b
end module
subroutine init_vals
use m
implicit none
a = 1
b = 2
end subroutine
This library is used from a even simpler program prog.f90, compiled as
x86_64-w64-mingw32-gfortran prog.f90 -o prog -L. -lsimple
! prog.90
program p
use m
implicit none
print *, 'Before', a, b
call init_vals
print *, 'After', a, b
end program
When the COMMON
block /numbers/
is commented out, the code works and prints the expected result:
Before 0 0
After 1 2
However, when I uncomment the COMMON
block, the output becomes
Before 0 0
After 0 0
as if the variables used by the program were suddenly distinct from those used in the library.
Both variants work equally well in a Linux-based OS with gfortran 9.1.
I am aware that "On some systems, procedures and global variables (module variables and COMMON blocks) need special handling to be accessible when they are in a shared library," as mentioned here: https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gfortran/GNU-Fortran-Compiler-Directives.html . However, I was not able to insert a statement of the type
!GCC$ ATTRIBUTES DLLIMPORT :: numbers
or
!GCC$ ATTRIBUTES DLLEXPORT :: numbers
anywhere in the code without being snapped at by the compiler.
As pointed out by M. Chinoune in the comment, current gfortran lacks the ability to import common blocks from DLLs. Even though there has been a patch for some time, it is not yet merged. In the end, I needed two things to make the above code work:
First, apply the following patch to GCC 9.2 and compile the compiler manually in MSYS2:
--- gcc/fortran/trans-common.c.org 2019-03-11 14:58:44.000000000 +0100
+++ gcc/fortran/trans-common.c 2019-09-26 08:31:16.243405900 +0200
@@ -102,6 +102,7 @@
#include "trans.h"
#include "stringpool.h"
#include "fold-const.h"
+#include "attribs.h"
#include "stor-layout.h"
#include "varasm.h"
#include "trans-types.h"
@@ -423,6 +424,9 @@
/* If there is no backend_decl for the common block, build it. */
if (decl == NULL_TREE)
{
+ unsigned id;
+ tree attribute, attributes;
+
if (com->is_bind_c == 1 && com->binding_label)
decl = build_decl (input_location, VAR_DECL, identifier, union_type);
else
@@ -454,6 +458,23 @@
gfc_set_decl_location (decl, &com->where);
+ /* Add extension attributes to COMMON block declaration. */
+ if (com->head)
+ {
+ attributes = NULL_TREE;
+ for (id = 0; id < EXT_ATTR_NUM; id++)
+ {
+ if (com->head->attr.ext_attr & (1 << id))
+ {
+ attribute = build_tree_list (
+ get_identifier (ext_attr_list[id].middle_end_name),
+ NULL_TREE);
+ attributes = chainon (attributes, attribute);
+ }
+ }
+ decl_attributes (&decl, attributes, 0);
+ }
+
if (com->threadprivate)
set_decl_tls_model (decl, decl_default_tls_model (decl));
Second, only the line
!GCC$ ATTRIBUTES DLLIMPORT :: a, b
was needed in the main program (right after implicit none
), but not any exports anywhere. This is apparently a different syntactical approach then in Intel Fortran, where one imports the COMMON
block rather than its constituents. I also found out that I needed to import both a
and b
even if I only needed b
. (When only a
was needed, importing a
only was enough.)