Note: This has not been tested in mono or a 32-bit system.
You'll need: 1) Visual Studio 2) a good text editor 3) gfortran
the code in this post is available via GitHub here
“I don't know what the language of the year 2000 will look like, but I know it will be called Fortran.” —Tony Hoare, winner of the 1980 Turing Award, in 1982.
Fact: Scientific computing is dominated by Fortran.
Some may argue SciPy is taking hold, but fact is that scientists/engineers still use Windows, and Python on Windows isn't quite the same experience. This is a shift I would like to see in some capacity, but Fortran and it's neolithic demeanor is here to stay. If C++ is a hammer, Fortran is a dull double-edged sword sans hilt-- it can will bludgeon you to death while cutting off your thumbs (and it won't tell you it's cutting anything off).
Some may ask, if Fortran is so prevalent, why is it almost always brought up only in context of 100-level college courses and (almost) always associated with great disgust? The code is old and I believe people have not bothered to put in the effort to port over the code to a fast, strongly-typed compiled language (none of which are Python characteristics!) like C++. Lots of legacy is written in Fortran 77, which features goto and if statements everywhere. No objects. An example is the ODEPACK written Fortran 77, probably on punch-cards originally.
Fun-fact: i was able to get this code to write an 8 GB file in under 15 seconds, so perhaps the 77 designation is not as dated as you think :)
Despite this, we can use a modern framework (.NET) to call legacy Fortran code via a compiled dll. This way, we can have the versatility of the modern world, and the speed and reliability of legacy.
1. Compile the Fortran dll
The generalized version of this is
gfortran -c myfile1.f90
gfortran -c myfile2.f90
gfortran -shared -o -mydll.dll myfile1.o myfile2.o
2. Create a .NET project in Visual Studio. If you are on a 64-bit computer, make sure you set the build configuration to x64 as the dll created cannot be called from a 32 bit version of your .NET program
3. Examine your Fortran file
In this example, I show a module mod_constants that define .NET doubles (they are noted as REAL in Fortran). This is very important because .NET doubles are 64 bit, but in Fortran, they are 32 bit by default. The selected_real_kind() modifies the precision and can create what I believe is a pseudo-64 (or 128) bit double. However, when you assess sizeof(DP), you get 32 bits. I am not a computer scientist so I could not attempt explain more without fudging facts.
What is important however, is that DP is set to 8 so that the size of the double annotated with "DP" is of 64 bit size. This matches with .NET so we can call it from C# or F#. Let me repeat, this is EXTREMELY important.
4. Open the .dll file in a text editor and find the name-mangled function. In this case, the two functions that are used are named
5. Now, we will use the DllImport attribute in the System.Runtime.InteropServices namespace to give a function reference to this these embedded within the dll.
There are a few important things here.
- CallingConvention.Cdecl. This has the function caller clean the stack, allowing methods with variable number of arguments to be called.
- The extern keyword, since this function is external
- The function name is exactly as appears in the .dll when you opened it in a text editor
- The arguments are passed in by reference. Fortran does what is equivalent to "passing by reference" in C#.
6. Make sure the dll is in the same directory as the .exe.
This is done by default. If you have set up a working directory for your project, make sure the dll is referenced there. The program does need to access the dll after all..
7. You are ready to use your function!
Now how do you do the same in F#? Going back to step 5...
5. Declare the function in a module and give the DllImport attribute.
Please remember the 4 important things in the C# step 5, they are the same here. Instead of the ref keyword, we use the & operator in F#
6. Make sure the dll is in the same director as the .exe
7. Ready to use the function!
One caveat here is a functional vs object-oriented (yes Fortran 2003+ is object oriented!). If you are going to pass in a value by reference, it needs to be mutable. The functional paradigm is no mutable data. We have no choice here though, so we have to wrap the dll method in an F# function to access it.
I hope this helped you and provided some insight on calling Fortran from .NET. It's a fascinating concept that this can work, and it does very well. Going further, I will post about more complex types and language interop, however this should be sufficient to get you started with real legacy Fortran code.