Question

I am trying to call out to a legacy dll compiled from FORTRAN code. I am new to Interop, but I've read some articles on it and it seems like my case should be fairly straightforward.

The method I really want to call has a complex method signature, but I can't even call this simple GetVersion method without getting a protected memory violation.

Here's my DllImport code:

[DllImport("GeoConvert.dll", 
            EntryPoint="_get_version@4", 
            CallingConvention=CallingConvention.StdCall)]
public static extern void GetGeoConvertVersion([MarshalAs(UnmanagedType.LPStr, SizeConst=8)]
                                                    ref string version);

Here's the FORTRAN code:

SUBROUTINE GetVer( VRSION )
C
!MS$DEFINE  MSDLL 
!MS$IF DEFINED (MSDLL)
        ENTRY Get_Version (VRSION)  
      !MS$ATTRIBUTES DLLEXPORT,STDCALL :: Get_Version
      !MS$ATTRIBUTES REFERENCE :: VRSION
!MS$ENDIF
!MS$UNDEFINE  MSDLL 
C
  CHARACTER*8  VRSION
C
  VRSION = '1.0a_FhC'                                        
C
  RETURN
  END

Here's my unit test that fails:

[Test]
public void TestGetVersion()
{
    string version = "";
    LatLonUtils.GetGeoConvertVersion(ref version);
    StringAssert.IsNonEmpty(version);
}

Here's the error message I get:

System.AccessViolationException
Message: Attempted to read or write protected memory. 
         This is often an indication that other memory is corrupt.

Other things I've tried:

  • Using the default marshalling
  • Passing a char[] instead of a string (get method signature errors instead)
Was it helpful?

Solution 2

OK, I got it to work, the problem was passing by ref. I'm not sure why, but this works:

[DllImport("GeoConvert.dll", 
                EntryPoint="_get_version@4", 
                CallingConvention=CallingConvention.StdCall)]
    public static extern void GetGeoConvertVersion([MarshalAs(UnmanagedType.LPArray)]
                                                    byte[] version);

With this test:

[Test]
    public void TestGetVersion()
    {
        //string version = "";
        byte[] version = new byte[8];
        LatLonUtils.GetGeoConvertVersion(version);
        char[] versionChars = System.Text.Encoding.ASCII.GetChars(version);

        string versionString = new string(versionChars);
    }

OTHER TIPS

...snip... OK, I got it to work, the problem was passing by ref. I'm not sure why, but this works: ...snip...

You need to pass by reference because that is the semantic being used by the FORTRAN code. The client code is passing in a buffer that the FORTRAN code is going to write to in lieu of using a return value.

...snip... !MS$ATTRIBUTES REFERENCE :: VRSION ...snip...

This attribute in your FORTRAN code specifies that this parameter is passed by reference. That means the FORTRAN code is going to write to this address. If the DllImport doesn't declare it as a ref value also, you will get an access violation.

Have you tried using a StringBuilder?

Create your String as a StringBuilder and pass that into the dll function.

Im unsure as to what Marashlling statement to use, perhapse the default might work.

Have a look at: Marshal C++ “string” class in C# P/Invoke

Heres a good article the might help as well: Interop Marshalling

I cannot try this solution since I do not have a FORTRAN compiler, but I think this would work for you:

    [DllImport("GeoConvert.dll", 
            EntryPoint="_get_version@4", 
            CallingConvention=CallingConvention.StdCall,
            CharSet=CharSet.Ansi)]
    public static extern void GetGeoConvertVersion(StringBuilder version);

Thank you all guys, I've been trying to pass a string from c# to a subroutine from fortran dll and this method was the only working one among lots of others

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