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

Passing a subroutine as an argument in a DLL

jdchambless
Beginner
772 Views

Hello All,

I've been searching on the forum intermittently for about a month now, and either my problem isn't represented, or I'm not looking in the right places. Nonetheless, here it is: I have created a Visual Studio / Fortran program that uses a Visual Studio GUI to call the Fortran DLL. Everything seems to work fine in that regard. My problem is that, within the Fortran DLL, I am attempting to pass a subroutine as an argument into the November 2003 version of LLNL's DLSODA subroutine (I'm sure it doesn't matter what the destination is, but just in case). Here's the call that occurs within the DLL:

CALL dlsoda(fex_fun,neq,f,xin,xout,itol,rtol,atol, &

itask,istate,iopt,rwork,lrw,iwork,liw,jac,jt)

The subroutine is "fex_fun", and is declared elsewhere as:

SUBROUTINE fex_fun(NEQ, X, F, FPRIME)

How should I declare (and where) this fex_fun subroutine so that I can call it as an argument in another subroutine? I've tried using the EXTERNAL declaration, but I must not be using it correctly. Any help would be appreciated.

0 Kudos
7 Replies
Steven_L_Intel1
Employee
772 Views
Is there an explicit interface for dlsoda? If so, you'll need to match how it declares the routine argument. It may have "external", in which case all you need is:

external fex_fun

or it may have a complete explicit interface nested within the interface for dlsoda. If that's the case, then you'll need to write a compatible explicit interface for fex_fun.

Is this dlsoda Fortran code that you also compiled with the Intel compiler, or is it in some other library or DLL? If it isn't Fortran source, you will need to know dlsoda's calling convention.

What error messages are you seeing?
0 Kudos
jdchambless
Beginner
772 Views


Hi Steve. Thanks for the quick reply. The DLSODA routine is part of the much larger ODEPACK.F file put out by LLNL. Though, I am attempting to only use the part concerned with DLSODA, since the whole file is more than 27,000 lines long. I am compiling the DLSODA routine by including it in a separate file and module, like this:

!dlsoda.f90
MODULE DLSODA_MOD
public :: dlsoda
contains
subroutine
dlsoda(FEX_FUN,...)
external
FEX_FUN
...
end subroutine dlsoda
END MODULE
DLSODA_MOD

The FEX_FUN subroutine is placed in the same file as the subroutine that calls DLSODA, like this:

!main.f90
MODULE main_mod
public :: caller_sub
contains
subroutine caller_sub(...)
use DLSODA_MOD
...
CALL dlsoda(FEX_FUN,...)
...
end subroutine caller_sub
!!**************************!!
subroutine FEX_FUN(...)
...
end subroutine FEX_FUN

END MODULE main_mod

I attempted to simply put "external FEX_FUN" in the declarations of the caller_sub subroutine, but I get this error:

MAIN.obj : error LNK2019: unresolved external symbol _FEX_FUN referenced in function _MAIN_MOD_mp_CALLER_SUB

Let me know if any more information is required.

0 Kudos
Steven_L_Intel1
Employee
772 Views
Ah, ok. Take out the EXTERNAL in main.f90 - you don't need it. Using the module main_mod properly declares fex_fun. You would need EXTERNAL (or an interface block) only if you did not already have an explicit interface visible for fex_fun. Since you do, nothing else is required.
0 Kudos
jdchambless
Beginner
772 Views
Hi Steve. That appears to have worked great. Would you mind elaborating a little on why this worked?

Thanks again,
Jason
0 Kudos
Steven_L_Intel1
Employee
772 Views
Let's say you have:

call foo(mysub)

The compiler needs to figure out what "mysub" is - it could be a variable or it could be a routine. If no other information is available, it's a variable. Simple. If you give mysub a type, it could still be a variable or a function. Somehow the compiler has to know it's a routine.

The F77 way to do this, which was the only way in F77, was to add an EXTERNAL declaration for mysub. This informs the compiler that it is an external procedure, or "confirms" that mysub is a BLOCK DATA subprogram which is part of the application. Either way, this gives the compiler enough information to generate an external reference to mysub (normally _MYSUB on IA-32) and pass that address as the argument.

In F90, we have explicit interfaces which can be provided several ways. Any of them, if visible to the caller, will let the compiler know that mysub is a routine and not a variable. When mysub is a module procedure the compiler also knows how to decorate the external name relating to the module, for example, _MODNAME_mp_MYSUB, since you could theoretically have two or more routines named MYSUB in the application (just can't use the name if two definitions are visible at the same time.)

So in your program, the compiler already knew that fex_fun was a function and how to find it - nothing else was needed. In fact, adding EXTERNAL hid your module procedure ext_fun and told the compiler that you wanted some other external procedure, which is what produced the reference to _EXT_FUN.

Note that while it is standard-conforming to pass a module procedure as an actual argument, it is NOT standard to pass a contained procedure (a procedure contained inside another procedure.) The standard has a note saying how this should work if a compler should choose to support this as an extension, and indeed Intel Fortran does.
0 Kudos
jdchambless
Beginner
772 Views
I appreciate the explanation. I am relatively new to Fortran, so the conceptual stuff really helps. I had another one for you (or anyone in here) if you have a minute:
In that same setup as above, there is a variable declared in caller_sub called RTEMP. I need to use RTEMP in the FEX_FUN subroutine, but that variable isn't passed into FEX_FUN as an argument. So, conceptually, how do I give a subroutine access to a variable that has been used previously in other subroutines, without actually passing it in as an argument? I'm sure this is exceedingly easy to do, but probably so easy that I'm overlooking it (as we engineers tend to do).

Thanks.
0 Kudos
anthonyrichards
New Contributor III
772 Views

I like the easy ones...

Put it in a MODULE and USE the module in the routines in which you want the variable to be available.

MODULE MYGLOBALS
INTEGER I
REAL*8 R
CHARACTER*100 FILENAME
END MODULE

SUBROUTINE FOO(A,B,C,D, E, J,K)
USE MYGLOBALS
INTEGER J,K
REAL*8 A,B,C,D,E
R=A
I=J
RETURN
END SUBROUTINE FOO

0 Kudos
Reply