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:
!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?
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.
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
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.
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.
COMMON /hack/ hackIgnoreThis !DIR$ OBJCOMMENT LINKER: "/SECTION:.data,RWS" COMMON /YourSharedCommon/ A,B,C,...
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???)
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.
block data cmndll integer sharedvar common /cmn/ sharedvar !DEC$ ATTRIBUTES DLLEXPORT :: /cmn/ end block data cmndll
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
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.