1. 程式人生 > >一個基於COM元件的Matlab與C#混合程式設計例項

一個基於COM元件的Matlab與C#混合程式設計例項

把matlab與其他語言混合使用是一種挺實用的技巧,在前期使用matlab進行演算法設計和計算模擬,而在需要編寫程式原型的時候把matlab演算法模組嵌入到C++或C#的程式裡,一方面省的再用C++或C#重寫程式碼,另一方面也可以直接呼叫matlab裡的一些工具箱。
至於混合程式設計的手段也是多種多樣的,最簡單的可以呼叫matlab引擎或將matlab檔案打包成動態連結庫,不過官方比較推薦的是打包成COM元件,這種方法很適合於演算法複雜,涉及很多matlab檔案的情況。matlab提供了打包COM的編譯器,使用起來也是很方便的。我在圖書館找的相關書籍都是講matlab和C++混合程式設計的,說實話用C++寫真的挺費勁的,最麻煩的是輸入輸出是矩陣序列的情況,需要寫很多轉換和處理的程式碼;不過如果用C#的話就方便很多了,根本不用管什麼型別,.NET框架幫我們處理了。下面是我參考其他資料後自己寫的一個matlab與C#混合的程式例項。

1 matlab程式編寫

先寫個matlab程式吧,我用的matlab比較老,2010b,程式的話就寫個簡單的繪製正弦波形函式,即繪製一個週期的y=Asin(ωt+θ) 波形,Aωθ 是三個輸入引數,具體程式碼如下:

%  Paras[1]:Amp
%  Paras[2]:Omega
%  Paras[3]:Theta
function []=DrawSin(Paras)
Amp=Paras(1);
Omega=Paras(2);
Theta=Paras(3);
t=0:(2*pi/Omega/1000):2*pi/Omega;
value=Amp*sin(Omega*t+Theta);
plot(t,value);

函式的輸入我用一個矩陣變數來存放三個引數,這是為了後面在C#程式裡呼叫時展示特意這麼做的。

2 生成COM元件

在matlab的”File”選單裡新建一個Deployment project:
這裡寫圖片描述
在Deployment project對話方塊中將target選擇為Generic COM Component:
這裡寫圖片描述
點選”Add class”新建一個類,之後再點選“Add files”將寫好的M檔案新增進去:
這裡寫圖片描述
這裡寫圖片描述
然後就可以點選上面的“Build”圖示進行編譯,旁邊那個圖示是打包,如果需要把COM元件用在其他沒有按照matlab的機器上的話就需要打包了,打包時會將matlab的編譯執行時(MCR)包含進去並生成一個安裝包,在別的機器上進行安裝,不過如果只是在本機測試的話就沒必要這麼麻煩了。Build的時候要注意這個專案檔案的目錄不要有中文,不然會出錯的。
這裡寫圖片描述


編譯完成後在專案目錄中的distrib中已經生成了一個dll檔案(這裡的名字是DrawSinCOM_1_0.dll),這就是COM元件的生成結果。COM元件使用前是需要在本機上註冊的,因為現在是在本機測試,生成過程中已經自動註冊好了,所以可以直接呼叫這個COM物件了。

3 C#程式呼叫

新建一個winform程式:
這裡寫圖片描述
把DrawSinCOM_1_0.dll複製到debug目錄下,在winform程式中新增DrawSinCOM_1_0.dll的引用,在後臺就可以初始化COM物件並呼叫其方法了,相關程式碼如下:

private DrawSinCOM.CDrawSin component;//定義COM元件物件
public Form1()
{
  InitializeComponent();
  this.textBox1.Text = "1";
  this.textBox2.Text = "10";
  this.textBox3.Text = "0";
  component = new CDrawSin();//初始化
}

private void button1_Click(object sender, EventArgs e)
{
  double[] input = new double[3];
  input[0] = Convert.ToDouble(this.textBox1.Text);//讀取輸入
  input[1] = Convert.ToDouble(this.textBox2.Text);
  input[2] = Convert.ToDouble(this.textBox3.Text);
  component.DrawSin(input);//呼叫方法        
}

如果我們檢視DrawSin的函式定義,可以看到其輸入引數的型別是object,因此我們可以直接將double[]物件直接傳進去,不需要管它內部具體怎麼轉換。執行結果:
這裡寫圖片描述

4 繫結matlab視窗

雖然呼叫成功了,不過波形視窗是彈出的,這樣還是很彆扭的,因此需要把這個波形視窗嵌入到C#視窗中。要實現這種“視窗綁架”的效果似乎必須要用Win32 API實現,需要在C#程式碼中匯入user32.dll中的函式。要掌握Win32 API中操作視窗相關的函式還是很費勁的,我在網路上搜羅了這裡要用到的主要的幾個函式:
FindWindow:查詢並返回指定的視窗控制代碼
SetParent:設定視窗的父視窗
MoveWindow:移動視窗並設定大小
GetWindowLong:獲取視窗相關引數
SetWindowLong:設定視窗引數
綜合使用這些函式就可以繫結視窗了,相關程式碼如下:

using System.Runtime.InteropServices;//新增名稱空間
... 
...
//匯入Win32函式
[DllImport("user32.dll", EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "SetParent")]
public static extern int SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", EntryPoint = "MoveWindow")]
public static extern int MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint);
private const int GWL_STYLE = (-16);
private const long WS_CAPTION = 0xC00000;
private const long WS_MAXIMIZE = 0x01000000L;
private const long WS_THICKFRAME = 0x00040000L;
private const int SW_HIDE = 0;
private const int SW_SHOW = 0;
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern long SetWindowLong(IntPtr hwnd, int nIndex, long newLong);
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
public static extern long GetWindowLong(IntPtr hWnd, int nlndex);    
...
...
private void button1_Click(object sender, EventArgs e)
{
      double[] input = new double[3];
      input[0] = Convert.ToDouble(this.textBox1.Text);
      input[1] = Convert.ToDouble(this.textBox2.Text);
      input[2] = Convert.ToDouble(this.textBox3.Text);
      component.DrawSin(input);//呼叫方法

      System.Threading.Thread.Sleep(100);
      IntPtr handle = FindWindow(null, "figure 1");//獲取波形視窗控制代碼
      SetParent(handle, this.panel1.Handle);//把波形視窗的父視窗設為主視窗中的面板
      long temp = GetWindowLong(handle, GWL_STYLE);//獲取視窗引數
      SetWindowLong(handle, GWL_STYLE, temp & (~WS_CAPTION) | WS_MAXIMIZE);//這裡是為了去掉視窗邊框
      MoveWindow(handle, 0, 0, this.panel1.Width, this.panel1.Height, true);//移動、調整視窗
}

注意到程式碼中用Sleep進行了延時,如果不延時,FindWindow方法很可能會返回零從而繫結失敗(也許是因為程式碼執行太快了)。此外我碰到過GetWindowLong呼叫失敗的情況,好像改了專案的.NET版本後又行了。最終的效果:
這裡寫圖片描述
視窗嵌入了主視窗,還可以方便地進行縮放、匯出等功能。

總結

利用COM技術可以輕鬆地構建matlab和C#混合程式設計應用,我個人覺得這種方法很適合在快速搭建軟體原型的時候使用,因為省去程式碼重構的時間了,不過如果是做產品或是對執行速度要求較高的場合,這種方式顯然不太合適了。此外,我在試驗的時候曾經試圖加入多核計算的元素,因為matlab和C#都有平行計算庫,不過後來發現混合程式設計時這是行不通的,我搜到了一個matlab的官方回覆,大意是matlab的模組打包中並不支援並行模組,這個回覆是很早之前的了,不知道最新版的matlab行不行。