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
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.
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
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?
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.
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.
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.
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.
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.
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.
Thanks bunches, Steve.
- I modified the fortran declaration to what you said.
- I rebuilt the fortran DLL.
- 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.
- 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?
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.
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?
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.)
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.
Brian I suggest you read: https://software.intel.com/content/www/us/en/develop/documentation/fortran-compiler-developer-guide-... that explains many things.
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.
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.