当前位置: 澳门新濠3559 > 操作系统 > 正文

语言的计算速度,Matlab函数返回的结果显示在w

时间:2019-12-21 19:57来源:操作系统
大家好,DLL是以前VC++编写的,主要用于各种计算,32位。供其他非.NET程序调用的。现在有需求开发一个VB.NET的程序,正好可以使用DLL中的一些计算逻辑,所以想直接使用。有一些不明白

大家好,DLL是以前VC++编写的,主要用于各种计算,32位。供其他非.NET程序调用的。现在有需求开发一个VB.NET的程序,正好可以使用DLL中的一些计算逻辑,所以想直接使用。有一些不明白的地方:1,.Net编译的时候一班选择AnyCPU。这样的话在64位主机上应该就是按照64位运行的,这个时候,调用的32位DLL会有什么问题吗?2,DLL是否应该被编译成64位的更好?还是说其实和DLL是32位64位没有任何关系?因为是春节期间,又是竞标阶段,所以来不及试了。如果有兄弟有类似的经验,麻烦分享。谢谢!

前言

  最近正在给客户做的个人项目,要求实现C#与Matlab之间的调用,即C# winform界面收集用户输入的参数,将参数传递给Matlab的算法计算,Matlab函数返回的结果显示在winform界面上。

我们曾经熟悉的WindowsAPI, 我们曾经花费了大量精力写的代码,难道我们就要轻易放弃吗 不过当下微软已经把向下兼容性放在很重要的位置.
C#程序员使用已有的代码来作为自己程序的一部分是很普通的事情.所以NET为了解决使用已有代码的问题作了很多方面的工作.比如说对于已有的C++
代码你可以使用C++托管扩展(managed extensions)来进行封装,以及将会着重讲到的P/Invoke。

C#发展到现在,已是一门相当完善的语言,他基于C语言风格,演化于C++。并依靠强大的.NET底层框架。C#可以用来快速构建桌面及Web应用。然而在我们的实际工作中,尽管C#已经非常完善,但还是不能完成我们所有的工作。在很多工程计算中,C#语言的计算速度,精度,以及执行效率相对来说都达不到项目的要求。因此我们便考虑是否有一种方式将我们的工程计算部分和我们的项目分开,将计算部分用另一种执行更快,精度更高的语言来编写,然后在C#中调用,最后完成我们的工作。答案是肯定的。

  网上此类的文章较多,但自己在实现过程中还是有些差别,所以在项目进行之前,自己写了一个测试的例子来实现C#对Matlab函数的调用

 

Fortran是一门古老的语言,它是世界上最早出现的计算机高级程序设计语言,广泛应用于科学和工程计算领域。FORTRAN语言以其特有的功能在数值、科学和工程计算领域发挥着重要作用。然而Fortran程序本身不适合开发独立的应用程序,例如我们传统的桌面应用或者Web应用。因此这里我们便想将C#与Fortran结合,C#借助Fortran可以实现精度更高,计算更快的程序,而Fortran通过C#,便也能够达到可视化设计。

一.  测试用例简介

1) P/Invoke是什么?

 

  功能:Matlab函数计算两个数值a与b的和,a与b的值由C#提供,和值c经Matlab计算得出后,返回给C#

 

一、基本思路

运用Fortran,编写动态链接库(DLL),在DLL中提供计算的函数接口,然后在C#中调用该DLL中计算部分的函数,实现计算过程。

这里需要注意的是,由于我们使用的是Fortran编译器,生成的DLL属于第三方非托管DLL,因此无法直接在程序中添加DLL的引用。具体的做法将在后续部分说明。

 

  环境:Microsoft Visual Studio 2008

      P/Invoke的全称是Platform Invoke (平台调用) 它实际上是一种函数调用机制通 过P/Invoke我们就可以调用非托管DLL中的函数 
  实际上很多NET基类库中定义的类 型内部部调用了从Kernel32.dll,User32.dll,gdi32.dll等非托管DLL中导出的函数。

二、编写Fortran程序,生成动态链接库文件

知道思路之后便开始正式的Coding。首先新建一个空的Fortran Dynamic-link Library项目。

澳门新濠3559 1

在Intel(R) Visual Fortran点击Library,选中右图的Dynamic-link Library.然后点击OK.这时的项目如下所示:

澳门新濠3559 2

点击Sources File文件夹,选择新建项。

澳门新濠3559 3

添加一个新的Fortran文件

澳门新濠3559 4

然后便开始Fortran代码的编写工作。这里我们主要实现两个方法:

一个方法是求两个数相加之和,并返回结果。

另一个是输入一个数组,对这个数组进行排序,并找出最大值,最后返回排序后的结果,并返回最大值。

这里我们分别演示的是Fortran传出一个数和一个数组有何不同。

关于Fortran的基本语法不是本文的讨论范畴,请读者自行查阅资料。

下面给出的上述我们要实现的功能的具体Fortran代码:

DOUBLE PRECISION FUNCTION ADD(A,B)
!DEC$ ATTRIBUTES DLLEXPORT::ADD
!DEC$ ATTRIBUTES STDCALL,ALIAS:'Add'::ADD
    DOUBLE PRECISION:: A,B
    ADD=A+B
END

FUNCTION SORTANDFINDMAX(ARRAY,LENGTH)
!DEC$ ATTRIBUTES DLLEXPORT::SORTANDFINDMAX
!DEC$ ATTRIBUTES STDCALL,ALIAS:'Sortandfindmax'::SORTANDFINDMAX
DOUBLE PRECISION ::ARRAY(LENGTH)
INTEGER::I,J
DOUBLE PRECISION::SORTANDFINDMAX,TEMP
SORTANDFINDMAX=ARRAY(1)
DO I=1,LENGTH-1
    DO J=I+1,LENGTH
        IF(ARRAY(I).GT.ARRAY(J)) THEN
            TEMP=ARRAY(I)
            ARRAY(I)=ARRAY(J)
            ARRAY(J)=TEMP
            SORTANDFINDMAX=ARRAY(J)
            END IF
    END DO
    END DO
END

上面我们声明了两个Fortran函数,一个是计算两个数相加,一个是选择排序并找出最大值。

之后我们点击Visual Studio的Build Solution.开始编译成DLL。

关于代码段解释:

!DEC$ ATTRIBUTES DLLEXPORT::ADD

!DEC$ ATTRIBUTES STDCALL,ALIAS:'Add'::ADD

这两句代码很关键。下面通过三个一致来简单的说一下以上代码段的意思和C#调用需要注意的问题。

1.函数名一致:

在Fortran编译器中默认的导出函数名全部是大写形式。而在C#中调用Fortran Dll时必须指定函数名一致。在Fortran方面解决的办法是:使用ALIAS(别名)属性指定导出函数名。

例如对于下面的Fortran函数:

DOUBLE PRECISION FUNCTION ADD(A, B)

!DEC$ ATTRIBUTES DLLEXPORT:: ADD

DOUBLE PRECISION A,B  

ADD =A+B

END

对应的C#声明为:

[DllImport("MathDll")]

private static extern double ADD (double A, double B);

使用ALIAS修改后的定义如下:

Double Precision Function ADD (A, B)

!DEC$ ATTRIBUTES DLLEXPORT:: ADD

!DEC$ ATTRIBUTES ALIAS:'Add' :: Add

Double Precision A,B  

Add =A+B

End

对应的C#声明为:

[DllImport("MathDll")]

private static extern double Add (double A, double B);

而在C#中提供的解决方案是:通过使用Dlllmport的EntryPoint属性指定导出的Fortran函数名。

例如:

Double Precision Function ADD(A, B)

!DEC$ ATTRIBUTES DLLEXPORT:: ADD

DOUBLE PRECISION A,B  

ADD =A+B

END

对应的C#声明为:

[DllImport("MathDll",EntryPoint = " ADD ")]

private static extern double Plus(double A, double B);

此外,还可以使用 .NET Framework提供的dumpbin.exe工具查看DLL导出的函数名称。

    A. 在开始菜单中打开Microsoft Visual Studio 2010/Visual Studio Tools/ Visual Studio 2010 命令提示。

澳门新濠3559 5

     B. 在命令提示窗体中将路径指向编译生成.dll文件的路径,然后输入以下命令:

        dumpbin /exports FileName.dll

即可查看当前目录下FileName.dIl中导出的所有函数信息。

澳门新濠3559 6

2. 堆栈管理一致

堆栈管理约定包括:在调用过程中子例程接受参数的数目和顺序,调用完成后由哪一方来清理堆栈等。C#语言在windows平台上的调用模式默认为StdCall模式,既由被调用方清理堆栈。而Fortran语言则默认由调用方清除。因此必须统一调用双方的堆栈清除方式才能保证2种语言间的正常函数调用。这一约定在Fortran语言或C#语言中均可以采取措施进行统一。

在Fortran语言中可以通过编译指令“!DEC$”后的可选项“C”或“STDCALL”参数来实现:

A.

!DEC$ ATTRIBUTES STDCALL ::Object

该语句语句中的STDCALL模式指定由被调用方清除堆栈(其中“Object”为变量名或函数名)。

B.

!DEC$ ATTRIBUTES C :: Object

该语句中的C模式声明由主调函数清除堆栈(但在传递数组和字符串参数时不能用此方法指定)。

如果在C#语言内做改动,则需要在DllImport属性中设置CallingConvention字段的值为Cdecl(表示由调用方清理堆栈)或StdCall(表示由被调用方清理堆栈)。

[DllImport("FileName.dll", CallingConvention = CallingConvention.StdCall)]

[DllImport("FileName.dll", CallingConvention = CallingConvention.Cdecl)]

只有当Fortran程序和C#程序的堆栈管理一致时,才能保证正常的调用。

3.参数类型保持一致

 在Fortran中常用的数据参数类型有:

REAL:表示浮点数据类型,即小数,等价于C#的float,

INTEGER:表示整数类型,相当于C#的int数据类型

DOUBLE PRECISION:表示双精度数据类型,相当于C#的double数据类型。

  在C#调用Fortran DLL是必须保证参数的一致性,例如在Fortran中变量定义的是REAL类型,而我们传入的是Double,那么就会出现计算错误。

     Matlab R2009a(Version 7.8.0.347)

 

三、编写C#代码调用Fortran DLL

 

C#调用的Fortran的过程很简单,只需要注意上述说的几个问题即可。

 

这里我们先新建一个控制台应用程序:

 

澳门新濠3559 7

 

然后将我们编译的Fortran项目所生成的DLL拷贝到控制台应用程序的Debug文件夹下。

澳门新濠3559 8

接着我们添加一个类:FortranMethod.cs

该类用来调用Fortran DLL。

代码如下:

using System;
using System.Text;
using System.Runtime.InteropServices;

namespace MixedProgram
{
   public static class FortranMethod
    {
       [DllImport("TestDll.dll",CallingConvention = CallingConvention.Cdecl)]
       public static extern double Add(double a, double b);

       [DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)]
       public static extern double Sortandfindmax(double[] array, int length);
    }
}

关于C#调用注意的事项在上面已说明,在此不再讨论。

然后在Main函数中测试我们的Fortran DLL。

示例代码如下:

using System;
using System.Collections.Generic;
using System.Text;

namespace MixedProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请输入两个数相加:");
            double num1=Convert.ToDouble(Console.ReadLine());

            double num2 = Convert.ToDouble(Console.ReadLine());
            Console.WriteLine("输入的两个数是:" + num1 + " ," + num2);
            double sum = FortranMethod.Add(num1,num2);
            Console.WriteLine("求和结果是:"+sum);

            double[] Array = { 1,5,2,4,3,7,6};
            Console.WriteLine("初始数组:");
            for (int i = 0; i < Array.Length; i++)
                Console.Write(Array[i]+" ");

            double b=FortranMethod.Sortandfindmax(Array, Array.Length);
            Console.WriteLine("n"+"排序后:");

            for (int i = 0; i < Array.Length; i++)
                Console.Write(Array[i]+" ");

            Console.WriteLine("n"+"最大值为:");
            Console.WriteLine(b);

            Console.ReadKey();
        }
    }
}

到此为止,所以的工作已经完成,下面看一下结果:

澳门新濠3559 9

到此为止,C#与Fortran编程的小示例就已经完成了。

 

 

总结:

 

本文主要演示了如何使用C#调用Fortran的DLL来实现相关的计算工作。并主要讲了C#调用时应该注意的事项。在工程计算中如果对于精度要求较高,计算较复杂时,我们便可以考虑通过C#与Fortran的混合编程来达到所需的要求。本文是基于本地调用Fortran DLL,下一篇将讲解基于Web调用Fortran DLL.

 

 关于C#与Fortran混合编程还可参加这篇文章:

 

 

(版权所有,转载请标明出处)

 

  备注:由官网可知,Matlab对类似其他程序调用都提供了很好的支持,这里没有选择VS版本大于Matlab版本,是因为担心Matlab版本只支持自己之前的VS版本。

2) 看一个最简单的例子

 

 

二.  实现步骤

     //这个只是设置鼠标相对于屏幕位置的系统函数,当然还有许多API函数的位置是需 要公式计算的(例如:SendInput函数,位于user32.dll内)。

  1. 实现Matlab函数

 

    A. 打开Matlab R2009a,新建testAdd.m文件

     [DllImportAttribute("user32.dll", EntryPoint="SetCursorPos")]

    B. testAdd.m中实现加法函数

 

1 function y = testAdd(a, b)
2 y = a + b;

     [return:MarshalAsAttribute(UnmanagedType.Bool)]  //可写可不写,定义如何封送返回参数

 

 

  2. 编译testAdd.m,得到DLL文件,以便由C#引用

   public static extern  bool SetCursorPos(int X, int Y) ;

    A. 在Matlab命令行中输入"deploytool",即可弹出"Deployment Tool"工具窗口

 

      在网上看很多资料,执行"deploytool"命令之前都需要安装Matlab编译器(命令:"mbuild -setup"),但我不这样做,也可编译。怀疑是否与我将VS,Matlab都装在一台机器有关。另外即便我执行这安装命令,也找不到正确的编译器。总之,我并没有按照网上教程,直接"deploytool"即可。这也提醒自己:实践过后,才知是否正确,不要盲目听从别人方法。

3) P/Invoke的过程(引用图)

    B. 编译DLL,需要在"Deployment Tool"工具窗口中新建Deployment project

 

      这里我建了名为"test"的工程,选择project类型时,应选择.NET Component,因为这里我需要其作为C#的引用。同时注意这里的工程名,即是你编译出DLL的名称,同时C#程序调用时,"Test"即为封装Matlab函数的类名。他将你的工程名,首字母大写用为高级语言中的类名。

澳门新濠3559 10

    C. 配置"test" project

 

      首先将testAdd.m添加到test工程下Test文件夹中(右击Test,选择Add File)(注意:这里不要使用中文路径,详见下方六.测试过程中Bug记录)。

  P/Invoke依次执行以下操作.(理解重要)

      其次选择Setting,在设置中,配置.NET Microsoft Framework,由"Default"改为"2.0",不能用默认。网上说法是否则编译出的DLL会有问题。

 

      注意:这里网上许多资料讲,要将Assembly Type设置为Shared,但我发现如果这样,必须要提供Encryption Key的文件路径,这个就没法提供了。所以我并没有这样做,事实上没有影响。疑惑的地方。

  1 查找包含该函数的非托管DLL

    D. 点击"Build"编译文件(如下图所示)

 

      澳门新濠3559 11

  2 将该非托管DLL加载到内存中

     即可在testsrc路径下得到编译后的DLL文件

 

     澳门新濠3559 12

  3 查找函数在内存中的地址并将其参数按照函数的调用约定压栈

  3. 实现C#程序,调用Matlab编译出的DLL

 

    A. C#项目中,导入Matlab DLL引用

  注意事项:只在第一次调用函数时,才会查找和加载非托管DLL并查找函数在内存中的地址。

      导入的文件:test.dll, testNative.dll, MWArray.dll(%matlabpath%toolboxdotnetbuilderbinwin32v2.0,MWArray是用于C#与Matlab之间的数据交换类,传值,取结果都用到它)

 

    B. 实现C#调用代码

  4 将控制权转移给非托管函数

 1 using System.Data;
 2 using System.Drawing;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Windows.Forms;
 6 using MathWorks.MATLAB.NET.Utility;
 7 using MathWorks.MATLAB.NET.Arrays;
 8 using test;
 9 
10 namespace testMatlab
11 {
12     public partial class Form1 : Form
13     {
14         public Form1()
15         {
16             InitializeComponent();
17 
18             label6.Text = "";
19         }
20         
21         private void button1_Click(object sender, EventArgs e)
22         {
23             //Get input number from UI
24             int iTextBox1 = int.Parse(textBox1.Text.ToString());
25             int iTextBox2 = int.Parse(textBox2.Text.ToString());
26                         
27             MWArray result = null;
28             MWNumericArray a = new MWNumericArray(iTextBox1);
29             MWNumericArray b = new MWNumericArray(iTextBox2);
30 
31             
32             //call function testAdd, provided by Matlab
33             Test t = new Test();
34             result = t.testAdd((MWArray)a, (MWArray)b);
35 
36 
37             int y = ((MWNumericArray)result).ToScalarInteger();
38             
39             //show result in UI
40 
41             textBox3.Text = y + "";
42             label6.Text = "Get the result by Matlab DLL, Answer: " + y;
43         }
44     }
45 }

 

 

  注意:当非托管函数产生异常时,P/Invoke会将异常传递给托管调用方。但需要设置相关特性,后面 章节会有相关说明。

三.  执行C# winform程序,验证计算结果,确定是否执行了Matlab函数调用

 

    1. 输入初值

回到目录

   澳门新濠3559 13

 

    2. 得到结果

初次使用

   澳门新濠3559 14

 

    

  1).要使用P/Invoke我们需要描述调用函数的原型.CLR会使用这些信息进行调用.

四.  小结

 

  由此,可实现C#对Matlab算法的调用。当然这里只是自己开发需要时,设计的一个小例子。由于Matlab计算,输入与结果需要大量的矩阵,那C#与其之间的数据交换也是通过MWArray进行,只要遵循接口规范,都可以实现。这里不再研究,网上资料也很多。

    下面用一个简单的例子来说明如何使用P/Invoke 在这个例子中我们将调用SetCursorPos  这个API函数来做说明。

  

 

五.  完整的测试例子附件

    该函数的定义是这样描述的:

  Matlab函数:

 

  C#程序:

 

 

 

.  测试过程中Bug记录

澳门新濠3559 15

  1. 调用DLL时,程序报错"MathWorks.MATLAB.NET.Utility.MWMCR.mclCreateComponentData,错误描述是:传递给系统调用的数据区域太小"

函数功能:该函数把光标移到屏幕的指定位置。如果新位置不在由 ClipCursor函数设置的屏幕矩形区域之内,则系统自动调整坐标,使得光标在矩形之内。

函数原型:BOOL SetCursorPos(int X,int Y);  

参数:X:指定光标的新的X坐标,以屏幕坐标表示。Y:指定光标的新的Y坐标,以屏幕坐标表示。   

返回值:如果成功,返回非零值;如果失败,返回值是零  

备注:该光标是共享资源,仅当该光标在一个窗口的客户区域内时它才能移动该光标。 

    解决方法:不能使用中文路径的.m文件,可能会有诡异的问题产生

澳门新濠3559 16

    参考链接:

 

 

 

虽然网上参考资料很多,但经过自己实践,还是发现一些不同的小地方。项目进行前,通过一个小的测试例子来证明技术路线可行。希望对需要的同学有帮助。抛砖引玉:-)

 

 

  2).开始使用

Best Regards

 

Kevin Song

       为了在C#中正确的表示以上声明.我们需要将Win32类型转换为对应的C#类型.int是四个字节的整数我们可以使用int或者 uint
    在这里两者的区别并不大,我选择使用int.一来int更常用,二来 int是CLS兼容类型(这意味着在所有基于NET都具有这个类型).BOOL对应的则是bool
    好了,我们可以写出SetCursorPos(...)函数的C#版本定义了

  

 

public static extern bool SetCursorPos(int X, int Y) ;

 

 

 

  这个声明已经告诉了运行时该如何调用SetCursorPos(...)函数.下面就要告诉运行时应该到哪里去找到这个函数了.同样

 

  通过MSDN.我们可以知道SetCursorPos(...)函数被定义在user32.dll 库中.这意味着SetCursorPos(...)函数的运行时代码在user32.dll中.

 

  我们用DIIlmport属性来告诉运行时SetCursorPos()函数的位置

 

   [DIIImport(”user32.dll”)]

 

 

 

   好了!一切准备就绪了.下面就让我们来看看移动鼠标位置把。(让鼠标在动一会儿把..)

 

澳门新濠3559 17

using System;
using System.Runtime.InteropServices;

namespace ConsoleTest
{

    class Program
    {

        [DllImportAttribute("user32.dll", EntryPoint = "SetCursorPos")]
        [return: MarshalAsAttribute(UnmanagedType.Bool)]
        public static extern bool SetCursorPos(int x, int y);

        //该实例运行效果为 鼠标随机在屏幕内跳动
        static void Main(string[] args)
        {
            Random r = new Random(unchecked((int)DateTime.Now.Ticks));
            int i = 1;
            do
            {
                int x = r.Next(0, Screen.PrimaryScreen.Bounds.Width);
                int y = r.Next(0, Screen.PrimaryScreen.Bounds.Height);
                SetCursorPos(x, y);
                i++;
                Thread.Sleep(300);

            } while (i < 100);
            Console.ReadKey();

            //^_^,如果你写个随机数无限循环,就可以让别人永远无法使用鼠标,除非关 //机。自己试的时候别写死循环,小心关不掉。

        }
    }
}

澳门新濠3559 18

 

 

 

  需要注意的一点是DIIImport属性允许你可以调用任意的Win32代码.这其中可能会有一些不怀好意的函数.
  所以在使用P/Invoke调用非托管代码时需要你完全信任所调用的函数。

 

编辑:操作系统 本文来源:语言的计算速度,Matlab函数返回的结果显示在w

关键词: