Question

There is a code in Fortran by Robert L. Parker and Philip B. Stark:

FORTRAN

      subroutine bv(key, m, n, a, b, bl, bu, x, w, act, zz, istate,  loopA)
      implicit double precision (a-h, o-z)
!      x  is an unknown n-vector
!      a  is a given m by n matrix
!      b  is a given  m-vector 
!      bl is a given n-vector of lower bounds on the components of x.
!      bu is a given n-vector of upper bounds on the components of x.
!      key = 0
!   ---Output parameters:
!      x   the solution vector.
!      w(1)    the minimum 2-norm || a.x-b ||.
!  istate  vector indicating which components of  x  are active 
!  loopA   number of iterations taken in the main loop, Loop A.
!  ---Working  arrays:
!  w      dimension n.               act      dimension m*(mm+2). mm=min(m,n).
!  zz     dimension m.               istate   dimension n+1.

I am trying to call that function from a dll in c# like:

C#

class Program
{
    [DllImport("bv.dll", CallingConvention = CallingConvention.StdCall )]
    public static extern void bvls(
         int key, //key = 0, the subroutine solves the problem from scratch. If key > 0 the routine initializes using the user's guess about which components of  x  are `active'
         int m,
         int n,
        double[] a, //  m by n matrix
        double[] b, //  m-vector 
        double[] bl, //n-vector of lower bounds on the components of x.
        double[] bu, //n-vector of upper bounds on the components of x.
        ref double[] x, //unknown n-vector
        //Working  arrays:
        ref double[] w,  //dimension n
        double[] act, //dimension m*(mm+2). mm=min(m,n).
         double[] zz, //dimension m
        ref double[] istate, //dimension n+1.
        ref int loopA //   number of iterations taken in the main loop, Loop A.
        );
    
       
    static void Main(string[] args)
    {
        double[] a = new double[3 * 3] { //M*N
                  1.0, 10.0, 10.0,
                  2.0, 18.0, 16.0,
                  1.8, 69.0, 16.0
        };
        double[] b = new double[3]  {  //LDB*NRHS
             4.3, 6.8, 1.0,
        };
        double[] bl = new double[3];
        double[] bu = new double[3];
        double[] x = new double[3];
        double[] w = new double[3];
        double[] act = new double[3* 5];  //dimension m*(mm+2). mm=min(m,n).
        double[] zz = new double[3];
        double[] istate = new double[4];
        int loopA =0;
        Program.bv(0, 3, 3, a, b, bl, bu, ref x, ref w, act, zz, ref istate, ref loopA);
        for (int j = 0; j < 3; j++)
            Console.Write(" \t" + x[j]);
            
    }
}

However when executing the code I get

EntryPointNotFoundException: Entry point was not found. in 'bv' on file 'bv.dll'.
    myProject.Program.bv(Int32 key, Int32 m, Int32 n, Double[] a, Double[] b, Double[] bl, Double[] bu, Double[]& x, Double[]& w, Double[] act, Double[] zz, Double[]& istate, Int32& loopA)

Basically I have 2 questions, How to get this to work? and other question,is it correct the way I defined the function

 [DllImport("bv.dll", CallingConvention = CallingConvention.StdCall )]
            public static extern void bvls(...)

based on the routine information of fortran code?

When using dependency walker I get: enter image description here

I am suspecting dll is not correct and does not have the routine, Is there a way to check if dll was generated the right way?

UPDATE

After trying ILSpy I get following which seems incorrect, could you please suggest how to gen dll file correctly?, Does ILSpy tells that bvlsFortran is the function I should use? I tried it but couldn't get it to work enter image description here

enter image description here

Was it helpful?

Solution

Try this in FORTRAN:

MODULE CALCBV

    INTEGER, PARAMETER :: sp = SELECTED_REAL_KIND(p=6,r=37)     ! IEEE Single Precision (32-bit)
    INTEGER, PARAMETER :: dp = SELECTED_REAL_KIND(p=15,r=307)   ! IEEE Double Precision (64-bit)       

CONTAINS

    subroutine bv(key, m, n, a, b, bl, bu, x, w, act, zz, istate,  loopA)
    IMPLICIT NONE
    !DEC$ ATTRIBUTES DLLEXPORT :: bv
    !DEC$ ATTRIBUTES ALIAS:'BV' :: bv
    !DEC$ ATTRIBUTES VALUE :: key, m, n

    INTEGER, INTENT(IN)   :: key, m, n
    REAL(dp), INTENT(IN)  :: a(m,n), b(m), bl(n), bu(n)
    REAL(dp), INTENT(OUT) :: x(n), w(n)
    REAL(dp), INTENT(IN)  :: act(m,MIN(m,n)+2), zz(m)
    INTEGER, INTENT(OUT)  :: istate(n+1)
    INTEGER, INTENT(OUT)  :: loopA

    ! DO CALC HERE

    end subroutine

END MODULE

And then call it from C# with:

[DllImport("bv.dll", CallingConvention=CallingConvention.Cdecl, EntryPoint="BV")]
static extern void bvls(
    int key, //key = 0, the subroutine solves the problem from scratch. If key > 0 the routine initializes using the user's guess about which components of  x  are `active'
    int m,
    int n,
    double[] a, //  m by n matrix
    double[] b, //  m-vector 
    double[] bl, //n-vector of lower bounds on the components of x.
    double[] bu, //n-vector of upper bounds on the components of x.
    double[] x, //unknown n-vector
    //Working  arrays:
    double[] w,  //dimension n
    double[] act, //dimension m*(mm+2). mm=min(m,n).
    double[] zz, //dimension m
    int[] istate, //dimension n+1.
    ref int loopA //   number of iterations taken in the main loop, Loop A.
    );

// Test code
static void BVTEST()
{
    int key=0, n=2, m=3;
    double[] a= { 1.0, 2.0, 3.0, 4.0, 5.0 };
    double[] b= { 10.0, 20.0, 30.0 };
    double[] bl= { 0.0, 1.0 };
    double[] bu= { 1.0, 2.0 };
    double[] x=new double[n];
    double[] w=new double[n];
    double[] act=new double[m*Math.Min(m, n)+2];
    double[] zz=new double[m];
    int[] istate=new int[n+1];
    int loopA = 0;
    // Call Fortran .dll
    bvls(key, m, n, a, b, bl, bu, x, w, act, zz, istate, ref loopA);
}

ScreenShot

Remember arrays are already reference types (the default) so they don't need ref keyword. Output values need it like with loopA, but passed by value arguments need the VALUE attribute declaration in order to avoid passing them with ref, like with key, m, n. You might need to fix the size of act to something bigger that it is because I am getting some memory corruption in the parameters after this.

This posting should get you going in the right direction. Remember always use Cdecl with FORTRAN .dll and always use implicit none declarations. Compile as x86 and Win32 and do not use AnyCPU. Declare your exports with the ALIAS attirbute in order to show up.

DependecyWalker

OTHER TIPS

I would start by trying to generate a DLL for a small example, making it run, than going for that big FORTRAN code of yours.

In the past I had a some problem creating a FORTRAN DLL and calling it from my C# code.

First thing you should do is get yourself a cool compiler for that FORTRAN code, I used Open Watcom in that time because it has an easy to use IDE. And I generated my dll from it.

Here is the first FORTRAN code that I tryed:

*$pragma aux DON "DON" export parm(value*8, reference, reference)


  SUBROUTINE DON(DAA,DBB,DCC)
  REAL*8, DAA,DBB,DCC
  DBB=DAA+1
  DCC=DBB+1 
  RETURN
  END

The $pragma directive is for Open Watcom Compiler use only. So don't mind it if you're gonna be using a different compiler.

This is a simple subroutine for you to test. If you can make that work for you, you're gonna be half your way for making that huge one that you want. :-)

The C# code I used to call it was this one here:

[DllImport("Lks.dll",
        CharSet = CharSet.Auto,
        CallingConvention = CallingConvention.StdCall)]
    public static extern void DON(
        [MarshalAs(UnmanagedType.R8)]     double DAA,
        [MarshalAs(UnmanagedType.R8)] ref double DBB,
        [MarshalAs(UnmanagedType.R8)] ref double DCC
        );

    static unsafe void Main(string[] args)
    {
        //double TIME = 100.0;
        double DAA = 5.5;
        double DBB = 7;
        double DCC = 9;
        //START( ENERIN, VAL1);
        DON(DAA, DBB, DCC);

        Console.Write("val1 = " + DAA);
        Console.Write("val2 = " + DCC);
        Debug.WriteLine("VAR = " + DBB.ToString());
        Console.Write("Press any key to exit");
        Console.ReadKey(false);
    }

So for your case I think you can do almost the same but you will need to watch out for getting the right types on MarshalAs.

This worked for me, and I hope it points you to the right direction. You can get the Open Watcom from here (http://www.openwatcom.org/)

I'm a noob, and this is my first answer so don't mind me if I couldn't help.

-Roiw

Looking at your latest update, it seems that your Fortran compiler has emitted a .net assembly. You should add that as a reference and forget all about the p/invoke. Consume the .net assembly as you would any other.


This is the original answer, commenting on the p/invoke

There seems to be some confusion in the question over the name of the function. Is it bv or bvls? In any case the Fortran compiler may decorate it. Use Dependency Walker to find out its name. OK, I see you tried that. It seems that your compilation of the DLL has failed to export the function. You'll need to find out how your particular compiler marks functions for export.

The calling convention could be stdcall or cdecl. That depends on how the DLL was compiled, and the compiler used. IIRC, most Fortran compilers on Windows will use stdcall. Check in the compiler docs.

You used ref for a couple of the array parameters. That is not correct. Remove ref from the array parameters. Arrays are marshalled as pointer to first element. You never marshal arrays to native by ref since the native code cannot make a managed array.

The istate parameter is of type int[]. Other than that I think you've got the types correct.

I've not checked how you prepare the parameters. Worth double checking that in due course. You'll certainly need to make sure you respect Fortran's col major storage.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top