1 前言
C# (C Sharp)是微軟公司在2000年6月釋出的一種新的程式語言。C#與Java有很多的相似之處;包括了諸如單一繼承、介面、與Java幾乎同樣的語法,和編譯成中間程式碼再執行的過程。它又借鑑了Delphi的一個特點,與COM(元件物件模型)是直接整合的,而且它是微軟公司.NET windows網路框架的主角。
IDL則一直是應用程式開發和科學家進行視覺化與分析的首選語言,因為它功能強大,簡單易學,很少的幾行程式碼就能實現其他語言很難實現的功能,所以它是進行科學資料分析、視覺化表達和跨平臺應用開發的高效軟體和理想工具。作為第四代語法簡單、面向矩陣運算的計算機語言,IDL擁有豐富的分析工具包。同時支援遙感影像處理軟體ENVI的二次開發,使得利用IDL進行ENVI二次開發實現資料處理分析和視覺化程式變的非常容易。
2 C#呼叫IDL方式
C#可以透過COM元件的方式直接呼叫IDL進行開發。IDL提供了IDLDrawWidget和COM_IDL_CONNECT兩個元件,其中IDLDrawWidget元件是帶UI的可視元件,COM_IDL_CONNECT是不帶UI的功能元件,在實際使用的時候可以根據應用需求選取。
以IDLDrawWidget元件為例,該元件包含了多種功能方法(見表1),這些方法使得C#在呼叫的時候方便進行初始化、功能呼叫、引數傳遞和事件響應傳遞。
表1 IDLDrawWidget元件的方法
方法名稱 |
功能描述 |
CopyNamedArray |
複製IDL下陣列到元件呼叫環境中的變數陣列 |
CopyWindow |
將IDLDrawWidget元件顯示內容複製到Windows剪貼簿中 |
CreateDrawWidget |
IDLDrawWidget控制元件初始化介面 |
DoExit |
退出ActiveX控制元件並釋放IDL佔用的資源 |
ExecuteStr |
執行IDL命令,相當於IDL的命令列功能 |
GetNamedData |
獲取IDL中變數的值 |
InitIDL |
IDL執行環境初始化(1:成功;0,失敗;-1元件未被許可;-2,IDL未安裝許可) |
InitIDLEx |
IDL執行環境初始化(可傳入引數) |
|
元件中顯示內容輸出到預設印表機 |
RegisterForEvents |
元件是否傳遞程式事件(參考表17.2) |
SetNamedArray |
基於輸入的變數名和內容在IDL下建立陣列 |
SetNameData |
基於輸入的變數名和內容在IDL下建立變數 |
SetOutputWnd |
元件顯示內容輸出到指定視窗 |
VariableExists |
判斷IDL下是否存在此變數 |
3 關鍵技術
以在Visual Studio 2008 C#下呼叫IDLDrawWidget元件為例,分析下呼叫該元件的關鍵技術。
(一) 元件初始化
與其他ActiveX元件一樣,在VisualStudio的工具箱元件上單擊滑鼠右鍵,彈出選單中選擇[選擇項],見圖1.
圖1 載入元件
彈出的選擇工具箱項介面中點選TAB介面[COM元件],列表中找到“IDLDrawWidget Control3.0”並勾選(圖2)。若列表中不存在該元件,點選[瀏覽]查詢IDL安裝目錄下的子目錄“binbin.x86”中的“idldrawx3.ocx”檔案。
圖2 COM列表
元件初始化前需要設定元件的IDL安裝目錄,本機的IDL安裝目錄可以透過查詢登錄檔選項的方式獲取,獲取IDL8.0安裝路徑的C#示例程式碼如下:
//讀取登錄檔獲取IDL8.0
RegistryKey rsg = null;
rsg = Registry.LocalMachine.OpenSubKey("SOFTWARE\ITT\IDL\8.0", true);
if (rsg.GetValue("InstallDir") != null) //讀取失敗返回null
{
//初始化IDL80路徑
axIDLDrawWidget1.IdlPath = Path.Combine(rsg.GetValue("InstallDir").ToString(), @"IDL80binbin.x86idl.dll");
}
int n;
//初始化
n = axIDLDrawWidget1.InitIDL((int)this.Handle);
if (n == 0)
{
MessageBox.Show("IDL初始化失敗", "IDL初始化失敗,無法繼續!");
return;
}
(二) 功能呼叫
IDLDrawWidget元件支援呼叫IDL的原始碼檔案和sav檔案。其中ExecuteStr方法相當於IDL的命令列,而IDL可以使用點命令(見表2)在命令列下進行原始碼的編譯和功能呼叫。故,透過ExecuteStr方法可以輕鬆的呼叫IDL功能。
表2 點命令(DotCommand)
命 令 |
功 能 |
.COMPILE |
編譯程式碼; |
.CONTINUE |
繼續執行程式碼; |
.EDIT |
在編輯器中開啟程式碼以便編輯; |
.FULL_RESET_SESSION |
編譯器完全重置(包括DLM等); |
.GO |
執行最近編譯過的主函式; |
.OUT |
執行當前程式直至返回; |
.RESET_SESSION |
編譯器重置,等同於點選工具欄的“重置”; |
.RETURN |
程式返回; |
.RENEW |
新建一個pro; |
.RUN |
編譯記憶體中的程式並執行主程式; |
.SKIP |
跳過程式段; |
.STEP |
執行1個或n個程式; |
.STEPOVER |
執行1個程式段,如果程式段中呼叫了其他函式則除錯進入函式; |
.TRACE |
程式異常時繼續執行。 |
使用點命令在命令列下進行原始碼編譯和執行的示例程式碼:
IDL>;編譯原始碼檔案,注意原始碼檔案路徑是字串,用’’或””。 IDL> .compile 'C:tempfirstIDL.pro' % Compiled module: MYFUN. % Compiled module: FIRSTIDL. % Compiled module: TEST. IDL>;呼叫原始碼中的pro執行 IDL> firstidl abc 9 |
(三) 資料傳遞
IDLDrawWidget元件透過SetNamedArray、SetNameData等方法進行資料傳遞(表1),C#與IDL之間支援基本的資料型別變數和陣列傳遞(表3)。
表3 IDL與ActiveX下的通用的變數型別
IDL型別 |
ActiveX型別 |
IDL_TYPE_BYTE |
UT_UI1 – unsigned char |
IDL_TYPE_BYTE |
VT_I1 - signed char |
IDL_TYP_INT |
VT_I2 - signed short |
IDL_TYP_LONG |
VT_I4 - signed long |
IDL_TYP_FLOAT |
VT_R4 - float |
IDL_TYP_DOUBLE |
VT_R8 - double |
傳遞字串變數和陣列的示例程式碼如下:
//初始化定義變數
object objStr = "abc";
object objOri,objNow;
//定義變數
this.axIDLDrawWidget1.SetNamedData("var", objStr);
//編譯IDL功能程式碼並傳入單個變數
this.axIDLDrawWidget1.ExecuteStr(@".compile 'exchangevar.pro'");
this.axIDLDrawWidget1.ExecuteStr("exchangevar, var = var");
//將IDL中修改過的變數獲得並對話方塊顯示
objStr = this.axIDLDrawWidget1.GetNamedData("var");
//顯示IDL程式中更改後的值
MessageBox.Show("C#中的變數值為:"+objStr.ToString());
//定義陣列
int[,] dataarr = new int[3, 2] { { 6, 4 }, { 12, 9 }, { 18, 5 } };
//將陣列內容copy到IDL下的變數arr中
this.axIDLDrawWidget1.SetNamedArray("arr", dataarr, true);
//編譯IDL功能程式碼並傳入陣列
this.axIDLDrawWidget1.ExecuteStr(".compile 'exchangeArr.pro'");
this.axIDLDrawWidget1.ExecuteStr("exchangeArr,arr,oriArr= oriArr");
//透過CopyNameArray方法直接複製獲取IDL中的陣列
objOri = this.axIDLDrawWidget1.CopyNamedArray("oriarr");
//透過CopyNameArray方法直接複製獲取IDL中的陣列
objNow = this.axIDLDrawWidget1.CopyNamedArray("arr");
//彈出第一個元素的值
MessageBox.Show("C#中的陣列值為:" + ((Array)objNow).GetValue(0, 0));
(四) 事件傳遞
IDLDrawWidget元件可以在C#或IDL下響應鍵盤和滑鼠事件。即透過C#主程式可以觸發元件的事件並由IDL事件響應程式進行響應。元件的事件響應處理方式與元件的RegisterForEvents值有關,各個值的含義見表4。
表4 RegisterForEvents對應功能描述
值 |
功能描述 |
0 |
停止傳遞所有事件 |
1 |
傳遞滑鼠移動事件 |
2 |
傳遞滑鼠按鍵點選事件 |
4 |
傳遞檢視捲軸事件 |
8 |
傳遞暴露事件 |
元件介面中新增滑鼠滾輪事件的示例程式碼如下:
public Form1()
{
InitializeComponent();
//增加滾輪滾動事件
((Control)this).MouseWheel += new MouseEventHandler(Form1_MouseWheel);
}
private void IDLDrawWidgetCreate()
{
//指定事件由C#響應
axIDLDrawWidget1.RegisterForEvents(3);
}
//滑鼠滾輪事件
void Form1_MouseWheel(object sender, MouseEventArgs e)
{
//轉換當前滑鼠點在元件上的位置
y = axIDLDrawWidget1.Height - (e.Y - axIDLDrawWidget1.Location.Y);
//呼叫IDL的滑鼠時間程式碼
axIDLDrawWidget1.ExecuteStr("oImg.WheelEvents," + e.Delta.ToString() + ","+ (e.X - axIDLDrawWidget1.Location.X).ToString() + "," + y.ToString());
}
IDL中的響應該事件的程式碼如下:
;滑鼠滾輪時的事件
PRO ImgSHow::WheelEvents,wType,xPos,yPos
COMPILE_OPT idl2
;獲取元件原始大小
self.OWINDOW.GETPROPERTY, dimensions = winDims,graphics_tree = oView
oView.GETPROPERTY, viewPlane_Rect = viewRect
;判斷是放大還是縮小
IF wType GT 0 THEN rate = 0.8 ELSE rate = 1.125
;計算放縮後的顯示區域大小
oriDis =[xPos,yPos]*viewRect[2:3]/winDims
viewRect[0:1]+=(1-rate)*oriDis
viewRect[2:3]= viewRect[2:3]*rate
;更新顯示區域並重新渲染繪製
oView.SETPROPERTY, viewPlane_Rect = viewRect
self.OWINDOW.DRAW
END
類似的方式可以新增滑鼠拉框放大和縮小等功能,即透過C#與IDLDrawWidget元件構建了一個完整的影像顯示與基本處理程式,透過滑鼠可以對顯示影像進行放大、縮小和平移操作,並透過IDL實現了基本的影像處理、投影變換和模擬模擬功能。部分效果圖:
圖3 靈活的操控
圖4 影像顯示與影像處理
圖5 投影變換功能
圖6 衛星模擬模擬
4 結束語
透過IDLDrawWidget等元件提供的方法,C#可以方便靈活的整合IDL程式,輕鬆搭建視覺化分析與處理系統的框架,快速整合IDL的視覺化分析與處理功能。這樣充分發揮各語言的優勢,構建複雜的視覺化應用與分析的系統將會變得非常方便。