Author Archives: ryan.lucking@gmail.com

List Builder Pattern – Unit Testing Object Hierarchies in C#

When coding processing-intensive tasks, my classes are typically immutable and data-only (similar to F# records without structural equality). I then create classes that take the inputs through the constructor and transform the data with a single public method call.

In order to unit test this code, I need to be able to quickly generate dummy test data. At this point I usually turn to the builder pattern.

Sample Application

In the following examples we have a simple data hierarchy of a CAD assembly. An assembly has a name, ID, and a list of parts. Each part has a name, ID, mass, and quantity.

public class Assembly
{
	public Guid Id { get; private set; }
	public string Name { get; private set; }
	public IList<Part> Parts { get; private set; }

	public Assembly(Guid id, string name, IList<Part> parts)
	{
		Id = id;
		Name = name;
		Parts = parts;
	}
}

public class Part
{
	public Guid Id { get; private set; }
	public string Name { get; private set; }
	public int Quantity { get; private set; }
	public double Mass { get; private set; }

	public Part(Guid id, string name, int quantity, double mass)
	{
		Id = id;
		Name = name;
		Quantity = quantity;
		Mass = mass;
	}
}

After reading assembly data from a spreadsheet or some other user input, we need to validate it before inserting it into the database. We will unit test the following rules.

  • Assembly names must be unique.
  • Part names must be unique within and across assemblies.

Builder Pattern

The standard unit test builder pattern:

  1. Establishes default values for all fields
  2. Provides public setters to override the defaults – typically using a fluent style interface.
  3. Contains a Build() method (or an implicit conversion) which calls the constructor to build the type under test.

An assembly builder would look like this.

 
public class AssemblyBuilder
{
	private Guid _id = Guid.Parse("b5f6f602-f123-4730-b1b2-9c246f4807e8");
	private string _name = "Assembly 1";
	private IList<Part> _parts = new List<Part>();

	public AssemblyBuilder Id(Guid id)
	{
		_id = id;
		return this;
	}

	public AssemblyBuilder Name(string name)
	{
		_name = name;
		return this;
	}

	public AssemblyBuilder Parts(IList&lt;Part&gt; parts)
	{
		_parts = parts;
		return this;
	}

	public Assembly Build()
	{
		return new Assembly(_id, _name, _parts);
	}
}

A unit test for assembly name uniqueness would look like:

public void Validate_DuplicateAssemblyNames_Error()
{
	var assembly1 = new AssemblyBuilder()
					.Name("Wing").Build();

	var assembly2 = new AssemblyBuilder()
					.Name("Wing").Build();

	var validator = new AssemblyValidator(new[] { assembly1, assembly2 });
	var error = validator.Validate();

	Assert.Contains("duplicate names", error);
}

There are a couple issues with this approach already.

1. When making several assembly instances the default behavior is to create the same exact assembly. What’d we like is for each assembly be different but valid. For Guid fields all we need is a call to Guid.NewGuid(). For other fields we will need to make some helper methods.

2. It’s tedious to call multiple builders to make a list. We want something like NBuilder.

var assemblies = Builder<Assembly>CreateListOfSize(2)
					.TheFirst(2).With(a => a.Name = "Wing").Build();

However we can’t use NBuilder here because the class fields do not have public setters and there is no default constructor. NBuilder also nulls the Parts field by default.

3. We’ve completely avoided the parts collection in this implementation. This goes back to point #1 where we’d like to generate a list of unique and valid objects.

List Builder Pattern

Instead of the builder generating a single instance, we will design it to generate a list of objects. We will also use helper methods to generate random, valid test data – à la FsCheck.

Here is the PartsBuilder. The size of the list is passed in via the constructor. Given the size, a list of random data values is given for each field. The *Generator classes are simple helper classes to generate each field array. The Build() method simply loops over the field arrays and generates a list of parts.

public class PartsBuilder
{
	private int _size;
	private IList<Guid> _ids;
	private IList<string> _names;
	private IList<int> _quantities;
	private IList<double> _masses;
.
	public PartsBuilder(int size)
	{
		_size = size;
		_ids = new GuidsGenerator(size: size).Generate();
		_names = new OrderedStringGenerator(size, basename: "Part").Generate();
		_quantities = new RandomIntegerGenerator(size, min: 1, max: 999).Generate();
		_masses = new RandomDoubleGenerator(size, min: Double.Epsilon, max: Double.MaxValue).Generate();
	}

	public PartsBuilder Ids(params Guid[] ids)
	{
		_ids = ids;
		return this;
	}
	
	public PartsBuilder Names(params string[] names)
	{
		_names = names;
		return this;
	}
	
		
	public PartsBuilder Quantities(params int[] quantities)
	{
		_quantities = quantities;
		return this;
	}
	
	public PartsBuilder Masses(params double[] masses)
	{
		_masses = masses;
		return this;
	}
	
	public IList<Part> Build()
	{
		var parts = new List<Part>();
		for (int i = 0; i < _size; i++)
		{
			var part = new Part(_ids[i], _names[i], _quantities[i], _masses[i]);
			parts.Add(part);
		}

		return parts;
	}
}

The assembly builder follows the same pattern. Notice that we are now generating valid parts data. By default each assembly will have 1-10 valid parts assigned.

public class AssembliesBuilder
{
	private int _size;
	private IList<Guid> _ids;
	private IList<string> _names;
	private IList<PartsBuilder> _parts;

	public AssembliesBuilder(int size)
	{
		_size = size;
		_ids = new GuidsGenerator(size).Generate();
		_names = new OrderedStringGenerator(size, basename: "Assembly").Generate();
		_parts = Enumerable.Range(0, size).Select(i => new PartsBuilder(Gen.Int(1, 10))).ToList();
	}

	public AssembliesBuilder Id(IList<Guid> ids)
	{
		_ids = ids;
		return this;
	}

	public AssembliesBuilder Names(params string[] names)
	{
		_names = names;
		return this;
	}

	public AssembliesBuilder Parts(params PartsBuilder[] parts)
	{
		_parts = parts;
		return this;
	}

	public IList<Assembly> Build()
	{
		var assemblies = new List<Assembly>();
		for (int i = 0; i < _size; i++)
		{
			var assembly = new Assembly(_ids[i], _names[i], _parts[i].Build());
			assemblies.Add(assembly);
		}
		return assemblies;
	}
}

Our test code to detect duplicate assembly names is now similar to NBuilder. The main difference is that when overriding fields – each field in the list must be defined.

public void Validate_DuplicateAssemblyNames_Error()
{
	var assemblies = new AssembliesBuilder(size: 4)
		.Names("Wing", "Engine", "Wing", "Flaps")
		.Build();

	var validator = new AssemblyValidator(assemblies);
	var error = validator.Validate();

	Assert.Contains("duplicate names", error); // Wing
}

In our final example, we want to test duplicate part names across assemblies. Here we generate two assemblies and override the PartsBuilder for each one.

public void Validate_DuplicatePartNamesAcrossAssemblies_Error()
{
	var assemblies = new AssembliesBuilder(size: 2)
		.Parts(new PartsBuilder(size: 4)
				   .Names("Beam", "Bracket", "Widget", "Panel"),
			   new PartsBuilder(size: 2)
				   .Names("Widget", "Rivet")
		).Build();

	var validator = new AssemblyValidator(assemblies);
	var error = validator.Validate();

	Assert.Contains("duplicate names", error); // Widget
}

Fortran Error Handling Techniques

Error handling is critical in any software application. In computational applications, good error handling code will to detect the error as early as possible (fail fast), exit immediately, and report useful diagnostics to the user.

Boolean Return Value

One technique is to have every method return a boolean value. If the method returns true, proceed as usual, otherwise exit immediately.

function ProcessRegions() result(success)

    logical :: success

    success = .false.

    do i = 1, N

        region = regions(i)

        if (.not. ComputeDensity(region, density)) then
            return
        end if

        ! Do more stuff    

    end do

    ! No errors. Return true.
    success = .true.

end function

The downside of this technique is that you have no context as to what or why it failed. Why did the density computation fail? Was the mass zero? Did ComputeDensity method attempt to open a file that does not exist? There are 10,000 regions. Which region failed?

Without this information, you will need to debug the code interactively to figure out what went wrong.

Error Code Return Value

This is the classic programming technique of returning an integer error code. If the code is zero, by convention the method was successful. Otherwise exit immediately. The developer is responsible for making an enumeration containing error codes for all methods of failure.

integer, parameter :: ERR_None = 0, &
                      ERR_Default = 1, &
                      ERR_DivideByZero = 2, &
                      ERR_FileNotFound = 3, &
                      ERR_SomethingTerrible = 4

subroutine ProcessRegions(err) 

    integer, intent(inout) :: err ! inout since the err code should already be initialized to ERR_None.

    do i = 1, N

        region = regions(i)

        call ComputeDensity(region, density, err)
        if (err /= ERR_None) then ! Exit immediately if there's an error.
            return
        end if

        ! Do more stuff    

    end do

end subroutine

The error code now adds some context. We can now distinguish between a divide by zero error and a file not found error.

The downsides:
1) We still don’t know which region failed. This is very important if we are processing many regions.
2) We need to add an error code for every failure point in the application. For example a divide by zero error could occur in the ComputeVelocity() method. Therefore we need separate divide by zero error codes. This quickly becomes cumbersome – which leads to error handling code not being written.
3) Each method call is accompanied by three extra lines of error handling boilerplate. This code will be duplicated throughout the application.

Derived Type + Macros

It’s more convenient to use a piece of text instead of a raw integer. Let’s make a derived type.

type :: ErrorType
    integer :: Code
    character(len=256) :: Message
end type

OK so now we have a richer data type to store an error message. The downside is that it is more verbose to define an error and handle an error. Next let’s add some macros to handle this.

To use macros:
1) Enable the Fortran preprocessor compiler option (/fpp). In Visual Studio use Fortran | Preprocessor | Preprocess Source File.
2) Include the file with the macros using an include statement i.e. #include 'Error.fpp'. Beware of any leading spaces in this statement.

! Error.fpp

! Macros for error handling. 
! Enables user to store errors and exit the subroutine in single statement. 
! Fortran preprocessor must be enabled: -fpp.
      
! Raise Error
! Store the error code and info (only if the current code is zero).
! Return from the subroutine.
#define RAISE_ERROR(msg, err) if (err%Code == ERR_None) then; err = ErrorType(Code=ERR_Default, Message=msg); end if; return;

! Pass Error
! Returns if there's an error.
#define HANDLE_ERROR(err) if (err%Code /= ERR_None) then; return; end if;

These macros eliminate some of the boiler plate error handling code. The RAISE_ERROR macros throws and stores the error and the HANDLE_ERROR checks the error code and exits the subroutine as necessary.

Now the full example looks like:

#include 'Error.fpp'

module Processing

    use Errors ! Error type definition and constants.
    implicit none

contains

    subroutine ProcessRegions(regions, err) 

        integer, intent(inout) :: err

        do i = 1, N

            region = regions(i)

            call ComputeDensity(region, density, err)
            HANDLE_ERROR(err)

            ! Do more stuff    

        end do

    end subroutine

    subroutine ComputeDensity(region, density, err)

        type(RegionType), intent(in) :: region
        real(8), intent(out) :: density
        type(ErrorType), intent(inout) :: err

        if (region%Volume <= 0.0) 
            RAISE_ERROR("Volume must be greater than zero. Region = " // trim(region%Name), err)
        end if

        density = region%Mass / region%Volume

    end subroutine

end module

The error message is now very descriptive. Raising an error and handling an error only takes a single line of code.

Global vs. Local Error State

The above examples store the error state using a local variable. It is tempting to use a global variable for the error state so you don’t have to pass it around as a parameter. However, using a local variable is the best practice.
1) Any subroutine that can fail will have an error variable in the interface. This helps ensure that the caller will remember to handle the error.
2) Using local variables ensures that the application can be safely executed in multi-threaded context. This is an important consideration if you ever want to run your application in parallel.

Calling Fortran from C# – Monitoring Progress Using Callbacks

For long running Fortran routines, you will typically need to monitor the progress from the calling application. If the Fortran routine is an a separate process (EXE) you will need to use some form of inter-process communication (IPC).

Files are a simple form of IPC. Files are great for transferring bulk inputs and outputs to another process. However things get hairy when using files to delivers progress updates since you have to deal with multiple processes simultaneously accessing the same file – one reading, another writing.

My preference for calling long running Fortran routines via C# is to compile the Fortran routine as a DLL and pass a callback (function pointer) into the Fortran routine. The Fortran routine can invoke the callback function to provide progress updates.

The example demonstrates this technique. See Calling FORTRAN function or subroutine in DLL from C# code for more info on the general mechanics of call Fortran DLLs from C#.

The DoWork function takes in a procedure with the explicit interface ProgressUpdateAction. You could also use the external statement instead of an explicit interface (see xTechNotes).

module MyLib
    
    implicit none
    
    interface
        subroutine ProgressUpdateAction(percentProgress)
            integer, intent(in) :: percentProgress
        end subroutine
    end interface    
    
contains
    
    subroutine DoWork(progressUpdate)

        !DIR$ ATTRIBUTES DLLEXPORT :: DoWork
        !DIR$ ATTRIBUTES ALIAS: 'DoWork' :: DoWork
        !DIR$ ATTRIBUTES REFERENCE :: progressCallBack

        procedure(ProgressUpdateAction), intent(in), pointer :: progressUpdate

        integer, parameter :: STEP_COUNT = 9
        integer, parameter :: OP_COUNT = 100000000
        integer :: i, j, percentProgress
        real(8) :: n

        do i = 1, STEP_COUNT
            
            ! Update the status.
            percentProgress = (i - 1) * 100.0 / STEP_COUNT
            call progressUpdate(percentProgress)
            
            ! Do something expensive.
            do j = 1, OP_COUNT
                n = sqrt(real(j,8))
                if (j == OP_COUNT) print *, n
            end do

        end do

    end subroutine
        
end module

To call the Fortran routine from C# you define an equivalent delegate (also named ProgressUpdateAction below). To invoke the Fortran routine, pass an instance of the delegate. In the example below the delegate instance is a static method that echos the progress percentage to the screen. Note that the progress argument is passed by reference.

public static class FortranLib
{
    private const string _dllName = "FortranLib.dll";

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ProgressUpdateAction(ref int progress); // Important the int is passed by ref (else we could use built-in Action<T> instead of delegate).

    [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    public static extern void DoWork([MarshalAs(UnmanagedType.FunctionPtr)] ref ProgressUpdateAction progressUpdate);
}

class Program
{
    static void Main(string[] args)
    {
        try
        {
            FortranLib.ProgressUpdateAction updateProgress = delegate(ref int p)
            {
                Console.WriteLine("Progress: " + p + "%");
            };

            FortranLib.DoWork(ref updateProgress);

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

How to Create Fortran Static Libraries With Intel Fortran and Visual Studio

Intel Fortran with Visual Studio integration makes it easy to create and consume static libraries. Static libraries are shared components that compile to LIB files which in turn link into DLLs and EXEs.

Static libraries have several benefits:

  • Unit Testing Static libraries enable unit testing. The static library makes it easy to create a unit test project that tests the static library in isolation. The unit test project is typically a console application. This pattern is also common in C++.
  • Internal Code Reuse Code that is shared among multiple DLLs within a solution can be collocated in a static library.
  • External Code Reuse Static libraries can also be used to publish code for use by 3rd parties. This use case is more complicated since certain compiler options can cause binary incompatibilities such as the choice run-time library. This use case is not covered in this post.
  • Organization Splitting up a large project into several static libraries makes the application easier to understand and maintain.

The the example below shows how to compile a static libraries into a Fortran console application.

Solution Setup

The solution has two projects: the “Calculator” static library and the main console application that calls the Calculator.

fsl-solution_explorer

Static Library

The static library has a single module with some advanced numerical computation routines. The Subtract subroutine has the bind(c) attribute. This will affect how the compiler exports the name of the function in the LIB.

module Calculator

    implicit none

contains

    function Add(x, y) result(z)

        real(8) :: z
        real(8), intent(in) :: x, y

        z = x + y

    end function

    function Subtract(x, y) result(z) bind(c)

        real(8) :: z
        real(8), intent(in) :: x, y

        z = x - y

    end function

end module

After you compile the LIB file, you can use DUMPBIN to inspect how the functions are being exposed. This is important when using LIB files from 3rd parties. DUMPBIN is a command line utility for inspecting binaries. The easiest way to run DUMPBIN is to open the Visual Studio command prompt from the Start Menu (or a shortcut) and cd to the target directory.

fsl-vs_cmd

(If the project does not compile a LIB file, try putting a module-less subroutine that calls a library routine. I’ve occasionally seen this behavior when starting a new project. Once the LIB compiles, you can get rid of the dummy subroutine.)

For this application use dumpbin /symbols to show the exported symbols. The Add and Subtract symbols are highlighted in the figure below. The Add method is exported as _CALCULATOR_mp_ADD where mp = ‘module procedure’. This is the typical Fortran name mangling convention. The Subtract method is exported using the C convention, _subtract since bind(c) was used. This means that the Subtract method can be linked into a C or C++ application.

fsl-dumpbin

Link the Static Library

Linking the static library to the calling application is very easy using the Visual Studio integration.

First, add the calling application project. In this sample we use a console application shown below. The Main subroutine references the Calculator module from the static library.

program Main

    use Calculator

    implicit none

    real(8) :: x, y

    x = 1.0
    y = 2.0

    print *, 'Add', Add(x, y)
    print *, 'Sub', Subtract(x, y)

end program

To link the static library, right-click on the calling project and select Project Dependencies. Select “Calculator” as a dependency of the “Program” application and click OK. Build the solution, and your are done.

fsl-project_dependencies

When interfacing with 3rd party LIB files (w/out source code), you will need to point the compiler to the path of the LIB files using the following linker options. If the static library uses modules, you will also need to point the compiler to the module (*.mod) binaries. Things get more complicated when dealing with Debug and Release modes and 32- and 64-bit configurations.

  • Fortran | General | Additional Include Directories (for modules)
  • Linker | General | Additional Library Directories
  • Linker | Input | Additional Dependencies

But if you have access to the source code to compile the LIB internally, the Visual Studio integration takes care of all this.

Sample Code

The link below contains sample code using Visual Studio 2010 and Intel Fortran.

Sample-StaticLib.zip

How to Call Fortran DLLs from C#

C# has direct support for calling Fortran via DLLs using P/Invoke. In C#, simply define a static method with the same method signature as the Fortran method. No code generation tools are needed.

Using Fortran with a high-level language like C# allows you to leverage the strengths of both languages. C# can be used to create a GUI as well as preprocess data from files, database, or other applications. This leaves Fortran to focus solely on the number crunching.

Here are some general rules for calling Fortran from C#.

  • The Fortran DLL should reside in the same directory as the C# assembly (DLL or EXE). Automatically copy the Fortran DLL using post-build events.
  • Array indices are switched between Fortran and C#. Fortran and C# arrays are stored column and row major order respectively.
  • Fortran derived types map to C# structs. Be sure to explicitly define the alignment. The default alignment in Intel Fortran is 8 bytes.
  • Scalar values in Fortran are passed by reference. The equivalent C# scalar should be passed by reference (ref) as well. Failing to do so will usually cause memory access violations.
  • C# strings should be converted to character arrays padded with spaces.

Solution Setup

The Visual Studio solution contains and Fortran dynamic link library project and a C# console application. In order for the C# console application to call the Fortran DLL, it must be in the same directory as the C# executable. Other configurations are possible, but are not recommended due to the potential for chaos (DLL hell).

Use post-build events to automatically copy the Fortran DLL to the C# build directory as shown below. Here we use some of the Visual Studio macros. Remember to include the quotes if you solution is located in a path with spaces.
copy "$(OutDir)\$(TargetName).dll" "$(SolutionDir)\CSharpApp\bin\Debug"
copy "$(OutDir)\$(TargetName).dll" "$(SolutionDir)\CSharpApp\bin\Release"

post-build_fortran

Passing Arrays

The following example shows how to pass arrays. The Fortran subroutine translates an array of coordinates by the vector delta.

coords is a 3 x n array where n is the number of coordinates. n is passed into the subroutine.

Several compiler directives are used to export the subroutine to the public interface of the DLL. The ATTRIBUTES DLLEXPORT directive exports the subroutine. The ATTRIBUTES ALIAS overrides any compiler specific name mangling. Compiler directives are prepended by !DIR$, !DEC$, or !MS$.

subroutine TranslateViaArrays(delta, n, coords)

    !DIR$ ATTRIBUTES DLLEXPORT :: TranslateViaArrays
    !DIR$ ATTRIBUTES ALIAS: 'TranslateViaArrays' :: TranslateViaArrays

    real(8), intent(in) :: delta(3)
    integer, intent(in) :: n
    real(8), intent(inout) :: coords(3,n)

    integer :: i

    do i = 1, n
        coords(:,i) = coords(:,i) + delta(:)
    end do

end subroutine

The calling code in C# wraps the DLL call in a static class call FortranLib. The name of the DLL is defined in a private field.

Within the class a static extern method is defined with the [DllImmport] attribute. The DllImport attribute defines how the DLL is called – most importantly the DLL name, calling convention, and the character set.

The signature of the method matches the Fortran version with some extra attributes to describe how each argument is marshalled from C# to Fortran and back.

  • The scalar argument n is passed by reference (ref) as this is the Fortran convention.
  • The array arguments do not use the ref keyword since arrays are reference types in C# (as opposed to scalar primitives like n which are value types).
  • The array arguments are annotated with the [In] and [Out] attributes. These should match the intent of the Fortran version.
  • The default calling convention for Intel Fortran is C (cdecl).
public static class FortranLib
{
    private const string _dllName = "FortranLib.dll";

    public const int PathLength = 256;

    [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    public static extern void TranslateViaArrays([In] double[] delta, ref int n, [In, Out] double[,] coords);
}

The snippet below shows the C# calling code. Notice that the array indices in the coords are reversed between C# and Fortran. The rest is standard C#.

static void RunTranslateViaArrays()
{
    var delta = new[] { 1.0, 2.0, 3.0 };
    int n = 10;
    var coords = new double[n, 3]; // Note that the indices are reversed from Fortran.
    for (int i = 0; i < n; i++)
    {
        coords[i, 0] = i;
        coords[i, 1] = i + 1;
        coords[i, 2] = -i;
    }

    FortranLib.TranslateViaArrays(delta, ref n, coords);

    for (int i = 0; i < n; i++)
        Console.WriteLine("Point {0}: ({1}, {2}, {3})", i + 1, coords[i, 0], coords[i, 1], coords[i, 2]);
}

Passing Types

The following Fortran code performs the same operation as before, except now the point coordinates are encapsulated in a derived type PointType. This derived type will map to an equivalent C# struct.

The bind(c) attribute dictates that the memory layout should be the same as in C – sequential with padding as necessary. The default padding in Intel Fortran is 8 bytes (/align:rec8byte). In PointType a padding integer is placed after the Id field so that the double precision coordinate fields start on the subsequent 8-byte boundary. You can confirm this at run-time using the inquire statement (shown below).

type, bind(c) :: PointType
    integer :: Id
    real(8) :: Coords(3)
end type

subroutine TranslateViaTypes(delta, n, points)

    !DIR$ ATTRIBUTES DLLEXPORT :: TranslateViaTypes
    !DIR$ ATTRIBUTES ALIAS: 'TranslateViaTypes' :: TranslateViaTypes

    real(8), intent(in) :: delta(3)
    integer, intent(in) :: n
    type(PointType), intent(inout), target :: points(n)

    integer :: i, words
    type(PointType), pointer :: point

    inquire(iolength=words) point
    print *, words ! Confirm the size of PointType. Id(1) + Pad(1) + Coords(3*2) = 8 words. 1 word = 4 bytes.

    do i = 1, n
        point => points(i) ! Use a local (pointer) variable to avoid writing points(i) multiple times.
        point%Coords(:) = point%Coords(:) + delta(:)
    end do    

end subroutine

The equivalent C# struct is shown in the code below:

  • The StructLayout attribute dictates that the memory layout is sequential (like bind(c)). The Pack option specifies a padding of 8 bytes.
  • The coords array has a special MarshalAs attribute which tells C# to reserve space for 3 doubles. C# does not have native support for fixed size arrays (essentially all C# arrays are like Fortran allocatable arrays). C# does have a notion of a fixed array which requires an unsafe code block which I have not tried.
  • The downside of the current approach is that if the struct is created with the default constructor (i.e. points = new Points[10]), it is in an unsafe state since the Coords field is null (high potential for NullReferenceExceptions).
  • The sample code has examples of other implementations.
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct Point
{
    public int Id;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public double[] Coords;

    public Point(int id, double x, double y, double z)
    {
        Id = id;
        Coords = new double[3] { x, y, z };
    }
}

The P/Invoke signature of the C# call is practically the same as the previous example. Switching from raw arrays to types helps make the code self documenting.

[DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void TranslateViaTypes([In] double[] delta, ref int n, [In, Out] Node[] nodes);

static void RunTranslateViaTypes()
{
    var delta = new[] { 1.0, 2.0, 3.0 };
    int n = 10;
    var points = new Point[n];
    for (int i = 0; i < n; i++)
        points[i] = new Point(id: i + 1, x: i, y: i + 1, z: -i);

    FortranLib.TranslateViaTypes(delta, ref n, points);

    for (int i = 0; i < n; i++)
    {
        var point = points[i];
        Console.WriteLine("Point {0}: ({1}, {2}, {3})", point.Id, point.Coords[0], point.Coords[1], point.Coords[2]);
    }
}

Passing Strings

Passing strings to Fortran requires that you convert the C# string to a fixed length character array. The following Fortran code shows an example use case where the path to an input file is passed to the DLL subroutine.

subroutine TranslateViaInputFile(inputPath)

    !DIR$ ATTRIBUTES DLLEXPORT :: TranslateViaInputFile
    !DIR$ ATTRIBUTES ALIAS: 'TranslateViaInputFile' :: TranslateViaInputFile

    character(len=LEN_PATH), intent(in) :: inputPath

    print *, "Input File: " // trim(inputPath)

    ! Do something amazing....

end subroutine

The P/Invoke call is similar to the ones before.

[DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void TranslateViaInputFile([In] char[] inputPath);

When converting C# strings to character arrays you must ensure that: 1) the array has the correct length and 2) leftover characters are written as whitespace. The following extension method handles this conversion.

public static char[] ToCharacterArrayFortran(this string source, int length)
{
    var chars = new char[length];
    int sourceLength = source.Length;
    for (int i = 0; i < length; i++)
    {
        if (i < sourceLength)
            chars[i] = source[i];
        else
            chars[i] = ' '; // Important that these are blank for Fortran compatibility.
    }
    return chars;
}

The calling code uses the extension method to create the character array to pass into Fortran.

static void RunTranslateViaInputFile()
{
    const string inputPath = @"C:\temp\input.txt";

    // Convert string to Fortran array of characters.
    char[] inputPathChars = inputPath.ToCharacterArrayFortran(FortranLib.PathLength);

    FortranLib.TranslateViaInputFile(inputPathChars);
}

Debugging

To debug Fortran from the C# application you must activate the unmanaged code debugging in the C# project. Project | Properties | Debug | Enable unmanaged code debugging. Once this option is enabled, you can step directly into the Fortran DLL from the C# application.

enable_unmanaged_debugging_fortran

Sample Code

The link below contains sample code using Visual Studio 2010 and Intel Fortran.
CSharpFortranInterop.zip