在2003年的Fortran标准中,引入了一个名为ISO C绑定的内建模块,其目的是显著简化Fortran与其他语言的混合编程。本文将解释如何在实际用例中使用ISO C绑定。
本系列旨在介绍混合语言编程的一些概念,并为感兴趣的人提供示例。虽然本系列主要介绍Fortran和C#语言的混合,但许多概念也适用于混合任何一组语言。
ISO C绑定模块
混合模式汇编
交换更复杂和有趣的数据
回调和字符串
如何实现不同语言之间的互操作性(简称Interop)?本系列将解释使Fortran和C#代码在.NET框架上互操作所涉及的内容。假设主程序在.NET框架上运行;C#代码将调用Fortran代码。可能会涉及回调,但原生Fortran代码本身永远不会调用任何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绑定模块返回传递的整数值的简单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();
}
}
}