Create .lib file with c++ and Fortran / Call c++ code from Fortran / Unresolved external symbol

2.9k Views Asked by At

I am attempting to create a .lib library file that contains Fortran functions that call c++ functions, but I am getting the dreaded "error LNK2019: unresolved external symbol...". The code will eventually be compiled with a bunch of other libraries as a DLL and used in a separate program (PSSE). I am getting the compile error when PSSE attemps to create the DLL using my library. Here is the code I attempting to use, followed by the compiling code. The code should just add two numbers together and output the answer.

fort_code.f

  SUBROUTINE TESTCPP ( II, JJ, KK, LL )
  INCLUDE 'COMON4.INS'

  integer*4, external :: CPPFUNCTION

  INTEGER a, b, c, test

  IF (.NOT. IFLAG) RETURN

  a = ICON(II)
  b = ICON(II + 1)
  test = CPPFUNCTION( a , b, c )
  WRITE ( ITERM, * ) 'C = ', c
  RETURN
  END

cpp_code.cpp

extern "C" {
      void _CPPFUNCTION(int a, int b, int *c);
}

void _CPPFUNCTION(int a, int b, int *c) {
      *c = a + b;
    }

compile.bat

cl /nologo /MD /c /W3 /O2 /FD /EHsc /errorReport:prompt /D"MSWINDOWS" /D"WIN32" ^
   /D"_WINDOWS" /D"NDEBUG" "cpp_code.cpp"

IFORT /nologo /Od /Oy- /assume:buffered_io /traceback /libs:dll /threads /c /Qip ^
      /extend_source:132 /noaltparam /fpscomp:logicals /warn:nodeclarations ^
      /warn:unused /warn:truncated_source /Qauto /Op /iface:cvf /define:DLLI ^
      /include:"C:\Program Files (x86)\PTI\PSSE32\PSSLIB" ^
      /object:"fort_code.OBJ" ^
      "fort_code.f" 

lib /out:fort_cpp.lib fort_code.obj cpp_code.obj

When the PSSE program attempts to create the DLL, this is the output I get:

 ifort /nologo /assume:buffered_io /traceback /libs:dll /threads /c /Qip /extend_source:132 /noaltparam /fpscomp:logicals /Qprec /warn:declarations /warn:unused /warn:truncated_source /Qauto /fp:source /iface:cvf /define:DLLI /include:"C:\Program Files (x86)\PTI\PSSE32\PSSLIB" /object:"C:\temp\INIT_620289\11hw2ap_conec.obj" /module:"C:\temp\INIT_620289" "11hw2ap_conec.f"


 ifort /nologo /assume:buffered_io /traceback /libs:dll /threads /c /Qip /extend_source:132 /noaltparam /fpscomp:logicals /Qprec /warn:declarations /warn:unused /warn:truncated_source /Qauto /fp:source /iface:cvf /define:DLLI /include:"C:\Program Files (x86)\PTI\PSSE32\PSSLIB" /object:"C:\temp\INIT_620289\11hw2ap_conet.obj" /module:"C:\temp\INIT_620289" "11hw2ap_conet.f"


 link /INCREMENTAL:NO /NOLOGO /DLL /SUBSYSTEM:WINDOWS /MACHINE:X86 /ERRORREPORT:PROMPT @"C:\temp\INIT_620289\linkfilestod9p1.txt" /OUT:"C:\temp\INIT_620289\11hw2ap_dsusr.dll" /map:"C:\temp\INIT_620289\11hw2ap_dsusr.map"

 fort_cpp.lib(fort_code.obj) : error LNK2019: unresolved external symbol _CPPFUNCTION@12 referenced in function _TESTCPP
 C:\temp\INIT_620289\11hw2ap_dsusr.dll : fatal error LNK1120: 1 unresolved externals

 ERROR during link(1)... Aborted

conec/conet are simply Fortran calls to the external library functions:

      SUBROUTINE CONEC
C
      INCLUDE 'COMON4.INS'
C
      CALL TESTCPP         ( 55791,     0,     0,     0)
C
      RETURN
      END
      SUBROUTINE CONET
C
      INCLUDE 'COMON4.INS'
C
      IF (.NOT. IFLAG) GO TO 9000
C
C  NETWORK MONITORING MODELS
C
C
 9000 CONTINUE
C
      RETURN
      END

I have seen a few different examples of calling c++ functions from Fortran, but they all look slightly different. One thing I noticed was differening uses of the _ before or after the c++ function name. How do I know which to use: _CPPFUNCTION, CPPFUNCTION, or CPPFUNCTION_. Do I need to export the function in c++ using __declspec( dllexport )? Do I need to create an ALIAS:'_CPPFUNCTION' in the Fortran code?

I am using the following compilers: ifort: IVF IA-32 v12.1.0.233 cl: v16.00.30319.01 x86

Is there something I am missing to link the c++ code properly to the Fortran functions?

1

There are 1 best solutions below

5
On

The problem with is that there are a lot of options. None, one or two underscores before and or after, string length after a variable or at the end of a list, call by value or call by reference. Capitalize, lowercase or original naming. With just these options, the probability of getting it right is already lower than 1 in 100 (1/3*1/3*1/2*1/2*1/3).

You can reduce it a bit by introspecting the .lib file using the dumpbin utility and manually checking intel fortran default settings and the settings in the project files.

The most elegant way, like some suggested, is to use the combination of bind(C) and iso_c_binding module. The bind(C) statement avoids having to know the name mangling. The iso_c_bindings provides c strings and integer(c_int) instead of integer*4 to ensure compatibility of your types with C. You have to know that fortran calls by reference by default and you can use , value to call by value. This should raise your succes rate all the way back to 1.

Here is a simple example of how to call the add1 function defined in c++ below:

test.f90

  program example

    use iso_c_binding
    implicit none

    interface
       integer(c_int) function add1(x) bind(C,name="add1")
         !DEC$ ATTRIBUTES DLLEXPORT :: add1
         use iso_c_binding
         integer(c_int), value :: x
       end function add1
    end interface

    call callingadd1()
  contains
    subroutine callingadd1()
      write(*,*) '1+1=', add1(1)
    end subroutine callingadd1
  end program example

add1.cpp

extern "C" {
  int add1(int x);
}

int add1(int x) {
  return(x+1);
}

edit an example with only a subroutine. For inclusion in your shared object/dll/dylib.

subroutineonly.f90

subroutine callingadd1() bind(C, name="callingadd1")
  !DEC$ ATTRIBUTES DLLEXPORT :: callingadd1
  use iso_c_binding
  implicit none
  interface
     integer(c_int) function add1(x) bind(C,name="add1")
       !DEC$ ATTRIBUTES DLLEXPORT :: add1
       use iso_c_binding
       integer(c_int), value :: x
     end function add1
  end interface
  write(*,*) '1+1=', add1(1)
end subroutine callingadd1