FORTRAN 与 .NET 的互操作性:第二部分 - ISO C 绑定模块





5.00/5 (2投票s)
FORTRAN 与 .NET 互操作系列文章的第二部分,我们将介绍 ISO C 绑定,它极大地简化了互操作性。
引言
在上一篇文章中,我们学习了如何使用 C# 代码从 .NET 环境调用一个基本的 Fortran 函数。在 2003 年的 Fortran 标准中,引入了一个新的内建模块,旨在使 Fortran 与其他语言的混合更加容易。本文将解释如何在我们的用例中使用 ISO C 绑定。
本系列旨在介绍一些混合语言编程的概念,并为感兴趣的读者提供示例。虽然本系列混合了 Fortran 和 C# 语言,但许多概念也适用于混合任何一组语言。
- FORTRAN 与 .NET 互操作性简介
- ISO C 绑定模块
- 混合模式程序集
- 交换更复杂和有趣的数据
- 回调函数和字符串
那么,如何在不同语言之间实现这种互操作性(或简称 interop)呢?本系列将解释在 .NET 框架上运行的 Fortran 和 C# 代码如何实现互操作。假定主程序运行在 .NET 框架上;C# 代码将调用 Fortran 代码。可能会涉及回调,但原生 Fortran 代码本身永远不会调用任何 C# 代码。
ISO C 绑定模块
也许从我介绍 Fortran 和 C# 代码互操作所需技术的上一篇文章得出的一个主要结论是,虽然这绝对是可能的,但并不是一件容易的事情。ISO C 绑定模块的设计初衷主要是为了简化用 C 语言编写的程序与用 Fortran 语言编写的程序之间的互操作性。ISO C 绑定解决了从调用约定到名称修饰和数据类型等一系列问题。
在本文中,我们将讨论 ISO C 绑定模块所解决的每个问题,并以一个简单的示例结束,演示该模块的用法。将使用的示例 Fortran 函数是 return_integer
函数,它只返回传入的整数,以演示 C# 和 Fortran 之间的互操作性。
名称修饰
使用 ISO C 绑定模块后,首先变得容易得多的事情是处理名称修饰。在上一篇文章中,我们花费了大量精力来弄清楚函数名称是如何修饰的,以及如何确保从 .NET 代码中调用正确的函数。
当使用 ISO C 绑定模块时,你基本上不再需要关心名称修饰。函数以声明它们时使用的确切名称导出,甚至可以直接控制 Fortran 函数导出的名称。在下面的示例中,return_integer
函数被修改为利用 ICO C 绑定。
! A simple function demonstrating returning a value using the ISO C binding.
function return_integer_c(input) result(output) bind(C, name='return_integer_c_blabla')
use iso_c_binding
integer*4, intent(in) :: input
integer*4 :: output
write(*,*), "Passed value: ",input
output = input
end function
注意附加到函数声明的 bind(C, name='return_integer_c_blabla')
。通过使用 name 参数,我们可以控制函数导出的名称。编译上面的代码,并在生成的 DLL 文件上使用 Dumpbin.exe 工具,会得到以下结果:
注意,在导出表的倒数第二项中,return_integer_c_blabla
显示的名称正是我们要求的。我想我不需要解释这让我们的生活变得多么容易。
调用约定
使用 ISO C 绑定模块使第二个问题得到简化是修复调用约定。因为 ISO C 绑定模块旨在简化与 C 代码的互操作性,所以所有情况下的默认调用约定都是 C 调用约定 (CDECL)。这再次消除了我们必须忍受的、我们无法随意选择调用约定的问题。这反映在上面段落的示例中,您可能会注意到以下行已被省略:
! Don't leave the calling convention to chance.
!GCC$ ATTRIBUTES CDECL :: return_integer
数据类型
在上一篇文章中还没有触及的一个方面,将在下一篇文章中得到更多关注,那就是在 C# 和 Fortran 之间传递数据。在上一篇文章的简单示例中,我们假设 C# 中的 int 等同于 Fortran 中的 integer。如果它们不同,例如在内存中的大小不同,堆栈可能会损坏,并可能发生各种奇怪的事情。是的,这是一个非常现实的问题,因为 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 绑定模块的声明。
! A simple function demonstrating returning a value using the ISO C binding.
function return_integer_c(input) result(output) bind(C, name='return_integer_c')
use iso_c_binding
integer(c_int), intent(in) :: input
integer(c_int) :: output
write(*,*), "Passed value: ",input
output = input
end function
有关 ISO C 绑定数据类型的更多信息,请访问:
http://fortranwiki.org/fortran/show/iso_c_binding
https://gcc.gnu.org/onlinedocs/gfortran/ISO_005fC_005fBINDING.html
应用 ISO C 绑定
现在,我将展示使用 ISO C 绑定模块的简单 Fortran 函数的完整代码,该函数返回传入的整数值,以及从 .NET 环境中使用它的所需 C# 代码。
然而,主要收获是让这段代码工作起来变得多么容易。总的来说,这段代码将与第一篇文章中提供的代码非常相似。它应该能帮助你开始编写自己的简单混合语言代码。稍后将在系列中讨论更详细的示例,但这些示例已包含在本文附带的源代码中。
Fortran 代码
我们定义了一个 Fortran 模块,其中包含一个简单的函数,该函数在利用 ISO C 绑定模块的同时返回传入的整数。此代码保存在一个名为 Interop.f90 的文件中。
module INTEROP
implicit none
! A simple function demonstrating returning a value using the ISO C binding.
function return_integer_c(input) result(output) bind(C, name='return_integer_c')
use iso_c_binding
integer(c_int), intent(in) :: input
integer(c_int) :: output
write(*,*), "Passed value: ",input
output = input
end function
end module
C# .NET 代码
在 .NET 端,我们需要使用 PInvoke 定义外部函数。为了保持代码的组织性,这是在一个名为 FortranInterop 的单独类中完成的。代码如下:
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 函数的一个非常简单的控制台应用程序的代码。
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();
}
}
}
运行示例
运行示例会产生如下图所示的熟悉结果。
编译和运行 Fortran 代码
Fortran 代码的编译和运行过程在本系列的第一个文章中已经相当详细地介绍过了。如果你不清楚如何处理示例,或者在运行中遇到问题,我恳请你阅读第一篇文章。
历史
- 2016 年 4 月 30 日:初始版本