I have a subroutine that was written in fortran that I need to call from VB.NET where all of my other functions are written. I did not write the fortran, and hardly know fortran. I am getting the below exception on my dll function call and don't know how to fix it. I wonder if it is due to incongruent variable lengths?
I have the source for my fortran and compiled it using the g95 compiler. I have tried compiling it with a flag on which is supposed to force all of the reals to 32 bits (-r4). It weirds me out that you don't seem to be required to initialize variables before use in fortran. I thought it was supposed to be a ridged language.
Anyway, below is the exception I am getting:
System.AccessViolationException was unhandled Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt. Source=PTPWrapper
StackTrace: at PTPWrapper.Module1.pointtopoint(Single& IELEVAT, Single& IDIST, Single& FREQ, Single& HTAMSL, Single& DLOSS, Single& CLUTTER) at PTPWrapper.Module1.Main() in C:\Documents and Settings\SGoldman\my documents\visual studio 2010\Projects\PTPWrapper\PTPWrapper\Module1.vb:line 18 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException:
here is my VB function declaration and function call:
Declare Sub pointtopoint Lib "diff5z11.dll" (ByRef IELEVAT As Single, ByRef IDIST As Single, ByRef FREQ As Single, ByRef HTAMSL As Single, ByRef DLOSS As Single, ByRef CLUTTER As Single)
pointtopoint(elevation(0), distance, freq, height, dlo, clut)
all of the va开发者_高级运维riables are defined as 32-bit singles here in VB.
and here are the first few lines of the fortran code:
subroutine pointtopoint(IELEVAT, IDIST, FREQ, HTAMSL, DLOSS, CLUTTER)
real ielevat(*)
dimension oblim(2)
dd = 0.1
EK = 1.333 ! Earth curvature (4/3 earth)
HR = 9.1 ! Rcvr Ant ht (m), for 30 feet
HRAMSL = IELEVAT(IDIST) + HR
DIST = float(idist)*dd
FRESMIN = HR + 1.0
DLOSS = 0.0
TDLOSS = 0.0
RDLOSS = 0.0
ADJ = 0.0
any ideas how i can get the call to work and get my data back? Thanks!
It looks like you are almost there.
First, I want to point out you can force required declarations in Fortran and it is encouraged. To do so add IMPLICIT NONE after the Subroutine declaration:
subroutine pointtopoint(IELEVAT, IDIST, FREQ, HTAMSL, DLOSS, CLUTTER)
IMPLICIT NONE
[variable declarations]
...
end subroutine pointtopoint
This may be a good idea in your case because it looks like there is some variable type confusion. If IMPLICIT NONE is not used the Fortran compiler makes assumptions as to what the variable type is by the first character of the variable name. Any variable that begins with an I, J, K, L, M, or N is assumed to be INTEGER and all else is assumed REAL. So the first problem I see is IDIST - you are sending a Single from VB and this probable causing the memory access violation you are seeing. Whatever number is being sent as the Single is being interpreted as an INTEGER and is most likely out of the bounds of the IELEVAT array.
Also, one other thing I notice (and this may not be an error - I can't tell because the whole subroutine doesn't appear to be posted) is that the subroutine receives the variable HTAMSL and later on uses HRAMSL. This looks like a possible typo where the programmer actually wanted to used HTAMSL. HTAMSL and HRAMSL are two completely different variables. This is another side effect of not using IMPLICIT NONE - typos go unnoticed and you'll end up with unexpected results.
Re " It weirds me out that you don't seem to be required to initialize variables before use in fortran." -- you are required in Fortran to initialize variables before using them. Initializing and declaring are different. Old FORTRAN frequently used "implicit typing", in which the variables were implicitly typed by the first letter of their name. Modern practice is to explicitly type each variable, but to allow legacy code to compile the old method is permitted. As already answered by @brady, you can cause the compiler to require explicit declarations of each variable by including "implicit none". Most compilers also have a compiler option for the same effect.
As @brady wrote, from implicit typing IDIST is an integer, and being used as an array index. IELEVAT should be large enough for the value of IDIST that you are passing. If this isn't enough to make it work, you can get more control over the calling using the ISO C Binding of Fortran 2003, which is widely available in Fortran 95 compilers. This will tell Fortran to use the C calling conventions for the subroutine, which are more likely to match the expectations of VB. And you can control whether the argument passing is by value or by reference.
Something like:
subroutine pointtopoint (IELEVAT, IDIST, FREQ, HTAMSL, DLOSS, CLUTTER) bind (C, name="pointtopoint")
implicit none ! optional
real (c_float), dimension (*) :: IELEVAT
integer (c_int) :: IDIST
real (c_float) :: FREQ, HTAMSL, DLOSS, CLUTTER
The gfortran manual has some documentation: http://gcc.gnu.org/onlinedocs/gfortran/Mixed_002dLanguage-Programming.html For MS Windows, you might need an extension to select between calling conventions: http://gcc.gnu.org/onlinedocs/gfortran/GNU-Fortran-Compiler-Directives.html#GNU-Fortran-Compiler-Directives
精彩评论