I'm not familiar with .NET interface for WM, but your problem can be solved with LibraryLink. With LibraryLink it is possible to create a shared lib which can be loaded with LibraryFunctionLoad[]
or, perhaps, this lib can be accessed with .NET interface.
Fortran
has interoperability with C
. While it is possible to call Fortran
functions with <name>_
, the modern way is to use the intrinsic iso_c_binding
module.
You want to interface a function that returns an array. It's OK in Fortran
, but C
functions do not return arrays. A solution is to use a subroutine
in Fortran
and a void
function in C
.
Create funs.f90
, test.c
, test.f90
and interface.cpp
in your notebook directory (see files contents below or download all files forum.tar.gz)
(* set directory *)
SetDirectory[NotebookDirectory[]] ;
You can define your Fortran
procedures in a module.
(* fortran module *)
FilePrint["funs.f90"]
module funs
use, intrinsic :: iso_c_binding, only : ik => c_int, rk => c_double
implicit none
private
public :: ik
public :: rk
public :: vsum
contains
! void vsum(int, double*, double*, double*) ;
pure subroutine vsum(n, v1, v2, v3) bind(c, name = "vsum")
integer(ik), value, intent(in) :: n
real(rk), dimension(n), intent(in) :: v1
real(rk), dimension(n), intent(in) :: v2
real(rk), dimension(n), intent(out) :: v3
v3 = v1 + v2
end subroutine vsum
end module funs
Note, array dims are set by the input arg n
. Array length n
also has value
keyword which allows to pass it by value in C
.
(* compile fortran module *)
Run["gfortran -c -std=f2008 -O3 funs.f90"]
(* 0 *)
Here is a test Fortran
program that uses this module.
(* fortran test program *)
FilePrint["test.f90"]
program test
use funs
implicit none
integer(ik), parameter :: n = 16_ik
real(rk), dimension(n) :: v1, v2, v3
integer(ik) :: i
v1 = real([(i, i = 1_ik, n, 1_ik)], rk)
v2 = v1
call vsum(n, v1, v2, v3)
do i = 1_ik, n, 1_ik
write(*,*) v3(i)
end do
end program test
(* compile and run fortran test program *)
Run["gfortran -c -std=f2008 -O3 test.f90"]
Run["gfortran -o test -std=f2008 -O3 funs.o test.o"]
ImportString[RunProcess["./test", "StandardOutput"],"List"]
(*0*)
(*0*)
(*{2.,4.,6.,8.,10.,12.,14.,16.,18.,20.,22.,24.,26.,28.,30.,32.}*)
Here is a test C
program that uses this module.
(* c test program *)
FilePrint["test.c"]
#include <stdio.h>
void vsum(int, double*, double*, double*) ;
int main(){
int n = 16 ;
double x[n], y[n], z[n] ;
for(int i = 0 ; i < n ; i++){
x[i] = (double) i + 1 ;
y[i] = (double) i + 1;
z[i] = 0.0 ;
}
vsum(n, x, y, z) ;
for(int i = 0 ; i < n ; i++){
printf("%lf\n",z[i]) ;
}
return 0 ;
}
(* compile and run c test program *)
Run["gcc -c -std=c99 -O3 test.c"]
Run["gcc -o test -std=c99 -O3 funs.o test.o"]
ImportString[RunProcess["./test", "StandardOutput"],"List"]
(*0*)
(*0*)
(*{2.,4.,6.,8.,10.,12.,14.,16.,18.,20.,22.,24.,26.,28.,30.,32.}*)
For LibraryLink
you need to define a wrapper.
(* library link *)
FilePrint["interface.cpp"]
#include "WolframLibrary.h"
#include "WolframCompileLibrary.h"
DLLEXPORT mint WolframLibrary_getVersion(){ return WolframLibraryVersion ; }
DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData){ return 0 ; }
extern "C" { void vsum(int, double*, double*, double*) ;}
EXTERN_C DLLEXPORT int vsum_ll(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res){
mint n = MArgument_getInteger(Args[0]) ;
MTensor v1 = MArgument_getMTensor(Args[1]) ;
MTensor v2 = MArgument_getMTensor(Args[2]) ;
MTensor v3 = 0 ;
libData->MTensor_clone(v1, &v3);
vsum(
n,
MTensor_getRealDataMacro(v1),
MTensor_getRealDataMacro(v2),
MTensor_getRealDataMacro(v3)
) ;
MArgument_setMTensor(Res,v3) ;
return LIBRARY_NO_ERROR ;
}
(* create library *)
Needs["CCompilerDriver`"] ;
CreateLibrary[
{"interface.cpp", "funs.o"},
"vsum_ll",
"TargetDirectory" -> NotebookDirectory[],
"CleanIntermediate" -> True,
"Debug" -> False,
"CompileOptions" -> "-O3"
] ;
This creates vsum_ll
lib with vsum_ll
function that calls vsum
procedure.
You can try to load it with .NET or with LibraryFunctionLoad[]
.
(* load library *)
Quiet[LibraryFunctionUnload[lib]] ;
ClearAll[lib] ;
lib = LibraryFunctionLoad[
FileNameJoin[{NotebookDirectory[],"vsum_ll"}],
"vsum_ll",
{Integer,{Real,1},{Real,1}},
{Real,1}
] ;
This function can now be used in WM.
(* test *)
n = 16 ;
v1 = v2 = N[Range[n]] ;
lib[n,v1,v2]
(*{2.,4.,6.,8.,10.,12.,14.,16.,18.,20.,22.,24.,26.,28.,30.,32.}*)
As an extra step, you can wrap it into Compile[]
, this allows to use listability and also provides some safety.
(* wrapper *)
ClearAll[vsum] ;
vsum = Compile[
{{n,_Integer},{v1,_Real,1},{v2,_Real,1}},
lib[n,v1,v2],
CompilationTarget -> "C",
CompilationOptions -> {"InlineExternalDefinitions" -> True, "InlineCompiledFunctions" -> True},
RuntimeOptions -> "Speed",
Parallelization -> True,
RuntimeAttributes -> {Listable}
] ;
Test wrapped version and instability.
(* test *)
(* sequential *)
vsum[n,v1,v2]
(* listable *)
vsum[n,{v1,v1},{v2,v2}]
(*{2.,4.,6.,8.,10.,12.,14.,16.,18.,20.,22.,24.,26.,28.,30.,32.}*)
(*{{2.,4.,6.,8.,10.,12.,14.,16.,18.,20.,22.,24.,26.,28.,30.,32.},{2.,4.,6.,8.,10.,12.,14.,16.,18.,20.,22.,24.,26.,28.,30.,32.}}*)
Unload lib when done.
(* unload *)
LibraryFunctionUnload[lib]
You can find more examples of Fortran in WM here or here.