1. 程式人生 > >C#與matlab混合程式設計以及C#程式設計

C#與matlab混合程式設計以及C#程式設計

把最近所做的C#與MATLAB混合程式設計,還有介面一些問題進行總結。MATLAB有非常強大的運算功能,C#有很多封裝好的庫可以用來做介面,所以利用兩者的優勢來製作一個c#呼叫MATLAB演算法程式的展示介面。

C#與MATLAB混合程式設計是整個專案中比較難的一部分,主要可以採用兩種方式:一是C#呼叫MATLAB編寫的動態連結dll檔案,這個方法我最後只用來呼叫畫圖程式,因為在利用這個方法來呼叫比較複雜的MATLAB演算法程式時,會出現問題,因為有些比較複雜的庫函式,不存在於MATLAB的執行環境MCR中,所以c#在呼叫含有這些庫函式的dll檔案時,會報錯;另一種方法是直接在C#裡呼叫MATLAB引擎來實現呼叫MATLAB的.m檔案進行運算,我在專案中就是利用第二種方法來呼叫演算法程式,就避免了第一種方法帶來的問題。

除了c#與MATLAB混合程式設計,還有一些編寫介面程式的總結。

一.c#呼叫MATLAB動態連結dll

這種方法,只需要MATLAB的安裝環境,不一定要安裝MATLAB,我在這裡演示使用的是MATLAB2016,MCR安裝可以在檔案安裝的檔案目錄下找到,如 MATLAB\R2016a\toolbox\compiler\deploy\win64,可以重新安裝一下執行環境以保證沒有錯誤。

1.編寫MATLAB函式.m檔案

首先編寫一個函式的.m檔案,編寫自己的函式方法,如下圖

實現的是,兩個矩陣的相加減,並輸出結果

2.創建制作dll檔案

在MATLAB命令視窗輸入如下圖程式碼,選擇編譯dll的語言

出現如下圖

我選擇的是c++2015語言進行編譯,然後開啟打包軟體,在命令視窗敲入deploytool,如下圖所示

選擇Libarary Compiler,如下圖

myFunction是自定義的dll名稱,myFunction_class是自定義的類名,將寫好的f.m檔案新增到工程內,f.m就是類下的方法,點選右上角的對勾進行打包就可以了。打包完成後檔案輸出如下

我們需要用的dll檔案就在for_redistribution_files_only裡

3.在c#中新增引用

如圖所示,右鍵點選引用,選擇新增引用,

點選瀏覽,找到逆所要新增的dll檔案,我在這裡需要用到的就是自己編寫的myFunction.dll和MWArray.dll,MWArray是MATLAB陣列矩陣的引用,主要用於MATLAB和c#之間的引數傳遞,檔案的位置就在MATLAB的安裝目錄下,如C:\ProgramFiles\MATLAB\R2016a\toolbox\dotnetbuilder\bin\win64\v2.0\MWArray.dll

4.c#呼叫MATLAB編寫的dll中的函式方法

如上圖中所示,MWNumericArray是MWNArray和c#語言資料的中間轉換類,是矩陣的轉換類,還有如MWCharray是字串的轉換類,都包含在引用MWArray.dll中,還有具體自己編寫的函式的使用可以在定義中檢視

我在上面用的是定義中第七行的方法

注意事項:c#呼叫MATLAB的dll檔案,主要問題出現在各種版本和編譯上的問題,還有一個比較常見的問題,如下圖所示

這個可能是編譯平臺的問題,我用的是vs2015和MATLAB2016,都是64位的,而且新增的MATLAB引用都是64位的,所以將平臺改成64位就可以了,在配置管理器中,如下圖

新新增64位的編譯平臺即可解決上述問題。

二.c#呼叫MATLAB dll畫圖將figure嵌入winform視窗中

需要用到Windows API來設定窗體,用FindWindow查詢影象窗體Figure 1的控制代碼。使用SetParent設定Figure 1父窗體為Winform的控制元件Panel,這樣就把figure放進Winform裡了,之所以放到panel控制元件裡,而不是作為子窗體在winform裡,是為了把它當做

Winform的一個控制元件,便於佈局。再使用MoveWindow移動到合適位置,setWindowLong去掉標題,不能通過邊框改變大小,在Winform窗體SizeChanged事件裡用MoveWindow改變Figure的大小,就能使figure的尺寸和窗體同步改變。

1.系統介面user32.dll

Windows API的程式碼,用於c#抓取MATLAB figure影象

 User32.dll是Windows使用者介面相關應用程式介面,用於包括Windows處理,基本使用者介面等特性,如建立視窗和傳送訊息。裡面的函式都可以在網上找到很詳盡的說明,這次介面的編寫中用到的user32.dll裡的MoveWindow涉及到了嵌圖的效果,所以在c#介面部分的引數設定需要注意,將函式進行一下說明:

MoveWindow

函式功能:該函式改變指定視窗的位置和尺寸。對於頂層視窗,位置和尺寸是相對於螢幕的左上角的:對於子視窗,位置和尺寸是相對於父視窗客戶區的左上角座標的。

函式原型:BOOL MoveWindowHWND hWnd.int x.int y,int nWidth,int nHeight,BOOL BRePaint

引數:

hWnd:視窗控制代碼。

x:指定視窗的新位置的左邊界。

y:指定視窗的新位置的頂部邊界。

nWidth:指定視窗的新的寬度。

nHaight:指定視窗的新的高度。

bRepaint: 確定視窗是否被重新整理。如果該引數為TRUE,視窗接收一個WM_PAINT訊息;如果引數為FALSE,不發生任何重新整理動作。它適用於客戶區,非客戶區(包括標題欄和滾動條),及由於移動子視窗而露出的父視窗的區域。如果引數為FALSE,應用程式就必須明確地使視窗無效或重畫該視窗和需要重新整理的父視窗。

返回值:如果函式成功,返回值為非零;如果函式失敗,返回值為零。若想獲得更多錯誤資訊,請呼叫GetLastError函式。

2.定義全域性變數,窗體載入事件

這當中主要用到了多執行緒的處理,還有c#引用在工作執行緒中更新介面執行緒的顯示

3.執行緒執行的方法

每隔50ms查詢一下figure窗體,找到嵌入到winform的panel控制元件裡

注意當中使用引用更新介面的方式,還有抓圖即FindWindow方法需要一定的時間,找到MATLAB的figure的控制代碼需要一定的時間,所以必須使用Thread.sleep()讓執行緒休眠一段時間讓作業系統找到MATLAB figure控制代碼。

4.其他

Radiobutton:切換不同畫圖效果

注意Form1_SizeChanged函式需要自己新增,下圖是InitializeComponent()方法(即初始化所有元件,這部分不需要自己寫,在你拖動控制元件時就會自動生成)下新增的函式宣告,然後需要自己在編寫部分新增定義

定義函式部分,如下圖所示,該部分主要是使嵌圖隨窗體大小移動而移動

三.c#呼叫MATLAB引擎

該方法相當於直接呼叫MATLAB的計算引擎在後臺進行運算執行,本人的開發環境是Matlab2016a和VS2015,由於是呼叫matlab引擎,所以必須安裝Matlab。

1.新增com引用

在工程中新增引用,選擇com,新增Matlab Application Type Library,如下圖

MATLAB的版本可能不同。新增引用後,引用目錄下多了一個MLApp檔案

2.引用程式碼

如下圖所示,下圖中的程式碼其實就是開啟後臺MATLAB計算引擎,注意需要設定MATLAB的當前路徑,否則會出現錯誤

如果在程式設計是採用MLAppClass的話還需要將檔案的屬性Embed Interop Types的true改為FALSE,不然會報錯,如下圖所示

Matlab.Execute()括號內的執行內容,其實相當於是MATLAB程式的命令視窗執行內容,因此我們可以直接在裡面執行MATLAB語句,或者是自己的.m檔案

3.執行自己的.m檔案

首先要清除MATLAB中的變數,以防影響到要執行的MATLAB程式,需要注意,傳遞變數時只能傳標量,不能傳矩陣和陣列,而且有字串變數時,在c#裡面要加單引號。

.m檔案,input相當於輸入的地址,裡面存了x,y變數

需要將m檔案放在工作目錄下,然後在c#裡呼叫的MATLAB就可以執行該程式了,如下圖所示

4.注意事項:

  1. 一定記得設定MATLAB工作目錄,不一定要在介面目錄下,可以設定一個相對目錄,並將需要執行的m檔案放在該目錄下;
  2. matlab.Execute(command),command相當於在命令視窗內容,“clear all”就是保證現在MATLAB內的變數不會影響之後的操作,但是要記得利用這個方法進行c#與MATLAB傳參時,只能用標量,不能涉及到陣列和矩陣,否則會報錯;
  3. 記得在向MATLAB傳遞字串時,記得加單引號。

四.c#程式設計部分

1.多執行緒的使用

除了介面部分的操作,還有比較耗時和耗記憶體的演算法運算部分,所以我就多開了一個工作執行緒去單獨做演算法運算,這樣能減輕介面部分的負擔,並且兩部分可以併發執行。主要程式碼如下

由於工作執行緒不能直接訪問介面執行緒的控制元件,如果需要在工作執行緒中去更新介面執行緒(防止介面“假死”,即在執行工作執行緒時,介面沒有任何輸入輸出,像卡住了一樣),則需要用到引用(delegate)方法去在工作執行緒更新介面執行緒的內容。主要方法在程式碼中都有註釋,控制元件更新的方法有Invoke和beginInvoke兩種方法,區別在於,Invoke會使得工作執行緒等待執行完成,避免了競爭條件(如其他執行緒直接操作介面執行緒所屬控制元件),避免了不可預料的結果。BeginInvoke不會等待委託方法的執行結束,不會使工作執行緒等待執行完畢,不會造成工作執行緒阻塞。

多執行緒的處理還需要注意資源搶佔問題,不同執行緒同時訪問同一資源時,可能會導致執行緒崩潰,這個需要特別注意,儘量不要有過多的共享資源,並且確保不會在同一時間訪問。

2.輸入輸出流的使用

在這次介面專案的過程中,主要是用到了二進位制檔案的輸入輸出方法,如下圖所

無論是二進位制流還是文字流都必須建立在檔案流的基礎上。

3.其他控制元件的使用

這次主要用到了gridControl、button、textbox、layoutcontrol等一些其他控制元件,由於控制元件的使用太過細節,具體需要用到可以到網上去找,一般都會有。

4.效率問題

要注意程式碼區域性性,這樣可以節省時間和空間資源;

還要注意儘量不要使用二維陣列,這樣會導致訪問速度的降低。