Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
Beginner
7 Views

Passing function pointers to a Fortran DLL

I want to pass callback routines in to the DLL I have compiled from Fortran source. One of the parameters is already a pointer to an object that includes the pointers to the callback functions. 

How do I dereference the function pointer fields from the pointer to the structure that was passed in? (The structure is actually an object but that shouldn't make any difference, right?)

0 Kudos
7 Replies
Highlighted
Black Belt
7 Views

Are these TYPE(C_FUNPTR)

Are these TYPE(C_FUNPTR) function pointers?  If so, use C_F_PROCPOINTER to associate a Fortran procedure pointer with the procedure address that is in the C_FUNPTR object.

(If they are already Fortran procedure pointers, then they are dereferenced automatically in any context that expects a procedure, e.g. if they are used as the procedure designator in a call statement or function statement.)

If they are the non-standard integer pointer (sometimes called cray pointers) things, then you use the POINTER(pointer, pointee) statement, where pointee is the name of a specific procedure that has been described in an interface block or similar.

(The standard C interoperability stuff (C_PTR, C_FUNPTR, etc) should be preferred to the integer pointer extension stuff.)

If you showed some code that might encourage more specific examples.

0 Kudos
Highlighted
Beginner
7 Views

Here is the current interface

Here is the current interface spec for one of the functions I want the fortran DLL to be able to call. It is currently being passed to it by way of an additional pascal dll that essentiually duplicates the code that is in the main executable (also written in pascal)

interface
        Integer FUNCTION FREF18(base,leg,i1,i2,ain)
        !DEC$ ATTRIBUTES STDCALL, DLLIMPORT :: FREF18
        !DEC$ ATTRIBUTES REFERENCE :: leg, ain
        Integer base,i1,i2
        Character leg*(*), ain*(*)
        end FUNCTION
    end interface

Then there is the association of the pointer with the function call to be used later in the code.

Pointer(pFREF18,         FREF18)

The main entry point for the fortran DLL that is called from the main pascal executable looks like this:

 SUBROUTINE CORETG(nfunci, erri, pblui)
      !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, REFERENCE,
    1 ALIAS:'_CORETG' :: CORETG

The parameters are defined like this:

INTEGER*4 nfunci, erri, pblui

And part of the initial code in this function is the following:

     dllaux = 'WTFuncAux.DLL'C
    pdllDelphi = LoadLibrary(dllaux)

    IF (pDLLDelphi.ne.NULL) THEN
         pFREF18     = GetProcAddress(pDLLDelphi, 'FREF18'C)
    Else ...

Then later there is a call to this function:

NRC = FREF18(BASE, BLOCK, I1, I2, AIN)

 

What I want to do is pass the pointer to the function FREF18 directly to the DLL function CORETG instead of using the intermediate DLL written in pascal that duplicates a lot of the code in the main program. (That seems wasteful and unnecessary and makes the job of maintaining program state more complicated.)

In the main code, FREF18 is declared like this:

function FREF18(Base: integer; leg: pansichar; i1, i2: integer; ainx: pansichar): Integer; stdcall;

And it has a normal looking definition which I won't bother to put here.

 

I hope that is detailed enough.

 

 

0 Kudos
Highlighted
Black Belt
7 Views

That Fortran code is using

That Fortran code is using the integer pointer extension.  For consistency I'll also use that below, but for new code I would prefer to use the standard F2003 C interoperability language features.

I don't know delphi from a bar of soap - so you will need to sort out what the interface of the modified Fortran procedure looks like to delphi, and how you go about acquiring the address of a procedure on the delphi side (@xxx?) - but the modifications to the Fortran code are quite straight forward.  You need to add a dummy argument to allow the address of the procedure of interest to be passed, and delete the code that currently gets that address from the DLL.
 

SUBROUTINE CORETG(nfunci, erri, pblui, fun_iptr)
  IMPLICIT NONE
  
  !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, REFERENCE, ALIAS:'_CORETG' :: CORETG
  INTEGER*4 :: nfunci, erri, pblui
  INTEGER(INT_PTR_KIND()), INTENT(IN) :: fun_iptr
  
  INTERFACE
    INTEGER FUNCTION FREF18(base, leg, i1, i2, ain)
      IMPLICIT NONE
      !DEC$ ATTRIBUTES STDCALL :: FREF18
      !DEC$ ATTRIBUTES REFERENCE :: leg, ain
      INTEGER :: base,i1,i2
      CHARACTER :: leg*(5), ain*(5)
    END FUNCTION FREF18
  END INTERFACE
  
  POINTER(fun_iptr, FREF18)

  ... 
  
  r = FREF18(base, leg, i1, i2, ain)
END SUBROUTINE CORETG

 

0 Kudos
Highlighted
Beginner
7 Views

I tried that and now I am

I tried that and now I am getting this:

error #6401: The attributes of this name conflict with those made accessible by a USE statement.   [FREF18]

I should tell you that the interface spec you show above as defined within the scope of the subroutine CORETG is actually in my ase defined in a separate module which is included here with a USE statement. Other than that, I changed all my code to mirror what you show above. Is there something else maybe I am missing? I know you probably typed out your suggestion without trying to compile it so maybe there is something missing?

0 Kudos
Highlighted
Black Belt
7 Views

Remember - I can't see your

Remember - I can't see your code.

Bar some scope-local attributes not relevant here, once an entity is defined in a module its definition is complete - you can't then add attributes to the entity in a scope that accesses the entity via a use statement that fundamentally change the nature of the entity. 

Without the integer POINTER statement the interface definition defines an external procedure identified externally by its name, but in combination with the integer POINTER statement it defines a procedure identified by the address in the given integer object.

Move the interface definition into the same scope that has the integer POINTER statement.  If that creates problems for the organisation of your code elsewhere, then some other changes might be required, such as making FREF18 in the module a Fortran procedure pointer that gets associated to the address passed from delphi in CORETG.  As part of these changes I would suggest abandoning the integer pointer extension, and moving to standard Fortran's C interoperability feature.

This was the original test case I compiled at the time using ifort 16.0.4.  I don't have access to (or know how to write) delphi, so I mocked up a main program and the FREF18 function.

SUBROUTINE CORETG(nfunci, erri, pblui, fun_iptr)
  IMPLICIT NONE
  
  !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, REFERENCE, ALIAS:'_CORETG' :: CORETG
  INTEGER*4 :: nfunci, erri, pblui
  INTEGER(INT_PTR_KIND()), INTENT(IN) :: fun_iptr
  
  INTERFACE
    INTEGER FUNCTION FREF18(base, leg, i1, i2, ain)
      IMPLICIT NONE
      !DEC$ ATTRIBUTES STDCALL :: FREF18
      !DEC$ ATTRIBUTES REFERENCE :: leg, ain
      INTEGER :: base,i1,i2
      CHARACTER :: leg*(5), ain*(5)
    END FUNCTION FREF18
  END INTERFACE
  
  POINTER(fun_iptr, FREF18)
  
  INTEGER :: base, i1, i2, r
  CHARACTER(5) :: leg, ain
  
  ! Dummy values.
  base = nfunci
  i1 = erri
  i2 = pblui
  leg = 'leg'
  ain = 'ain'
  
  r = FREF18(base, leg, i1, i2, ain)
END SUBROUTINE CORETG


MODULE m
  IMPLICIT NONE
CONTAINS
  INTEGER FUNCTION FREF18(base, leg, i1, i2, ain)
    !DEC$ ATTRIBUTES STDCALL :: FREF18
    INTEGER :: base, i1, i2
    CHARACTER :: leg*(5), ain*(5)
    !DEC$ ATTRIBUTES REFERENCE :: leg, ain
    
    PRINT *, 'Hello'
    FREF18 = 0
  END FUNCTION FREF18
END MODULE m

PROGRAM you_have_to_write_this_bit_in_delphi
  USE m
  IMPLICIT NONE
  
  INTERFACE
    SUBROUTINE CORETG(nfunci, erri, pblui, fun_iptr)
      IMPLICIT NONE
      !DEC$ ATTRIBUTES STDCALL, REFERENCE, ALIAS:'_CORETG' :: CORETG
      INTEGER*4 :: nfunci, erri, pblui
      INTEGER(INT_PTR_KIND()), INTENT(IN) :: fun_iptr
    END SUBROUTINE CORETG
  END INTERFACE
  
  INTEGER*4 :: nfunci, erri, pblui
  
  ! Dummy values.
  nfunci = 1
  erri = 2
  pblui = 3
  
  CALL CORETG(nfunci, erri, pblui, LOC(FREF18))
END PROGRAM you_have_to_write_this_bit_in_delphi

 

0 Kudos
Highlighted
Beginner
7 Views

I am having no luck.

I am having no luck.

The parameter passed to the CORETG function from the pascal code seems right to me. When I look at it from the fortran debugger, the other parameters have the values I expect. But it looks like the function pointer has, instead of the address of the function, the value of the first four bytes of the code at that address. It calls the function and throws an access violation exception, which I would expect because the first four bytes interpreted as an address are way outside valid memory range!

 

I suspect the problem is on the calling side, and not the called side. Any ideas? Anyone?

0 Kudos
Highlighted
Black Belt
7 Views

With the attributes specified

With the attributes specified for the CORETG procedure, the thing passed for the fun_iptr argument should be the address of the address of the procedure to call, i.e. a pointer to a pointer, if delphi has that sort of concept.

There is a redundant level of indirection with that argument.  Ordinarily I would expect that could be removed by applying ATTRIBUTES VALUE to the fun_iptr argument, but in this instance I cannot get it to work.  From investigating the generated assembly it appears that the procedure reference is constructed still assuming the integer argument is passed by reference.  Perhaps other have better ideas (delphi-with value, compiled on x86 using /debug /check:all /warn:all /Od), my !DEC$ limit has been reached for the day.

But I might have made this more complicated than it needs to be.  If you are just passing a few procedures, then you can pretty much just specify each procedure directly as a dummy argument.  Try the following Fortran variant, which should just require a single level of indirection on the delphi side.

SUBROUTINE CORETG(nfunci, erri, pblui, FREF18)
  IMPLICIT NONE
  
  !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, REFERENCE, ALIAS:'_CORETG' :: CORETG
  INTEGER*4 :: nfunci, erri, pblui
  
  INTERFACE
    INTEGER FUNCTION FREF18(base, leg, i1, i2, ain)
      IMPLICIT NONE
      !DEC$ ATTRIBUTES STDCALL :: FREF18
      INTEGER :: base, i1, i2
      CHARACTER :: leg*(5), ain*(5)
      !DEC$ ATTRIBUTES REFERENCE :: leg, ain
    END FUNCTION FREF18
  END INTERFACE
  ...
END SUBROUTINE CORETG

If you have lots of procedures, then you may need to consider passing an array of procedure pointers - which will look like an array of pointer sized integers or an array of C_FUNPTR objects across the EXE/DLL boundary.

0 Kudos