Eng-Tips is the largest engineering community on the Internet

Intelligent Work Forums for Engineering Professionals

  • Congratulations waross on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Have I missed something in FORTRAN?

Status
Not open for further replies.

PaulWDent

Electrical
Apr 22, 2013
4
0
0
US
I have been programming in FORTRAN for 47 years, from FORTRAN II up to Compaq Visual Fortran (CVF) and IVF, but recently came up against this simple thing I wanted to do, which I cannot solve either in FORTRAN or Assembler.

I have the address of a routine that I want to call (it's in a .DLL provided by somebody else).
I have the address as a 32 bit value in a Fortran Integer SUBNAM.

But you cannot write CALL SUBNAM( argument list )

This is because I actually want an indirect call, not to the address of SUBNAM, but to the address it contains.

So I went into assembler and passed SUBNAM in as an argument, and tried to JMP to that address after saving a return address on the stack. I also tried pushing it on the stack and just doing a RET.

No dice. ACCESS VIOLATION!!

One reason may be that the assembly code may be trying to jump to an absolute address, but this is not allowed. You are only allowed to touch virtual addresses. And user ring programs are not allowed to have access to the mapping from one to the other.

Does anyone know how to solve this?

Of course this is all Microsoft and Intel's fault for making such a dog's breakfast of the PC programming model.
 
Replies continue below

Recommended for you

You didn't say whether you're using F77 or F90 or later. Anyway, this works on F77. If you need F90, you need to mess with interface definitions. F90 etc may be the latest greatest stuff but it is what Chinese call luan.
Code:
      ! A sample subroutine
	subroutine subsub (x)
         real x
         print *, "subsub called ", x
      end subroutine subsub

	! A sample function
      integer function funcfunc (single)
         integer single
         funcfunc = single + single
      end function funcfunc

	! Calling a subroutine indirectly
      subroutine callsub (subname, param)
         external subname
	   real param
         call subname (param)
      end subroutine callsub

	! Calling a function indirectly
      subroutine callfunc(funcname, param)
         integer funcname
         external funcname
         integer ires

         ires = funcname(param)
         print *, "funcname(param) returned ", ires
      end subroutine callfunc

      ! Usage
      program funcs
         integer funcfunc
         external subsub, funcfunc
      
         call callsub (subsub, 3.14)
         call callfunc (funcfunc, 100)
         stop
      end
 
Just in case you want the hippy method of doing things
Code:
subroutine subsub (x)
   real, intent(in):: x
   print *, "subsub called ", x
end subroutine subsub

integer function funcfunc (single)
   integer, intent(in):: single
   funcfunc = single + single
end function funcfunc

subroutine callsub (subname, param)
   interface
      subroutine subname(rr)
         real, intent(in):: rr
      end subroutine subname
   end interface
   real, intent(in):: param
   call subname (param)
end subroutine callsub


subroutine callfunc(funcname, param)
   interface
      integer function funcname(ii)
         integer, intent(in):: ii
      end function funcname
   end interface
   integer, intent(in):: param
   integer:: ires

   ires = funcname (param)
    print *, "funcname(param) returned ", ires
end subroutine callfunc

program funcs
   integer funcfunc
   external subsub, funcfunc

   call callsub (subsub, 3.1415)
   call callfunc (funcfunc, 30)

   stop
end

Both GNU and PS4/DVF/CVF/IVF have a function called LOC which gets the address of the function, similar to what comes back from GetProcAddress which is used with LoadLibrary which you can use for late loading DLLs.
 
I tried passing the subroutine name in an argument list, but that only works if the subroutine is linked into your program. It doesn't work if SUBNAM is a variable with an address in it. However, I have now found the solution, in an obscure part of CVF that I had never had to use before. Here is the solution:

(1) in the IDE, do "Add to Project". "files" amd locate the .dll you want to use. Alternatively copy the .dll to the program's workspace.

(2) put
USE kernel32
at the start of the main program to provide access to Windows APIs.

(3) For each subroutine or function that the .dll provides that you want to use, provide an INTERFACE block like

INTERFACE
SUBROUTINE NAME1(...argument list...)
...declare the types of the arguments....
If the .dll routines use C calling conventions, add
!DEC$ ATTRIBUTES CALLER
(this means that it is the calling program that will clean up the stack)
I have not found it necessary in the interface to specify whether arguments are
passed by value or address. This happens in the call, later.)
END SUBROUTINE NAME1
END INTERFACE

(replace SUBROUTINE by FUNCTION if it returns a value, as most C is written to do)

The names such as NAME1 above are your choice.

(3) In the program declarations,include statements like
POINTER(P1,NAME1)
for every subroutine or function that the .dll provides that you want to use. "NAME1" is the same name as you chose for the associated INTERFACE block

Also include a declaration
POINTER(PLIB,ILIB)

(4) in the program, load the .dll by including the windows API statement
PLIB-LOADLIBRARY("dllname.dll")
the logical variable STATUS will come back true of the .dll was loaded corrctly.

(5) For each subroutine or function you want to use, include a windws API statement like
P1=GETPROCADDRESS(PLIB,"subnam1"C)
"subnam1" is what the routine is called (case sensitive) in the .dll, not your name choice. They can be the same however, but FORTRAN will ignore the case. The suffix C passes the string as a C string, that is null-terminated, and no length passed. This sets up the subroutine or function you chose to call by the FORTRAN name NAME1.

(6) Now you can call the subroutine NAME1 in your program by writing

CALL NAME1(....argument list...). where for every scalar variable that is an input argument such as "N" you write %VAL(N) to make FORRTAN pass it by value if the .dll requires it. Output variables that are returned as well as strings and arrays are passed by address so do not need %VAL()

For a function you can write STATUS=NAME1(....argument list...) likewise

(7) Now, if you want to call such a subroutine or function inside another subroutine other than the program in which the above initialization was done, you have to repeat the declarations like:

POINTER(P1,NAME1)
in the subroutine in which you want to use NAME1 and you have to pass the pointers P1 in in the argumant list or better, by declaring a NAMED COMMON to hold them, thus avoiding encumbering the argument list
 
Correction of typo in last post:

Step 4 should read

(4) in the program, load the .dll by including the windows API statement
PLIB=LOADLIBRARY("dllname.dll")

PLIB comes back as zero if the .dll failed to load.

 
Yes, I forgot about the Cray pointers. It isn't really obscure: it is just that not many Fortran programmers use it. C/C++ programmers use that all the time. Late loading is one of the banes of my life. You don't know the dependencies until that part of the code runs so you don't know that you have to supply particular DLLs with the executable for the system to run. Then you get a call from site that says a DLL is missing and you have to send them a hot fix. This happens all the time in C#.
 
Status
Not open for further replies.
Back
Top