雪铁龙c5怠速声音大:FORTRAN和C#混合编程

来源:百度文库 编辑:偶看新闻 时间:2024/05/01 08:31:56
Department of Gophysics, Yangtze University, Jingzhou, Hubei, China 434023   
mou_yq@126.com
=====================================================================================
  摘要:          Fortran 语言是目前流行较广的使用与科学计算的程序开发语言,但是它的图形界面开发功能较弱.Visual C#是新一代面向对象开发语言,擅长于图形界面系统开发.因此,在开发交互式解释系统软件时,可以采用C#与Fortran混合开发, 这样可以发挥C# 的高效开发特点,并且使得现有的经典Fortran 计算程序可以得到充分利用,从而避免资源的浪费.本文主要从混合编程的实现方面阐述了一些常用的方法和要点.关键字: Fortran; C#; 混合编程; 动态链接库  1 引言       所谓混合编程,是指利用两种或两种以上的程序设计语言组合起来的程序设计开发,彼此互相调用,进行参数传递,共享数据结构或数据信息,从而形成一个统一的程序实体的过程.利用Fortran 语言和C# 语言进行的混合编程的目的是为了能够更充分的发挥两种语言各自的特点.Fortran是比较底层的数值计算语言,因此计算速度较高层的程序设计语言要快,且其自身就拥有非常强大的程序集和计算类的数据结构,因此非常适合科学计算.C# 是最近几年才出现的一门新型的界面开发语言,拥有十分强大的界面开发平台,并且其最大的特点就是快速开发; C# 程序设计以C 和C++ 入门, 在保留C++ 设计的灵活性的基础上, 加入了VB的快速开发.总体上, C#特点为 Easy to learn and easy to use, 因此十分的适合图形界面的交互系统开发.本文针对的是Compaq Visual Fortran 6.5版本的Fortran 和VS 2005 / VS 2008 版本的混合编程开发. 2 混合编程的技术关键       混合编程时,要注意调用程序与被调用程序遵守相同的规则,包括语言约定的一致性和数据处理的相容性等接口问题.       2.1 语言约定一致       <1> 命名约定.相互匹配的标识符在编程过程中应处理成一致的,因为不同语言的编译器智能机械的按本语言的命名约定进行处理.由于C# 语言的符号名需区分大小写,Fortran 语言中不存在大小写问题,如果处理不一致则导致程序连接失败.Fortran的缺省方式使符号名在OBJ文件中变成大写,如果在 C# 中调用一个使用Fortran 缺省的子例程时,在C# 中需用一个纯大写的名称来生成一个调用.    public static extrern void FORDLL ( ) ;   // 将子例程名声明为纯大写的形式如果想在C#中使用小写声明Fortran中的子例程,则应该在Fortran程序中需用C 和 STDCALL 属性将所有名称转换为纯小写的形式; 若是在C# 中调用一个例程以大小写混合形式出现的时候,需要使用Fortran 的 ALIAS 属性来解决混合形式之间的命名冲突.  !DEC$ ATTRIBUTES ALIAS: "Fsort" :: Fsort      ! 限定子例程名为Fsort的混合书写形式        <2> 调用约定. 混合编程中的调用约定,决定了程序对过程如何调用,如何传递参数等,可以从三个方面来考虑         首先,调用例程利用调用约定决定传递给另一个例程的自变量顺序;         其次,被调用的例程利用调用约定决定接收传递过来的参数的顺序;         最后,所有涉及堆栈这样一种数据结构的参数从堆栈中移去后,调用例程和被调用例程
              必须在调整堆栈的职责上取得一致.
C# 和Fortran 都可以使用C 属性或 STDCALL 属性作为调用约定,参数的传递方式为从左到右传递.        <3> 参数传递约定.只有以同样的方式发送和接收参数,才能获得正确的数据传送和正确的程序结果.常见的参数传递为值传递和引用传递两种.C# 默认的是值传递,而Fortran 默认的是引用传递,因此在混合编程过程中应注意保持传递方式的一致性.        若统一为应用传递类型,则将C# 的参数类型定义为引用类型,此时需使用ref 关键字
   public static extrern void FORDLL(ref int m);//通过 ref将整形参数m被定义为引用传递         若统一为值传递类型,则将Fortran 的参数定义为值类型,此时使用 VALUE 关键字   !DEC$ ATTRIBUTES VALUE :: m                  ! 使用 VALUE将m定义为值传递方式              若传递过程中既有值类型也有引用类型,则为上面的两种综合使用,Fortran 中使用引用为REFERENCE.   !DEC$ ATTRIBUTES REFERENCE :: m              ! 使用REFERENCEE将m定义为引用传递方式   2.2 数据类型一致
       数据传递也就是涉及基本的数值传递和字符串的传递,其中数值传递又包括单值型数据和数组型的数据.              <1> 数值传递满足好引用类型就基本可以实现,但是在传递数组的时候要特别注意: C# 的数组是从0开始记录的,而Fortran则是从1开始记录;另外,C# 采用的是按行存放,而 Fortran 则为按列存放.在递时应详细考虑数组问题.           例如在Fortran中的A[2][3]数组,在 C# 中就应为A[2][3].           并且数组传递还应注意,只需要传递数组的首地址即可,DLL需要的是数组的起始位置.                            int[] bb=new int[5];                            FORDLL(ref bb[0]);             <2> 字符串传递. 传递和返回字符串是混合编程中最为复杂,也是需要考虑问题最多的部分.C#和Fortran的字符串传递同样麻烦,首先,C# 中的字符串是双字节的Unicode类型,而Fortran中默认的是单字节的Ansi类型.这样产生的问题是C# 会自动把Fortran 的每两个字符合并为一个字符,Fortran 中的128 个字符的字符串变成了C#中的64个字符的垃圾.另外,C# 和Fortran 关于字符串的表示方式也有所不同: C# 的字符串表示方式与C 语言相同,使用\0表示字符串的结束;Fortran中则采用在最右端添加空格表示,并在最右端使用一个隐藏的参数表示实际的长度,因此要正确的传递字符串,应该解决如何正确表达自付串长度的问题.解决方案1:原理很简单,就是避免直接的进行不同类型的字符串传递
换用数组传递,具体操作流程如下:
C#中的字符串传向FORTRAN
step1: 在C#中将字符串分割为字符数组
step2: 再将字符数组转为ASCII码数组,然后传递给FORTRAN
step3: 在FORTRAN中利用 CHAR()函数将ASCII码还原为字符串即可
NOTE: FORTRAN传向C#也是可以采用相同的思路进行Applied Example
Example Codes:
---------------------------------------------------------------------
//C# 中的字符串分割,并转存为ASCII码数组
            string c = "abcdefg";         
            ASCIIEncoding ascii = new ASCIIEncoding();
            int[] num = new int[c.Length];
            for (int i = 0; i < c.Length; i++)
            {
                num[i] = (int)ascii.GetBytes(c)[i];
              
            }
           
            int m = c.Length;
            S(ref num[0],ref m);
---------------------------------------------------------------------
//C# 调用DLL声明及操作
[DllImport("DLL TEST.dll", SetLastError = true, CharSet = CharSet.Unicode,
                           CallingConvention = CallingConvention.StdCall)]
public static extern void S(ref int ka, ref int m);// 参数说明: ka为ASCII码数组名; m为ka数组大小(即字符串长度)
----------------------------------------------------------------------
!FORTRAN 中还原操作
subroutine s(ka,m)
 !dec$ attributes dllexport ::s
 character(m)::str
 dimension ka(m)
 integer i
 integer x,y
 do i=1,m
   str(i:i)=char(ka(i))
 enddo
end
-----------------------------------------------------------------------解决方案2:根据Compaq Visual Fortran Version 6.6 随机帮助文件中的 Programmer's Guide->Creating Fortran DLLs &->Programming with Mixed Languages 和 Language Reference->A to Z Reference->A to B->ATTRIBUTES 等相关部分的内容做出的一个测试例程,用来说明C#调用Fortran DLL过程中参数传入Fortran的实现(只涉及到了数值类型和字符及字符串类型,其它类型读者可以继续到帮助中阅读相关资料)。!FortranDLL.f90文件的内容如下:
!用来建立DLL项目
!函数 WTSTR(Str)用来建立Str文件并在该文件里记录Str字符串的内容
Subroutine writestr(str)
!DEC$ ATTRIBUTES DLLEXPORT,ALIAS:'WTSTR':: Writestr
Character*(*) str
Open(1,file=str)
Write(1,*) str 
End subroutine writestr
!
! 函数Iadd2(A,B)用来就算两个整型数A、B的和并将结果输出到屏幕
Subroutine iadd2(a,b)
  !DEC$ ATTRIBUTES C, DLLEXPORT,ALIAS:'Iadd2'::Iadd2
  integer::a,b
  integer::sum
  sum=a+b
  write(*,*)"The sum:",sum,"in DLL iadd2"
End subroutine iadd2
!上述Iadd2函数中用来C属性字段,是用来说明参数是按值来传送的,而在WTSTR函数中因为是
!用来传递字符类型的,所以不能用 “C”。(If C or STDCALL is specified for a subprogram,
!arguments (except for arrays and characters) are passed by value. )
!//C# 中的调用代码如下:
//这部分代码将编译生成.EXE可执行程序,调用上面的DLL
using System;
using System.Runtime.InteropServices;
namespace CCallDll
{
    class Program
    {
        [DllImport("dlltest.dll")] //WTSTR 函数说明部分,注意此处字符串参数的传递
        public static extern void WTSTR(String str, int strlength);
        [DllImport("dlltest.dll")] //Iadd2 函数说明部分
                                   //两种类型的参数传递涉及到在Fortran程序中的处理也不一样
        public static extern void Iadd2(int a, int b);
        // 主程序入口
        static void Main(string[] args)
        {
            //Delcare the String Variable. Notice:it's Unicode
            String unicodeString = "This.txt";
            Console.WriteLine("Original string is      : {0}", unicodeString );
            Console.WriteLine("The length of the string: {0}",  unicodeString .Length);
            //Call the Function of WTSTR( ,) 此处增加了一个表示字符串长度的参数是因为
            //Fortran、和C#存贮字符串的方法不一样(详情查阅相关资料,有很多介绍)
            WTSTR(unicodeString, unicodeString.Length );
            Iadd2(1000, 10);
        }
    }
}  3 混合编程的基本步骤 3.1 创建Fortran DLL     step1: 进入到CVF 6.5 集成环境下,依次打开File|New|Fortran Dynamic Link Library,为
            新的动态库命名 如:FORTRAN.DLL     step2: 使用Ctrl+N 快捷方式,添加文件FreeFormat的*.f90 子程序作为当前工作空间.     step3: Fortran建立动态链接库是使用子函数形式的,我们推荐是用Subroutine而拒绝
            function.同时要记得使用编译为DLL的注释性命名:                !DEC$ ATTRIBUTES DLLEXPORT :: FORDLL     step4: 当完成DLL的程序设计以后,编译生成DLL文件.进入工程文件中的Bin/Debug目录,便
            能找到该库文件. 3.2 C# 调用Fortran DLL 的过程    准备工作:    (1) 将Fortran 生成的DLL文件拷贝至 C# 工程文件的bin/Debug目录下,目的是确保可执行程
        序与库文件必须在同一文件夹中,便于Windows自动查找 DLL文件.    (2) 在进行DllImport 连接之前,需要在C# 中增加对动态连接库操作的类的引用:          using System.Runtime.Interopservices;          完成相关的连接设置就可以在C# 程序中使用Fortran DLL中的子例程了.         C# 调用动态链接库的参数设置         使用DllImport 属性来实现.然后使用一个实例来装载传递过来的子例程和相应的参数.         Example:                 [DllImport("FORTRAN.DLL",SetLastError=True,CharSet=CharSet.Unicode,          CallingConvention=CallingConvention.StdCall)]         public static extern void FORDLL()  DllImport 的命名参数:        a. CallingConvention    指示入口点的调用约定.如果未有指定,则使用默认值: 
                          CallingConvention.Winapi b. CharSet              指示用在入口点中字符集,默认为CharSet.Auto c. EntryPoint            给出DLL中入口点的名称.若未指定,则使用方法本身的名称 d. ExactSpelling         指示EntryPoint是否必须与指示的入口点的拼写完全匹配.默认为
                          False e. PreserveSig           指示方法的签名被保留还是被转换.当签名被转换时,它被转换为一
                          个具有HRESULT返回值和该返回该值的一个名为retval 的附加输出
                          参数的签名.默认值为True f. SetLastError          指示方法是否保留Win32 "上一错误".默认为False   FORDLL()修饰符说明:
      
 public      用来说明这个函数是公用的,可以在程序中的其它地方访问它; static       则表示这个函数是静态的,即C# 不会在调用的时候不会出入除参数以外的其它信
              息; extern     则表示这个函数由程序以外的模块实现; void        代表函数的返回类型.
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/HanYanBin/archive/2010/07/09/5723101.aspx