问题
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:
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
回答1:
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);
}
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.
回答2:
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
回答3:
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.
来源:https://stackoverflow.com/questions/22134259/fortran-dll-import