混合语言编程:从.NET调用Fortran函数

在2003年的Fortran标准中,引入了一个名为ISO C绑定的内建模块,其目的是显著简化Fortran与其他语言的混合编程。本文将解释如何在实际用例中使用ISO C绑定。

本系列旨在介绍混合语言编程的一些概念,并为感兴趣的人提供示例。虽然本系列主要介绍Fortran和C#语言的混合,但许多概念也适用于混合任何一组语言。

Fortran与.NET的互操作性介绍

ISO C绑定模块

混合模式汇编

交换更复杂和有趣的数据

回调和字符串

如何实现不同语言之间的互操作性(简称Interop)?本系列将解释使Fortran和C#代码在.NET框架上互操作所涉及的内容。假设主程序在.NET框架上运行;C#代码将调用Fortran代码。可能会涉及回调,但原生Fortran代码本身永远不会调用任何C#代码。

ISO C绑定模块

ISO C绑定模块主要是为了简化用C语言编写的程序与用Fortran编写的程序之间的互操作性。ISO C绑定解决了从调用约定到名称混淆和数据类型的一系列问题。

在本文中,将讨论ISO C绑定模块解决的每个问题,并以一个简单的示例结束,演示了模块的使用。示例Fortran函数是 return_integer 函数,它简单地返回传递的整数,以演示C#和Fortran之间的互操作性。

使用ISO C绑定模块时,处理名称混淆变得更容易。在之前的文章中,花了很多时间来弄清楚函数名称是如何混淆的,以及如何确保从.NET代码中调用正确的函数。

当使用ISO C绑定模块时,基本上不再需要关心名称混淆。函数以声明时的确切名称导出,甚至可以直接控制Fortran函数导出的名称。在以下示例中, return_integer 函数被修改以利用ISO C绑定:

FORTRAN代码:

module INTEROP implicit none ! 一个简单的函数,演示使用ISO C绑定返回值。 function return_integer_c(input) bind(C, name='return_integer_c_blabla') result(output) use iso_c_binding integer*4, intent(in) :: input integer*4 :: output write(*,*), "Passed value: ", input output = input end function end module

注意bind(C, name='return_integer_c_blabla')附加到函数声明上。使用name参数,可以控制函数导出的名称。编译上述代码并使用Dumpbin.exe工具查看生成的DLL文件的导出表,结果如下:

在导出表的倒数第二项中,return_integer_c_blabla显示的名称与要求的名称完全一致。认为不需要解释生活因此变得多么容易。

使用ISO C绑定模块可以简化的另一个方面是调用约定的固定。因为ISO C绑定模块旨在简化与C代码的互操作性,所以假设的调用约定在所有情况下都是C调用约定(CDECL)。再次,这消除了不得不忍受的问题,即不能将调用约定留给机会。这在上面段落中的示例中反映出来,可能会注意到以下几行被省略了:

FORTRAN代码:

! 不要将调用约定留给机会。 ! GCC$ ATTRIBUTES CDECL :: return_integer

在之前的文章中尚未触及的一个方面,将在下一篇文章中得到更多关注,是C#和Fortran之间来回传递数据。在之前文章的简单示例中,假设C#中的int等于Fortran中的整数。如果它们不相等,例如在内存中的大小不同,栈可能会被破坏,可能会发生各种奇怪的事情。是的,这是一个非常现实的问题,因为Fortran标准没有明确定义这些数据类型在内存中的大小,只定义了字。不幸的是,一个字和一个字节的大小不需要匹配(但在许多情况下它们是匹配的)。

幸运的是,ISO C绑定模块再次提供了帮助,通过声明在内存中具有固定值的Fortran数据类型,允许轻松地将它们与正在接口的其他语言匹配。下面是一个简短的概述,列出了这些新数据类型及其在C中的匹配对应物:

Fortran类型 名称常量 C类型
INTEGER C_INT int
INTEGER C_LONG long int
REAL C_FLOAT float
REAL C_DOUBLE double
CHARACTER C_CHAR char

现在能够明确指定Fortran函数中的整数必须是标准的4字节整数大小。这是通过声明函数参数使用integer(c_int)来完成的,如下例所示。注意需要通过在函数声明内放置use iso_c_binding语句来指示打算使用ISO C绑定模块的声明。

FORTRAN代码:

module INTEROP implicit none ! 一个简单的函数,演示使用ISO C绑定返回值。 function return_integer_c(input) bind(C, name='return_integer_c') result(output) use iso_c_binding integer(c_int), intent(in) :: input integer(c_int) :: output write(*,*), "Passed value: ", input output = input end function end module

有关ISO C绑定数据类型的更多信息,请访问:

将ISO C绑定付诸实践

将展示使用ISO C绑定模块返回传递的整数值的简单Fortran函数的完整代码,以及从.NET世界使用它的所需C#代码。

主要收获是这段代码的工作原理。总体而言,这段代码将与第一篇文章中介绍的代码非常相似。它应该可以让开始编写自己的简单混合语言代码。更复杂的例子将在本系列的后续文章中讨论,但已经包含在本文附带的源代码中。

定义了一个Fortran模块,它包含一个简单的函数,该函数利用ISO C绑定模块返回传递的整数。此代码保存在名为Interop.f90的文件中:

FORTRAN代码:

module INTEROP implicit none ! 一个简单的函数,演示使用ISO C绑定返回值。 function return_integer_c(input) bind(C, name='return_integer_c') result(output) use iso_c_binding integer(c_int), intent(in) :: input integer(c_int) :: output write(*,*), "Passed value: ", input output = input end function end module

在.NET端,需要使用PInvoke定义外部函数。为了保持代码组织,这是在一个名为FortranInterop的单独类中完成的。代码如下:

C#代码:

using System; using System.Text; using System.Runtime.InteropServices; namespace FortranInterop { public class Interop { public Interop() { } /// <summary> /// Returns the passed integer value. /// </summary> /// <remarks> /// External function definition for the simple return_integer_c Fortran function. /// Using CDECL calling convention and the function name as specified using the ISO C Binding module. /// </remarks> /// <param name="input">Integer value to return.</param> /// <returns>Returns the input value.</returns> [DllImport("Interop.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "return_integer_c")] public static extern int ReturnIntegerIsoC(ref int input); } }

注意仍然使用[DllImport]属性来告诉.NET运行时有关此外部方法的详细信息。调用约定是CDECL,因为在Fortran端使用了ISO C绑定模块。入口点简单地指定了Fortran代码中声明的正常函数名称,因为ISO C绑定模块避免了名称混淆。

最后是一段非常简单的控制台应用程序代码,它使用Fortran函数:

C#代码:

using System; namespace FortranInterop { class Program { static void Main(string[] args) { int value, result; value = 5; // Pass the input value as a reference. This is required to comply with the Fortran argument passing method. result = Interop.ReturnIntegerIsoC(ref value); // Output the result and wait for a keystroke. Console.WriteLine("Returned value: {0}", result); Console.Read(); } } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485