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

Calling Fortran DLL from C++ DLL using Visual Studio 2015

Dear All,

I have a DLL written in C++ and a program written in Fortran. What I need to do is to transform the Fortran program into a DLL and make the C++ DLL able to call it.

I read several topics on this forum but I am still having trouble: LoadLibrary fails. I don't know if this is a problem due to errors in compiling the Fortran DLL or due to errors in the C++ side.

So far I have:

- Fortran side:

!!!!!PROGRAM MAIN
MODULE MAIN

USE, INTRINSIC :: ISO_C_Binding

USE MODULE_Subs  ! This has all the subroutines needed in the program

IMPLICIT  NONE

CONTAINS

SUBROUTINE FORTRAN_DLL ( ind_TimeStep, avrSWAP ) BIND (C, NAME='FORTRAN_DLL')
!DEC$ ATTRIBUTES DLLEXPORT :: FORTRAN_DLL

IMPLICIT  NONE

INTEGER(C_INT),         INTENT(IN)    :: ind_TimeStep
REAL(C_FLOAT),          INTENT(INOUT) :: avrSWAP   (*)


!!!!! Instructions


RETURN

CONTAINS

!!!!! Some other subroutines used only here

!!!!!END PROGRAM MAIN
END SUBROUTINE FORTRAN_DLL

END MODULE MAIN

 

- C++ side:

#define _CRT_SECURE_NO_DEPRECATE 1
#define _CRT_SECURE_NO_WARNINGS  1

#include <windows.h> //CED - for external DLL management
#include <fstream>

HINSTANCE h_FORTRAN_dll;

typedef void (*__FORTRAN_DLL)(int, float *);

__FORTRAN_DLL  FORTRAN_DLL;

// Variables for FORTRAN DLL
int ind_TimeStep = 1;

//avoid mangled names
extern "C"
{
void __declspec(dllexport) __cdecl C_DLL(float *avrSwap,
                             int *aviFail,
                             char *accInfile,
                             char *avcOutname,
                             char *avcMsg);
}

//Main DLL routine
void __declspec(dllexport) __cdecl C_DLL (float *avrSwap,
                             int *aviFail,
                             char *accInfile,
                             char *avcOutname,
                             char *avcMsg)
{

			if (!(h_FORTRAN_dll = LoadLibrary(".\..\Folder\FORTRAN_DLL.dll")))
			{
				printf("\n*** FORTRAN_DLL.dll not found. Aborting.\n"); exit(-1); return;
			}

			FORTRAN_DLL = (__FORTRAN_DLL)GetProcAddress(h_FORTRAN_dll, "FORTRAN_DLL");


			FORTRAN_DLL(ind_TimeStep, avrSwap );

			ind_TimeStep = ind_TimeStep + 1;


}

This C++ DLL does not have to do anything but pass avrSWAP(coming from the main program) to the Fortran DLL.

C++ DLL is compiled in release version in Win32 Configuration.

Fortran DLL is compiled in release versione in x86 Configuration.

 

Any help would be appreciated.

Thank you in advance

 

Best regards,

Matteo Strada

Matteo Strada Politecnico di Milano - Milan
0 Kudos
22 Replies
Highlighted
Black Belt
13 Views

What does GetLastError return

What does GetLastError return after the failed LoadLibrary? Is this being run on the same system you built on? (If not, you may need to install the Fortran redistributables.)

You don't need to use LoadLibrary to call Fortran code from C++, though you can if you want to. There are samples provided of C calling Fortran and use of LoadLibrary.

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
13 Views

Dear Steve, 

Dear Steve, 

I am sorry but I am not able to use that function. I tried to use it but I can't make it print any message or code number. Could you please tell me where to put it and how?

Thank you

Matteo Strada

Matteo Strada Politecnico di Milano - Milan
0 Kudos
Highlighted
Valued Contributor II
13 Views

LoadLibrary("./../Folder

LoadLibrary("./../Folder/FORTRAN_DLL.dll")

Remember that C turns those backslashes into escape sequences!

 

0 Kudos
Highlighted
Black Belt
13 Views

Ah, yes. You need to use \\

Ah, yes. You need to use \\ where you want \ in a string in C/C++. (RO turned them into forward slashes but backslashes is correct.)

GetLastError just returns an integer error number that you can then print (printf or similar).

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Valued Contributor II
13 Views

Forward slashes are also

Forward slashes are also correct in this context. It's only in expressions that will be read by the command line parser that you need backslashes instead of forward slashes.

EDIT:

program test
   use IFWIN
   use ISO_C_BINDING
   implicit none
   enum, bind(C)
      enumerator :: &
         PROCESS_DPI_UNAWARE = 0, &
         PROCESS_SYSTEM_DPI_AWARE = 1, &
         PROCESS_PER_MONITOR_DPI_AWARE = 2
   end enum
   integer, parameter :: PROCESS_DPI_AWARENESS = KIND(PROCESS_DPI_UNAWARE)
   abstract interface
      function FCN_SetProcessDpiAwareness(value) &
         bind(C)
         import
         implicit none
         integer(HANDLE) FCN_SetProcessDpiAwareness
         integer(PROCESS_DPI_AWARENESS), value :: value
      end function FCN_SetProcessDpiAwareness
   end interface
   procedure(FCN_SetProcessDpiAwareness), pointer :: SetProcessDpiAwareness
   type(C_FUNPTR) FCN
   integer(HANDLE) hLibrary
   character(80) :: paths(3) = [character(80) :: &
      "C:\Windows\System32\Shcore.dll"C, &
      "C:/Windows/System32/Shcore.dll"C, &
      "C:\\Windows\\System32\\Shcore.dll"C]
   integer i
   integer err
   integer messlen
   integer(HANDLE) result8
   type(C_PTR) buff
   character(:), pointer :: mess
   character(20) fmt

   do i = 1, size(paths)
      hLibrary = LoadLibrary(paths(i))
      if(hLibrary == 0) then
         err = GetLastError()
         messlen = FormatMessage( &
            dwFlags = iany([ &
               FORMAT_MESSAGE_ALLOCATE_BUFFER, &
               FORMAT_MESSAGE_FROM_SYSTEM, &
               FORMAT_MESSAGE_IGNORE_INSERTS]), &
            lpSource = NULL, &
            dwMessageId = err, &
            dwLanguageId = LANG_SYSTEM_DEFAULT, &
            lpBuffer = LOC(buff), &
            nSize = 0, &
            Arguments = NULL)
         BLOCK
            character(messlen), pointer :: temp
            call C_F_POINTER(buff, temp)
            mess => temp
         END BLOCK
         write(*,'(2a,$)') 'LoadLibrary failed with message ',mess
         result8 = Localfree(transfer(buff,0_HANDLE))
      else
         FCN = transfer(GetProcAddress(hLibrary,"SetProcessDpiAwareness"C),FCN)
         write(fmt,'(*(g0))') '(g0,Z0.',BIT_SIZE(0_HANDLE)/4,')'
         write(*,fmt) 'FCN = ',FCN
      end if
   end do
end program test

Output:

LoadLibrary failed with message The specified module could not be found.
FCN = 74608870
FCN = 74608870

 

0 Kudos
Highlighted
Beginner
13 Views

Dear All,

Dear All,

Thanks for your replies.

The GetLastError function gives me error 126. 

By the way, I don't think that the error is in the address of the DLL since I put in the same folder ( .\..\Folder\ ) the C++ DLL and make it call itself in order to prove if the C++ call works. In fact LoadLibrary works this way. (But then the program crashes, obviously). 

So I think that the problem is definitely in the Fortran side.

Have you noticed some errors in my code?

Thank you

Best regards,

Matteo Strada

 

EDIT:

I see that error 126 is the same that you got when calling your dll with only one backslash but when I use LoadLibrary with the C++ DLL itself (as said above) it works. 

I'm going to hearing from you before proceeding.

Matteo Strada Politecnico di Milano - Milan
0 Kudos
Highlighted
Employee
13 Views

I do not know whether this

I do not know whether this relates the issues you are experiencing, but in your original post you noted your Fortran DLL is using "x86 Configuration". That's typically "Win32". Can you see whether adding and using a "Win32" configuration along with following the earlier advice from others helps at all.

0 Kudos
Highlighted
Beginner
13 Views

Dear Kevin,

Dear Kevin,

In the Configuration Manager window I have:

Active solution configuration: Release     ||     Active solution platform: x86

and then

FORTRAN_DLL    ||    Release    ||    Win32    ||     Build: Flag     ||      Deploy: No Flag

I am afraid that I am giving wrong options in the project properties but I don't know how to check this...

 

 

Matteo Strada Politecnico di Milano - Milan
0 Kudos
Highlighted
Employee
13 Views

For the Active Solution

For the Active Solution Platform, left-click on x86. If you see Win32 in the selection list, then left-click it. That sets the active configuration to Win32. Then try to build the solution.

If you do not see Win32 in that selection list, then click New and using the pull-down in the new pop-up window, under New Platform select Win32, and set Copy settings from to X86. Click Ok at those pop-ups to dismiss them and that will set the Active Platform to Win32. Then try building the solution.

0 Kudos
Highlighted
Beginner
13 Views

Just tried as you suggested

Just tried as you suggested but It doesn't work.

Matteo Strada Politecnico di Milano - Milan
0 Kudos
Highlighted
Black Belt
13 Views

Matteo,

Matteo,

I don't think you understood the issue about the DLL location.

In C and C++, backslashes in quoted strings are introducers for escape sequences. For example, "\n" is a carriage return.If you want an actual backslash, you need to double it like "\\". Try using:

if (!(h_FORTRAN_dll = LoadLibrary(".\\..\\Folder\\FORTRAN_DLL.dll")))

and see if it works. RO says you can use (a single) forward slash (/) instead, though I have never tried that myself.

Error code 126 is "module not found" - here, "module" means file.

As for Win32 vs. x86, I don't think that matters.

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
13 Views

Dear Steve,

Dear Steve,

I made this change but it still doesn't work. 

Furthermore I tried to use another very simple Fortran DLL in order to check if the call for the .dll works. I put this .dll in the same folder of FORTRAN_DLL.dll. Both LoadLibrary and GetProcAddress work.

For this reason I suppose that the problem is in the FORTRAN_DLL.dll. Since this is a "manipulation" of a program, I think that I am making some mistakes converting the .exe in .dll. This Fortran program is very complex and the original Visual Studio project has also an Additional Dependency on a library.

To build the FORTRAN_DLL.dll I created a new project and copied all the Project Properties from the original program, included that Additional Dependency, but I don't know if this is the right way to do this job.

Hope that these details could help you in helping me back...

Best regards,

Matteo Strada

Matteo Strada Politecnico di Milano - Milan
0 Kudos
Highlighted
Black Belt
13 Views

There can be two reasons for

There can be two reasons for "module not found", One is that the DLL itself isn't found, the other is that a DLL used by your DLL is not found. I suggest downloading Dependency Walker and have it analyze your Fortran DLL to see what it says about any missing DLLs. (Ignore complaints about architecture mismatch or delayed load DLLs.)

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Valued Contributor III
13 Views

Quote:Matteo S. wrote:

Matteo S. wrote:

.. 

For this reason I suppose that the problem is in the FORTRAN_DLL.dll. ..

@Matteo Strada,

Look into tools such as Dependency Walker and check whether your FORTRAN_DLL.dll has dependencies on other DLLs and if those DLLs are also present on the DLL search order used by your OS.

0 Kudos
Highlighted
Beginner
13 Views

Dependencies Walker gives me

Dependencies Walker gives me error also opening DLLs that work, so I don't know how  much is reliable.

Anyway the error for FORTRAN_DLL.dll is in attachment.

 

As you will see, the MAP_Win32.dll is its Additional Dependency,

 

 

Matteo Strada Politecnico di Milano - Milan
0 Kudos
Highlighted
Black Belt
13 Views

That screenshot looks ok -

That screenshot looks ok - the problems it shows can be ignored.

Please show us the new source line for the LoadLibrary call. Have you tried a fully qualified path rather than a relative path? Are you sure you understand how Windows searches that relative path? It will be relative to the EXE location, not the DLL.

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
13 Views

Dear Steve, 

Dear Steve, 

In reality, the C++ code gets the address from a text file. But this is not the problem since I put other DLLs in the same folder and they all works. As stated earlier, to make things simpler I am working on a very simplified version of the FORTRAN_DLL.

The version I'm currently working with just reads the elements of avrSWAP and change their position in the array. And this works.

The problem is to make FORTRAN_DLL.dll work since I need the functionality of the whole program.

Could the problem be the location of the MAP_Win32.dll?

In order to run the original program, i.e. the EXE, I need to have it in the same position of the .exe. Now that it is no more an executable but a DLL, do I have to give to the FORTRAN_DLL.dll some particular options to find it?

 

Matteo Strada Politecnico di Milano - Milan
0 Kudos
Highlighted
Black Belt
13 Views

You need to understand how

You need to understand how Windows looks for DLLs. Other than giving a path in LoadLibrary, DLLs are searched for in the current directory, the directory with the EXE, locations on PATH, the Windows folder and the Windows\System32 folder. (I am ignoring DLLs that have side-by-side assemblies, which is not the case for you.)

Even though you give a path to FORTRAN_DLL in the LoadLibrary, any DLLs it references will use the default search paths.

Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
13 Views

Dear Steve,

Dear Steve,

It is exactly this what I was missing. Now putting MAP_Win32.dll in the same folder of the .exe, LoadLibrary works! Thank you very much.

Now I got some problems when running the subroutine...I'll try to investigate what is wrong even if I have some ideas. I guess that the integer ind_TimeStep that I am passing to FORTRAN_DLL.dll is giving some problems since it is not a pointer. My strategy is to convert it into a float and put it into the Swap, and then converting it again into an integer in the Fortran DLL. It is just a counter.

Then, I have another question.

I wish to change the minimum possible to the original Fortran program since its architecture is really complex. The Fortran program creates a video console where the program prints the status of the simulation. So does my main program. Can this create some problems? When running a co-simulation I am expecting to be created 2 different consoles updated one by the main program and the other one by the FORTRAN_DLL.dll.

Is this possible?

Thank you in advance

Best Regards,

Matteo Strada

Matteo Strada Politecnico di Milano - Milan
0 Kudos