Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Brian_Murphy
New Contributor II
575 Views

fortran calling c, build problem, unresolved external

I have built a C++ DLL which contains a function I can successfully call from Excel visual basic.  But I am having trouble trying to call it from a fortran DLL.  The code for the fortran DLL is below, and the linker error message is:

error LNK2019: unresolved external symbol _CheckFileProperties@0 referenced in function CheckKey		CheckKey.obj		

I have added to the fortran project the LIB file for the C++ DLL.  Is there something simple I am doing wrong?

integer function CheckKey(featureID)
!DEC$ ATTRIBUTES DLLEXPORT, STDCALL, REFERENCE, ALIAS:"CheckKey" :: CheckKey
	USE, INTRINSIC :: ISO_C_BINDING
	interface
		function CheckFileProperties() bind(C, name="CheckFileProperties")
			!DEC$ ATTRIBUTES STDCALL :: CheckFileProperties
			import
			integer :: CheckFileProperties
		end function
    end interface
	integer featureID
	integer, external :: CheckLimeKey
	integer, external :: CheckSentinelKey
	integer, external :: CheckUnikey
	integer :: LL, ierr, Xlrotor_Key_Type=1
    character(len=256):: valData

    LL = CheckFileProperties()
    call ReadRegVar('Software\\Xlrotor',valData,'Xlrotor_Key_Type',LL,ierr)
	if( ierr.eq.0 ) Read(valData,*) Xlrotor_Key_Type
	select case (Xlrotor_Key_Type)
    case (1) !Lime
		CheckKey = CheckLimeKey(featureID)
    case (2) !sentinel
		CheckKey = CheckSentinelKey(featureID)
    case (3) !Unikey
		CheckKey = CheckUnikey(featureID)
    case Default
    	CheckKey = 1
    end select
    return
end

 

0 Kudos
18 Replies
Steve_Lionel
Black Belt Retired Employee
551 Views

We would need to see the symbols exported from the C++ DLL to help you with this. Please zip and attach to a reply the export .LIB from the C++ DLL and we'll look. I don't see a problem with the Fortran code as far as it goes, but clearly the C++ DLL is exporting a different name.

Brian_Murphy
New Contributor II
541 Views

Thanks, Steve.  Attached is a DUMPBIN /ALL of the LIB file, and a copy of the symbols below.  The function has a return value of short in C++.  The Declare statement that works in VBA is Declare Function CheckFileProperties Lib "XLROTO32.DLL" () As Integer

    17 public symbols

      732 __IMPORT_DESCRIPTOR_xlroto32
      960 __NULL_IMPORT_DESCRIPTOR
      A98 xlroto32_NULL_THUNK_DATA
      D1E ?CallGetVersion32@@YGNN@Z
      D1E __imp_?CallGetVersion32@@YGNN@Z
      C84 ?CallEval32@@YGFPAPAUtagSAFEARRAY@@000000000NNNNFFFFFPAPBD@Z
      C84 __imp_?CallEval32@@YGFPAPAUtagSAFEARRAY@@000000000NNNNFFFFFPAPBD@Z
      D96 ?CallResp32@@YGFPAPAUtagSAFEARRAY@@0000000000000NNNFFFFFNFPAPA_W@Z
      D96 __imp_?CallResp32@@YGFPAPAUtagSAFEARRAY@@0000000000000NNNFFFFFNFPAPA_W@Z
      BEC ?CallDefl32@@YGFPAPAUtagSAFEARRAY@@0000000N00NNNFFFFFNF@Z
      BEC __imp_?CallDefl32@@YGFPAPAUtagSAFEARRAY@@0000000N00NNNFFFFFNF@Z
      EB0 ?compute_mode_shape32@@YGFPAPAUtagSAFEARRAY@@00000000NNNNFFFFFNNN@Z
      EB0 __imp_?compute_mode_shape32@@YGFPAPAUtagSAFEARRAY@@00000000NNNNFFFFFNNN@Z
      E36 ?CheckFileProperties@@YGFXZ
      E36 __imp_?CheckFileProperties@@YGFXZ
      F52 _SetExcel12EntryPt@4
      F52 __imp__SetExcel12EntryPt@4

 

Steve_Lionel
Black Belt Retired Employee
525 Views

I don't know how Excel found the routine, but you need to preface the C++ routine with:

extern "C"

(and also __STDCALL). It has the C++ "name mangling" in the library)

Brian_Murphy
New Contributor II
515 Views

In c++, the function is declared as short WINAPI CheckFileProperties(void), and the function is listed in the EXPORTS section of a .DEF file.  I can call this function and others like it from VBA. 

I tried putting extern "C" at the front of this statement, and rebuilt the C DLL and it still works from VBA.  But I still cannot build the fortran DLL because of the same unresolved external error.

Where is __STDCALL supposed to go?

Steve_Lionel
Black Belt Retired Employee
505 Views

You don't need the __STDCALL with WINAPI.

Brian_Murphy
New Contributor II
485 Views

In visual studio I had added the C DLL's LIB file to the file list for the fortran DLL project, and building generated to the linking error.

I then did Project/Project Dependencies and put a check in the box to make the fortran DLL dependent on the C DLL.  I was then able build the fortran DLL with no link error.  But that only happened one time.  Now it won't build at all, with a new error shown below.  This error makes no sense because I don't see why it is looking for the LIB file in a Microsoft folder.  I looked in the vfproj file, and the LIB file does appear anywhere.

atal error LNK1181: cannot open input file 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\Win32\Debug\xlroto32.lib'

 What am I doing wrong?  I tried unloading and reloading the fortran project, but that didn't help.

Steve_Lionel
Black Belt Retired Employee
482 Views

No - or rather, it tells me that something was incorrect about the way you added the .LIB to the Fortran project. Making the DLL a dependent causes the linker to reference the output library of the dependent project.

It would be interesting to see the output with the Fortran project property Linker > General > Show Progress set to "Show some progress messages (/verbose:lib)" when the DLL is not a dependent.

Brian_Murphy
New Contributor II
467 Views

After more fiddling with how the LIB is added to the project, the fortran DLL now builds.  Unfortunately, I don't know what I did that fixed it.  What I have now is basically back to where I started.  For future reference, what is the correct way, in visual studio, to add a c++ DLL's LIB file to the fortran DLL project?

Elsewhere, I'm finding that in the fortran DLL code, a function which is exported so it can be called from VBA cannot be called from other routines in the fortran DLL code, because trying to do so triggers unresolved external linking errors.  Is there a not so obvious trick required to do this?  I wonder if calling convention is the problem.

Steve_Lionel
Black Belt Retired Employee
446 Views

There is no one "correct" way, but my preference is to use Project Dependencies. Unfortunately, if the dependent project is a C++ DLL, MSVC doesn't properly export the information needed so in that case you need to add the library manually.

VBA (32-bit) wants to use a STDCALL routine (at least by default - there may be a way to tell it to use the C calling mechanism.) Intel Fortran defaults to the C mechanism, but you can change that with the ATTRIBUTES STDCALL directive on a per-procedure basis. (I recommend against using compiler options for this.) STDCALL has a naming convention where the name of the routine is suffixed with @n, where n is the number of bytes pushed on the stack for the argument list. 

In any case where you get "unresolved" references during linking, get a list of the names in the library you are linking to ("dumpbin /symbols" for a static library, "dumpbin /exports" for a DLL library) and compare that to the name in the error message.

The 64-bit world has only one calling convention, C.

Brian_Murphy
New Contributor II
442 Views

Thank you, Steve, for the tips on LIB configuration.  I've followed them and made some progress, but I can't seem to vanquish the demons of unresolved externals.

xlrotorfe.DLL is a fortran DLL containing a routine named CheckKey I want to call from a C++ DLL.  When building the C++ DLL, I get unresolved external for CheckKey , and the error says the missing routine is _CheckKey.  I have added xlrotorfe.lib to file list.  If I understand you correctly, I should do dumpbin /exports xlrotorfe.dll to see what the exported name of CheckKey really is, and I don't see any underscores or @n.

dumpbin.exe /exports xlrotofe.DLL
    ordinal hint RVA      name

          1    0 00164190 CheckKey = CheckKey

 In Fortran the declaration for CheckKey is:

integer function CheckKey(featureID)
!DEC$ ATTRIBUTES DLLEXPORT, STDCALL, REFERENCE, ALIAS:"CheckKey" :: CheckKey
	integer featureID

In C++ I have: 

#include "ISO_Fortran_binding.h"
extern "C" int CheckKey(int featureID);

 Do you see anything I'm doing wrong?  I had to arm-twist visual studio to find the .h file, but I think I got at least that part right.

Steve_Lionel
Black Belt Retired Employee
434 Views

It would help if you would show the linker error message. But I can already see problems.

Do this instead:

 

integer function CheckKey(featureID) bind(C,name="CheckKey")
!DEC$ ATTRIBUTES DLLEXPORT :: CheckKey
	integer, value ::  featureID

 

Your C++ declaration does not include STDCALL, so that's mismatch one. Mismatch two is that C++ (on 32-bit) is going to be looking for _CheckKey, but your use of ALIAS doesn't include the underscore. Mismatch three is that the C++ declaration specifies pass by value, but the Fortran interface says pass by reference.

Last, I recommend doing the dumpbin on the .lib, not the .dll.

Brian_Murphy
New Contributor II
418 Views

Thanks bunches, Steve.

  1. I modified the fortran declaration to what you said.
  2. I rebuilt the fortran DLL.
  3. I copied the fortran DLL's new .LIB file to the source folder for the c++ DLL which is where I reference it in the c++ project.  I don't know if I should be using a Release or Debug LIB file.  I hope it doesn't matter.  At this stage, everything I'm doing is Debug.
  4. Building the c++ DLL still failed with unresolved external error message, but I had left a comma after DLLEXPORT in step 1.  After removing the comma, it built ok, and runs ok.  

This is a huge step for me.  I can't thank you enough.

This brings me to my next unresolved external problem, which is related to the one just solved. 

Calling CheckKey from the c++ DLL now works great, but I also need to call it in the fortran DLL.  Doing so, the fortran DLL fails to build with the following linker error.

error LNK2019: unresolved external symbol _CHECKKEY referenced in function MKL_EIGS_SPARSE_INPUT		pardiso.obj		

In the fortran routine where I call CheckKey, how do I declare CheckKey?  I presently have integer, external :: CheckKey.  I tried _CheckKey but the compiler rejected the leading underscore.  Is there another keyword needed in the declaration?

andrew_4619
Honored Contributor I
406 Views

you have "external" but you need an explicit interface for the checkey function and I would say that needs the _ in the alias.  Without the interface it will call the function in a totally standard Fortran way when clearly in this case that is not correct.

Tags (1)
Brian_Murphy
New Contributor II
394 Views

Thank you very much, Andrew.  That did the trick! 

I had tried an explicit interface, but not with an ALIAS parameter.  Adding that with an underscore got it to build.

I took out the "external" statement and added the following.  This is in the routine that calls CheckKey.

	interface
		function CheckKey(featureID)
		!DEC$ ATTRIBUTES ALIAS:"_CheckKey" :: CheckKey
			use, intrinsic :: iso_c_binding
			integer :: CheckKey
			integer(c_int) :: featureID
		end function
	end interface

I have a question regarding how to specify pass by-value or by-reference for the featureID argument, with respect to calling CheckKey from fortran or from C++. 

In C++ I have:

#include "ISO_Fortran_binding.h"
extern "C" int CheckKey(int featureID);

In C++, calling CheckKey works if the fortran definition of CheckKey has a value keyword as follows.

integer function CheckKey(featureID) bind(C,name="CheckKey")
!DEC$ ATTRIBUTES DLLEXPORT :: CheckKey
	USE, INTRINSIC :: ISO_C_BINDING
	integer(c_int), value :: featureID

But the value keyword messed up a fortran call of CheckKey.  So I added the value keyword in the same place in the interface block of CheckKey.  But that didn't seem to make any difference according to what the debugger was showing me for the contents of featureID.  What did work was adding !DEC$ ATTRIBUTES VALUE::featureID in the interface block.

My question is, did I do this correctly?

IanH
Black Belt
384 Views

Is CheckKey in a Fortran module?  If so, the module should be USEd in the calling scope and there should not be a need for a separate interface. 

If CheckKey is not in a Fortran module, then strongly consider putting it in one!

If CheckKey is still not in a module, then then Fortran interface block for CheckKey needs to match the Fortran subprogram for CheckKey - the interface block should look like a truncated-but-otherwise-[nearly-]identical variant of the necessary bits of the specification part of the function subprogram.  That is not the case currently - so for example that !DEC$ ATTRIBUTES ALIAS graffiti appears in the interface block, while the function subprogram has BIND(C).

(As for "nearly" above - is the Fortran calling across a DLL boundary (i.e. - is CheckKey defined in one DLL but being called from another DLL or EXE?  If so, the caller of CheckKey ideally is told that CheckKey is a DLLIMPORT.  If CheckKey is in a module that the caller uses, then this is automatic.  If you are writing a separate interface block then you should add !DEC$ ATTRIBUTES DLLIMPORT.)

Your function subprogram is appropriate for the C++ prototype you have provided.  Your interface block is not.

(Why are you including "ISO_Fortran_binding.h"?

I recommend IMPLICIT NONE inside interface bodies.)

Steve_Lionel
Black Belt Retired Employee
376 Views

The standard Fortran VALUE attribute (not !DEC$ ATTRIBUTES VALUE) can have two different meanings.

1) If the procedure is not interoperable (does not have BIND(C) in its declaration), Fortran passes a writeable copy of the actual argument by reference. Any changes made to the argument in the called routine are discarded on return.

2) If the procedure is interoperable (has BIND(C) in its declaration), Fortran passes the argument by value.

You should not need ATTRIBUTES VALUE if you have properly declared the routine to be called with BIND(C) as I suggested above. And yes, you do need an explicit interface visible everywhere the routine is called.

andrew_4619
Honored Contributor I
367 Views

Brian_Murphy
New Contributor II
348 Views

Thanks to all for the replies and suggestions.

My reason including ISO_Fortran_binding.h was because it appeared in the C Calls Fortran example code.  I don't have any string arguments, so I guess I don't need it, and I've removed it.

The CheckKey routine is presently not in a module.  I will look into doing that.  In the meanwhile, I have read the Intel documentation page on BIND(C), and have modified my routine definition and interface to the following.  I like this much better than what I had.

Routine definition:

integer function CheckKey(featureID) bind(C,name="CheckKey")
!DEC$ ATTRIBUTES DLLEXPORT :: CheckKey
	use, intrinsic :: iso_c_binding
	implicit none
	integer(c_int), value :: featureID

Interface definition, which must be put in all other fortran routines calling CheckKey:

	interface
		integer function CheckKey(featureID) bind(C,name="CheckKey")
			use, intrinsic :: iso_c_binding
			implicit none
			integer(c_int), value :: featureID
		end function
	end interface

There are numerous routines throughout the fortran code that call CheckKey.  I can see advantages of creating a module, e.g. CheckKey_mod, and putting CheckKey in it.  I will work on that.  It's a new trick for an old dog.

At the present time CheckKey is exported because it is also called from a C++ DLL.  It is not called from any other fortran DLL's.  It might be in the future, but maybe not.

Reply