- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I've had quite a lot of luck interoperating between Fortran and C# using Robert Giesecke's "Unmanaged Exports" library. However, one of the things I haven't been able to achieve yet is sending a string from C# to Fortran. The worst of it is that I get no indication of what's wrong. My Fortran application just crashes. From all the research I've done, it would seem to me the following should work:
C# "sender"
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct MyStruct { [MarshalAs(UnmanagedType.LPStr, SizeConst = 200)] public string Title; public int SourceNode; public int EndNode; }
Fortran "receiver"
type, bind(c) :: MyFirstStruct character(200, kind=C_CHAR) :: Title integer :: SourceNode integer :: EndNode end type
When I run this I get the following:
forrtl: severe (172): Program Exception - exception code = 0x4352 (17234)
Any help or ideas would be much appreciated!
Brad.
P.S. I can include the entire source for both sides, if it helps - just let me know.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hello
I agree about the fact that if it works from C to C# it will works from Fortran
I think you should go back to basis.
First, replace your function with a subroutine because Fortran creates a shadow argument for function returning structures a strings.
subroutine GetStruct(struct) bind(C, name="GetStruct") import MyFortranStruct type(MyFortranStruct) :: struct !DEC$ ATTRIBUTES REFERENCE :: struct end subroutine
and on C# side the equivalent of :
void GetStruct(MyFortranStruct *result) { string="This is the title, padded to 200 characters just to make sure 345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; strncpy(&(result->Title),string,min(len(string),200)); result->SourceNode = 123; result->EndNode = 321; return; }
2nd, on both sides (before the call to GetStruct in Fortran and in GetStruct function in C#), print the size and address of the structure and addresses of all members. They must match.
3nd, find a way in C# function GetStruct to fill an array of chars (because that's what is a Fortran string)
Link Copied
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
@Bradley P.,
Try this on the C# side:
public const Int32 LENSTRING = 200; [StructLayout(LayoutKind.Sequential)] public struct MyStruct { [MarshalAs(UnmanagedType.ByValArray, SizeConst = LENSTRING)] public char[] Title; public int SourceNode; public int EndNode; }
Note using BIND(C), Fortran character variable can be made compatible with a companion processor that handles the type as C char; the above struct with char[] component tries to emulate that with the Marshaling directives; note .NET 'string' class is a different type.
Not a big deal, but on the Fortran side, you may want to mark your integer types as [fortran] integer(kind=c_int) :: ... [/fortran] a
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
FortranFan wrote:
Try this on the C# side:
Thank a lot for your suggestion. Unfortunately, that was one of the many things I've already tried, and it fails with the same meaningless result:
forrtl: severe (172): Program Exception - exception code = 0x4352 (17234)
It's enough to make me think I'm doing something else wrong, but I can't figure out what.
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Bradley P. wrote:
It's enough to make me think I'm doing something else wrong, but I can't figure out what. ..
You may need to show your actual code and share the details, the readers then may be able to help you.
module m use, intrinsic :: iso_c_binding, only : c_char, c_int, c_null_char, c_loc, c_f_pointer implicit none private integer(c_int), parameter :: LENSTRING = 200 type, bind(C), public :: MyStruct character(kind=c_char,len=1) :: Title(LENSTRING) integer(c_int) :: SourceNode integer(c_int) :: EndNode end type MyStruct public :: F_sub contains subroutine F_sub( struct ) bind(C, name="F_sub") implicit none !.. Argument list type(MyStruct), intent(inout) :: struct call load_title( struct%Title ) struct%SourceNode = 42 struct%EndNode = 99 return contains subroutine load_title( title ) bind(C) character(kind=c_char,len=1), target :: title(LENSTRING) !.. Local variables character(kind=c_char,len=LENSTRING), pointer :: F_title call c_f_pointer( c_loc(title), F_title ) F_title = "Hello World!" F_title => null() return end subroutine load_title end subroutine F_sub end module m
using System; using System.IO; using System.Runtime.InteropServices; using System.Text; namespace TestStruct { static class For { internal const Int32 LENSTRING = 200; [StructLayout(LayoutKind.Sequential)] internal struct MyStruct { [MarshalAs(UnmanagedType.ByValArray, SizeConst = LENSTRING)] internal char[] Title; internal int SourceNode; internal int EndNode; } [DllImport("For.dll", CallingConvention = CallingConvention.Cdecl)] internal static extern void F_sub(ref MyStruct s); } static class PassStruct { static void Main() { For.MyStruct foo; char[] tmpchar = new char[For.LENSTRING]; foo = new For.MyStruct(); foo.Title = new char[For.LENSTRING]; foo.SourceNode = 0; foo.EndNode = 0; try { Console.WriteLine("\n" + " --- Test Pass Struct with char[] to Fortran ---" + "\n"); // Call Fortran procedure For.F_sub(ref foo); Console.WriteLine(new string(foo.Title, 0, For.LENSTRING) + "\n"); Console.WriteLine("SourceNode = " + foo.SourceNode.ToString() + "\n"); Console.WriteLine("EndNode = " + foo.EndNode.ToString() + "\n"); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } }
Upon execution,
--- Test Pass Struct with char[] to Fortran --- Hello World! SourceNode = 42 EndNode = 99
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
FortranFan wrote:
You may need to show your actual code and share the details, the readers then may be able to help you.
Whereas the code you showed looks good, it's sending information in the wrong direction. I'm trying to get a structure INTO a Fortran executable FROM a C# DLL. Yes, it's probably the reverse of what most people attempt. :-)
Here is my source. First the C# DLL:
using RGiesecke.DllExport; using System.Runtime.InteropServices; namespace CSharpDll { public class Structures { [DllExport("GetInt", CallingConvention = CallingConvention.Cdecl)] public static int GetInt() { return 123; } public const int LENSTRING = 200; [StructLayout(LayoutKind.Sequential)] public struct MyFortranStruct { [MarshalAs(UnmanagedType.ByValArray, SizeConst = LENSTRING)] public char[] Title; public int SourceNode; public int EndNode; } [DllExport("GetStruct", CallingConvention = CallingConvention.Cdecl)] public static MyFortranStruct GetStruct() { var result = new MyFortranStruct(); result.Title = "This is the title, padded to 200 characters just to make sure 345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890".ToCharArray(); result.SourceNode = 123; result.EndNode = 321; return result; } } }
Next the Fortran module I called "Interfaces":
module InterfacesModule use iso_c_binding implicit none type, bind(c) :: MyFortranStruct character(200, kind=C_CHAR) :: Title integer :: SourceNode integer :: EndNode end type interface function GetInt() bind(C, name="GetInt") integer :: GetInt end function function GetStruct() bind(C, name="GetStruct") import MyFortranStruct type(MyFortranStruct) :: GetStruct end function end interface end module
Finally the Fortran executable:
program FortranStructures use InterfacesModule implicit none ! Variables type(MyFortranStruct) :: tmpStruct ! Body of FortranStructures print *, 'Before...' print *, GetInt() tmpStruct = GetStruct() print *, tmpStruct%Title print *, tmpStruct%SourceNode print *, tmpStruct%EndNode print *, 'After...' end program FortranStructures
I've attached a ZIP file that will hopefully be available to any and all. It contains all the source to demonstrate the problem. I'm running Visual Studio Professional 2015 with "Intel Parallel Studio XE 2016 Update 3 Composer Edition for Fortran Windows". With those two packages installed, the user should be able to open and run the solution ("FortranStructures.sln") contained in the ZIP. The solution consists of two projects: "CSharpDll.csproj" and "FortranStructures.vfproj". Note that the C# dll requires the "UnmnagedExports" NuGet that I referred to earlier.
Thanks again!
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hello
I will have declared Title in MyFortranstruct on C side as :
public
char
Title
[200]
;
The two structures must have the same size and members relative address must be equal on both sides and next, I will have copied the string explicitly:
for( i=0 ; i<200 ; i++} { if( i < strlen(string) ) result.Title=string; else result.Title=" " }
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
gvautier wrote:
I will have declared Title in MyFortranstruct on C side as :
public char Title[200];
I understand what you're saying, but this is C#, not C nor C++. In C#, the array size cannot be specified in a variable declaration.
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Have you tried sending two integer values instead: i.e the address of the first character in the string and the total length of the string?
You need to be aware whether or not the string is stored on the C# side as 'wide' characters i.e. with hex'00' added between each character and terminated with a double hex'00'. If that is the case, on the Fortran side you will need to use a windows API for WideCharacters to change from widecharacter string to a normal character string in order to extract the string you want.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Anthony Richards wrote:
Have you tried sending two integer values instead: i.e the address of the first character in the string and the total length of the string?
Perhaps you could show me how?
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Bradley P. wrote:
Quote:
gvautier wrote:
I will have declared Title in MyFortranstruct on C side as :
public char Title[200];
I understand what you're saying, but this is C#, not C nor C++. In C#, the array size cannot be specified in a variable declaration.
Brad.
What I want to say is that you must be sure that the structures on both sides have the same length. Check it by printing the lengths and offsets of members on Fortran and C# side. It must be 204 or 208 depending on alignment and integer size on Fortran side. Then try to put only one character back to Fortran.
In the final version, I think that it will be preferable to pad the Fortran string to the total length with space.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
See this post for how to solve one problem with C#-Fortran string transfers.
https://software.intel.com/en-us/forums/intel-visual-fortran-compiler-for-windows/topic/371292
When, on the C# side you define, for example, a string called logfile which you use as an argument in a C# function to be CALLED
by Fortran,
C# - [MarshalAs(UnmanagedType.LPStr] string logfile
On the Fortran side, you should use a compiler directive to apply the REFERENCE attribute to the argument of the called function because in this case C# sends a pointer (i.e. an address) to the string.
Note that what works OK above uses a SUBROUTINE call from FORTRAN to transfer a string from C# to Fortran. I note you use a FUNCTION . So perhaps try the subroutine method instead.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
gvautier wrote:
What I want to say is that you must be sure that the structures on both sides have the same length. Check it by printing the lengths and offsets of members on Fortran and C# side. It must be 204 or 208 depending on alignment and integer size on Fortran side. Then try to put only one character back to Fortran.
I've tried all manner of length matching. I doubt there's something there that I haven't tried. However, if you can get the sample I posted working, I'd be delighted!
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Anthony Richards wrote:
See this post for how to solve one problem with C#-Fortran string transfers.
Again, from everything I read in that post, it's passing a string in the other direction (i.e. from Fortran to C#). I have that direction working like a charm.
I'm wondering if I could get you to try the sample I uploaded. Getting that working would be the quickest way to 1) understand my question, and 2) know whether my question is answered.
Thanks!
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Bradley,
I have had success with StringBuilder when passing a string from C# to Fortran if Fortran has to manipulate the string and pass it back to C#. The only thing is your StringBuilder variable must have enough capacity to hold the string generated within Fortran. If string manipulation is not needed, then String type works as well.
Example for non-manipulated string:
Fortran:
FUNCTION IW_SetLogFile(iLen,cFileName) RESULT(iStat) INTEGER,INTENT(IN) :: iLen CHARACTER(LEN=iLen),INTENT(IN) :: cFileName INTEGER :: iStat END FUNCTION IW_SetLogFile
C#:
public static extern int IW_SetLogFile(ref int iLen, string cFileName);
Example for string manipulated in Fortran:
Fortran:
SUBROUTINE IW_GetName(iLen,cName) INTEGER,INTENT(IN) :: iLen CHARACTER(LEN=iLen),INTENT(OUT) :: cName END SUBROUTINE IW_GetName
C#:
public static extern int IW_GetName(ref int iLen, StringBuilder cName);
On the C# side, iLen must be equal to the capacity of cName plus 1 (cName.Capacity+1).
Jon
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
JonD wrote:
I have had success with StringBuilder when passing a string from C# to Fortran if Fortran has to manipulate the string and pass it back to C#.
From what I can tell, the examples you provide still show only a C# caller. I need Fortran to be the "caller" (i.e. the application).
Can I just ask that you try the sample I uploaded and see if you can get it working?
Thanks!
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Bradley P. wrote:
.. I'm trying to get a structure INTO a Fortran executable FROM a C# DLL. Yes, it's probably the reverse of what most people attempt. :-)
.. Note that the C# dll requires the "UnmnagedExports" NuGet that I referred to earlier. ..
Brad,
I suggest a little homework: forget about Fortran briefly. Since the direction you want to take is rather uncommon and it appears to depend entirely on what is possible with Robert Giesecke's "UnmnagedExports" NuGet - which can effectively be viewed as a middleman, a 3rd party solution - I suggest you try a C main program that calls your C# DLL, that is prove to yourself you are able exchange data in a C struct with the 3 components, especially a char[] array, with your C# dll. You may then post it here.
If you are able to get a simple, standard C program to do what you want, you will be able to achieve the same with Fortran. But if your C attempt is itself unsuccessful, you would need to work with Robert Giesecke or whoever supports NuGet to first get that to work. You can then come back to Fortran.
But if I had a need such as yours, I would NOT use Robert Giesecke's "UnmnagedExports" NuGet system, not that I know anything or have anything against it. It is just that I will be rather keen to avoid any further middlemen between Microsoft's .NET "managed code" framework and standard Fortran. That is, BESIDES anything that Microsoft itself might recommend. Looking at MSDN, that seems to be C++ code that can make use of COM interoperability in the Microsoft .NET Framework and can instantiate and free COM clients, all Microsoft components that are built and setup with Microsoft's own toolchain in Visual Studio. Thus I would seek to introduce a THIN layer of C++ unmanaged, wrapper dll that will have 'extern C' methods callable by Fortran and the C++ dll, in turn, will communicate with C# DLL using Microsoft recommended approach e.g., https://support.microsoft.com/en-us/kb/828736#bookmark-4.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
FortranFan wrote:
I suggest a little homework: forget about Fortran briefly.
I understand what you're saying, but my response is as follows:
- It feels like I'm really close given what I've already been able to do.
- I'm intentionally trying to avoid COM and the overhead and complexity it brings.
- My final goal is to interoperate between C# and Fortran. Given how close this feels, it seems a diversion to attempt to prove it first in C/C++ and then have to do the same again when I want to make it work with Fortran, especially when my C/C++ is as rusty as it is and I'd have to do a lot of learning there as well.
- I'm still optimistic that the knowledge and expertise in this forum is sufficient to solve this problem. I still think that there are people here who, if they took the time to download or recreate my simple sample, would be able to solve it very quickly or point me in the right direction.
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hello
I agree about the fact that if it works from C to C# it will works from Fortran
I think you should go back to basis.
First, replace your function with a subroutine because Fortran creates a shadow argument for function returning structures a strings.
subroutine GetStruct(struct) bind(C, name="GetStruct") import MyFortranStruct type(MyFortranStruct) :: struct !DEC$ ATTRIBUTES REFERENCE :: struct end subroutine
and on C# side the equivalent of :
void GetStruct(MyFortranStruct *result) { string="This is the title, padded to 200 characters just to make sure 345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; strncpy(&(result->Title),string,min(len(string),200)); result->SourceNode = 123; result->EndNode = 321; return; }
2nd, on both sides (before the call to GetStruct in Fortran and in GetStruct function in C#), print the size and address of the structure and addresses of all members. They must match.
3nd, find a way in C# function GetStruct to fill an array of chars (because that's what is a Fortran string)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
There is some confusion here because in your first post you say you want to 'send' a string from C# to Fortran (implying C# control) whereas in one of your latest replies you state that Fortran is to be the caller (i.e. in control). You also insist that the Fortran is an application (executable) whereas the C# is a DLL.
Taking your initial statement that you want to 'send' a string from a memory location initiated/controlled within a C# DLL 'to' a Fortran application i.e. executable program. I think there are two interpretations of or scenarios for this process.
Scenario #1
This implies that the C# code has control at the point when the string 'transfer' is to take place. Thus, unless you are using Dynamic Data Exchange (which I believe you are not), when you want to give the Fortran program access to the C# string (the latter possibly created within the C# code), you will have to call, from within the C# DLL code (which has control), a function or subprogram existing within the Fortran program that would allow control to be switched to the Fortran program and simultaneously give it access either directly to the memory location of and the length of the string buffer in the C# DLL, or to a copy of the string and an integer containing its length in bytes. Let's call it the SendString function (in the Fortran program code).
If you want to then tweak the string in the Fortran program and return it eventually to C#, you will have to be very careful if you directly access the memory location of the string, as you must not risk writing before or beyond the buffer in which it is held. You can then call a C# subprogram which allows the C# DLL to take back execution control (presumably until the DLL eventually returns control to the Fortran executable for it to complete to termination.)
After you have played with the string in the Fortran program (either directly, because you have its memory location, or indirectly because you have tweaked a copy of the string), then a call to GetString (a subprogram within the C# DLL) giving the address of the tweaked string and its buffer length or just allowing return to the C# code is required.
Scenario #2
This implies that the Fortran program has control at the point when the string 'transfer' is to take place. Thus, unless you are using Dynamic Data Exchange (which I believe you are not), when you want access to the already-existing C# string, you will have to call, from within the Fortran program, a function or subprogram existing within the C# DLL that would give you access either directly to the memory location of and the length of the string buffer, or to a copy of the string and an integer containing its length in bytes. Let's call it the GetString function (in the C# DLL).
If you want to then tweak the string in the Fortran program and return it eventually to C#, you will have to be very careful if you directly access the memory location of the string, as you must not risk writing before or beyond the buffer in which it is held. You can then call a C# subprogram which allows the C# DLL to take over execution until the DLL eventually returns control to the Fortran executable for it to complete to termination.
After you have played with the string in the Fortran program (either directly, because you have its memory location, or indirectly because you have tweaked a copy of the string), then a call to SendString (a subprogram within the C# DLL) giving the address of the tweaked string and its buffer length or just allowing return to the C# code is required.
Note that changing the length of the string will require deallocation and allocation commands within the C# code in order to ensure maintenance of accurate string buffer length.
If scenario#1 is your wish, then it would appear that a Fortran subprogram name must be made available (i.e. 'imported') to the C# DLL code during its creation. This requires it to be exported from a DLL, as far as I can recall, so you are talking about a Fortran DLL being intermedate between the Fortran executable and the C# DLL.
If scenario#2 is what is required, then the Fortran needs to be able to 'import' two C# DLL subprogram/function names during Fortran compliation (assuming that they can be exported in a form accessible to the Fortran compiler).
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
gvautier wrote:
First, replace your function with a subroutine because Fortran creates a shadow argument for function returning structures a strings.
THANK-YOU VERY MUCH!!! This was the tip that finally got me to a working solution!
OK, as I said, I have a working solution. Whereas I'd still appreciate comments on how this might be improved, it's working and I'm happy. :-)
It is worth noting that, since the structure is created (i.e. allocated) in C#, this will likely be a memory leak. In my case that really isn't much of a concern, since I'm passing a small structure and the Fortran application is just a straight run and done. I might be able to fix this by creating the structure in Fortran first and passing it to the C# code and simply allowing the C# code to fill it in. (I may even try that someday if/when time permits.) However, for now I'm satisfied.
Here are the relevant parts. My C# DLL consists of the following:
public class Structures { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct MyStruct { public int SourceNode; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 200)] public char[] Title; public int EndNode; } [DllExport("GetStruct", CallingConvention = CallingConvention.Cdecl)] public static void GetStruct(out MyStruct myStruct) { var result = new MyStruct(); result.SourceNode = 123; result.Title = "This is Title #1".PadRight(200).ToCharArray(); result.EndNode = 321; myStruct = result; } }
My Fortran interface looks like this:
module InterfacesModule use iso_c_binding implicit none type, bind(c) :: MyStruct integer :: SourceNode character :: Title(200) integer :: EndNode end type interface subroutine GetStructSub(struct) bind(C, name="GetStruct") import MyStruct type(MyStruct) :: struct !DEC$ ATTRIBUTES REFERENCE :: struct end subroutine end interface end module
My Fortran application (calling program) looks like this:
program FortranStructures use InterfacesModule implicit none type(MyStruct) :: tmpStruct print *, 'Before' call GetStructSub(tmpStruct) print *, tmpStruct%SourceNode print *, tmpStruct%Title print *, tmpStruct%EndNode print *, 'After' end program FortranStructures
Thanks again to all who contributed!
Brad.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Anthony Richards wrote:
There is some confusion here
Yes, I'm sorry. Despite my attempts to be as specific as possible, I realized eventually that I still hadn't communicated my requirements as well as I should have. That's one of the reasons I eventually posted my code - so that readers could see exactly what I was trying to do. None-the-less, I now have a solution (see my other post), so I'm happy! :-)
Brad.
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page