3

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

Community
  • 1
  • 1
edgarmtze
  • 24,683
  • 80
  • 235
  • 386
  • Use Dependency Walker to find the name of the function. All your ref arrays must not be ref. istate is int[]. Also, don't post fake code. bv or bvls? – David Heffernan Mar 02 '14 at 22:21
  • And take care about Fortran being col major. – David Heffernan Mar 02 '14 at 22:28
  • it is bv in the code I added to project, and what about CallingConvention and all ref params, are they correct? – edgarmtze Mar 02 '14 at 22:34
  • I don't understand that question – David Heffernan Mar 02 '14 at 22:39
  • what I mean is the dllimport code part, I use CallingConvention.Cdecl or CallingConvention.StdCall ? I guess as cdecl cleans stack is better to use?, and in method definition I added ref to variables that routine returns, but I am not sure if all vars must be defined as ref.. – edgarmtze Mar 02 '14 at 22:43
  • No. None of the arrays are ref. In fact I think none of the params are ref. Remove ref. As for Stdcall or Cdecl, you don't get a choice. What does your compiler docs say? – David Heffernan Mar 02 '14 at 22:45
  • 1
    You have to add a `!DEC$ ATTRIBUTES ALIAS:'BV' :: bv` in the fortran subroutine. – John Alexiou Mar 03 '14 at 01:34
  • 1
    And link to the right function with `[DllImport("bv.dll", CallingConvention=CallingConvention.Cdecl, EntryPoint="BV")]` in C# – John Alexiou Mar 03 '14 at 01:40
  • 1
    It is rather strange that your `bv.dll` links to `mscoree.dll`. What compiler do you use? It seems that the Fortran code has already been compiled to CLR and becomes a managed library. If you can confirm that, maybe you don't need PInvoke at all. – Lex Li Mar 03 '14 at 02:33
  • Take a look at your Fortran DLL using ILSpy or another decompiler. That way, you can confirm if @LexLi is correct, and if so, find the class name to be able to call that function. – Kris Vandermotten Mar 03 '14 at 02:49
  • I tried SilverFrost FTN95 to gen dll... – edgarmtze Mar 03 '14 at 03:05
  • If you use SilverFrost FTN95, then the library is already a managed one, you should directly add `bv.dll` as a reference on C# side to consume it. http://www.silverfrost.com/11/ftn95/ftn95_fortran_95_for_windows.aspx They almost clearly state that by saying "Silverfrost FTN95 is the only compiler that can produce Microsoft .NET applications". – Lex Li Mar 03 '14 at 03:20
  • How should I call the function is it bvlsFortran ?, could you please post a sample code? – edgarmtze Mar 03 '14 at 03:25
  • If I try `bvlsFortran(key, m, n, a, b, bl, bu, x, w, act, zz, istate, ref loopA); Console.WriteLine(loopA);` with @ja72 I get Error 1 'bvlsFortran' is a 'type' but is used like a 'variable' – edgarmtze Mar 03 '14 at 03:32

3 Answers3

2

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

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
1

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Could you please suggest a method to gen dll from fortran code, I tried SilverFrost FTN95 however it seems it is not genereting the dll correctly.. I am trying compaq visual fortran v6.6 – edgarmtze Mar 02 '14 at 23:40
  • Sorry. I don't have either of those compilers to hand. Your question doesn't ask about compilation. – David Heffernan Mar 02 '14 at 23:43
  • Thank you I will try to make some tests, As the root of problem seems to gen dll – edgarmtze Mar 02 '14 at 23:47
1

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

Roiw
  • 147
  • 2
  • 16