Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Sharing COMMON data between DLLs

Nick2
New Contributor I
915 Views

For this particular project, I have created A.dll, B.dll, and C.exe.

A.dll does a whole lot of calculations, and keeps the numbers in COMMON blocks.  Given the sheer number of variables and COMMON blocks, I have attempted to export them like so in a single subroutine:

      IMPLICIT DOUBLE PRECISION (A-H,K-Z)

      COMMON /FUN2       / CFUN2     (        76)
!DIR$ ATTRIBUTES DLLEXPORT :: /FUN2/

C.exe will load A.dll, direct A.dll to initialize itself, and populate the variables in the COMMON blocks.  So far so good!  But now, C.exe loads B.dll, and calls one special subroutine in B.dll.  Now, B.dll contains this:

      SUBROUTINE Special()

      !DIR$ ATTRIBUTES DLLIMPORT :: /FUN2/

      DOUBLE PRECISION C1, C2, C3, ...
      COMMON/FUN2/C1, C2, C3(2,11), ...

But unfortunately, once I step into Special(), the value of C1 is equal to 0.  If I'm in A.dll, the value of C1 is non-zero.  So, I have the impression that I messed up somewhere in sharing the data in the COMMON blocks between A.dll and B.dll.  Any idea what I'm doing wrong?

 

0 Kudos
6 Replies
Kevin_D_Intel
Employee
915 Views

There is a DLL example that demonstrates this. Its included w/the PSXE 2016 release (<install-dir>\IntelSWTools\samples_2016\en\compiler_f\PSXE\DLL.zip), or online for PSXE 2017 at: https://software.intel.com/en-us/product-code-samples (filter for Fortran and Windows), or try the direct link: https://software.intel.com/en-us/node/611720

Perhaps the missing ingredients are explicit initialization and the linker options, /section:.data,rws for the DLL containing the shared data.

0 Kudos
Nick2
New Contributor I
915 Views

Oh.

  1. The variables must be initialized to a non-zero value so that they are placed in the .data image section
  2. The linker command line options are modified to add: /section:.data,RWS

Bummer I didn't find this sooner.  Now that I look at it, my variables that are in BLOCK DATA are visible from B.dll.  And I never added the linker flag from the second requirement.

May I suggest somebody to update this page?  https://software.intel.com/en-us/node/535307

Is there any way to bypass the 1st requirement?  Some unknown fraction of my COMMON variables are declared in 3000-lines worth of BLOCK DATA; some unknown fraction is declared in BLOCK DATA that initializes it to 0, and I also rely on /Qinit:zero

0 Kudos
LRaim
New Contributor I
915 Views

You may check with Depwalker  if the common is really exported/imported.

Another solution is to pass the base address of the common and its length when the DLL is called and acquire its content. Copy back the common content at the DLL exit point. 

0 Kudos
Kevin_D_Intel
Employee
915 Views

I’m uncertain about the reason for the non-zero initialization and inquired about that and if it can be bypassed.

As I understand it, the linker flag makes the data explicitly read-write-sharable so that may not be applicable for all use cases, and we’ll look at improvements to the User’s Guide on these.

0 Kudos
jimdempseyatthecove
Honored Contributor III
915 Views

Try using

COMMON /hack/ hackIgnoreThis
!DIR$ OBJCOMMENT LINKER: "/SECTION:.data,RWS"
COMMON /YourSharedCommon/ A,B,C,...

Notes,

a) hackIgnoreThis and A,B, C are uninitialized and are expected to be placed into .bss section.
b) The /hack/ common block strategically placed before your shared and uninitialized common is placed in there such that the compiler is condition to having the output set to the .bss section. IOW any immediately following common block, intended for .bss, do not require a linker directive to set the section.
c) The linker comment is intended to redirect the output (to same section) to the .data section.

The unknowns are:

1) If the compiler would not reissue a section comment back to the .bss following the !DIR$
2) If the compiler supports !DIR$ OBJCOMMENT LINKER:"..."
(if not, why not???)

Jim Dempsey

0 Kudos
Kevin_D_Intel
Employee
915 Views

My apologies. Following some consultation, I learned my advice was incorrect for your use case.

The DLL example applies when data is to be shared across two or more separately running programs. In your case, to share data among a main program and DLLs in the same running program, neither the linker directive nor initialization is required.

To borrow from the guidance I received:

In your case, it is required that shared data (be it COMMON or a module) be linked into its own DLL, DLLEXPORTed, and any other DLL or EXE that wants to use the data, link directly to the shared data DLL and DLLIMPORT the data. (If the data is in a module, the DLLIMPORT happens automatically if the module DLLEXPORTs the data, but for a COMMON you have to do it yourself.) There is no requirement for initialization. Note the example below uses BLOCK DATA for the COMMON DLL definition.

The example below was also provided to me to help demonstrate your use case.
 
cmndll.f90 declares the COMMON block and DLLEXPORTs this. dllsub.f90 is a subroutine that uses the COMMON and updates it, and then main.f90 also uses the COMMON and calls the subroutine.  The intent is to show that the data is shared.

Build the example like this:

ifort /dll /libs:dll cmndll.f90
ifort /dll /libs:dll dllsub.f90 cmndll.lib
ifort /libs:dll main.f90 dllsub.lib cmndll.lib

Note the use of /libs:dll – this makes sure that all code is referencing the DLL form of the run-time library. When run you see:

Main program, setting sharedvar to 486
Calling DLLSUB
Entering DLLSUB, sharedvar =          486
Leaving DLLSUB, sharedvar =          487
Back in main program, sharedvar =          487

This demonstrates that the data is shared between the main program and the subroutine, with the data residing in the shared DLL.

cmddll.f90:

block data cmndll
integer sharedvar
common /cmn/ sharedvar
!DEC$ ATTRIBUTES DLLEXPORT :: /cmn/
end block data cmndll

dllsub.f90:

subroutine dllsub
!DEC$ attributes dllexport :: dllsub
integer sharedvar
common /cmn/ sharedvar
!DEC$ ATTRIBUTES DLLIMPORT :: /cmn/

print *, "Entering DLLSUB, sharedvar = ", sharedvar
sharedvar = sharedvar + 1
print *, "Leaving DLLSUB, sharedvar = ", sharedvar
end subroutine dllsub

main.f90:

program main
integer sharedvar
common /cmn/ sharedvar
!DEC$ ATTRIBUTES DLLIMPORT :: /cmn/

print *, "Main program, setting sharedvar to 486"
sharedvar = 486
print *, "Calling DLLSUB"
call dllsub
print *, "Back in main program, sharedvar = ", sharedvar
end program main

I apologize again for the mis-information for your use case.

0 Kudos
Reply