2015年8月12日 星期三

[原創] LabVIEW wrapper Callback from dll (3) Example:EnumWindows

最近有網友來信詢問於在LabVIEW中如何使用”User32.dll”中API EnumWindows由於它是使用callback的方式傳回資料因此需封裝成dll的形式給LabVIEW使用,本篇將介紹此過程的程式撰寫方式。
LabVIEW中若想要使用dll來註冊Eventdll必須加入extcode.h這個檔頭和使用Librarylabview.liblabviewv.lib
對於到底是使用labview.liblabviewv.lib,根據這篇文章(labview.lib or labviewv.lib?[http://forums.ni.com/t5/LabVIEW/labview-lib-or-labviewv-lib/td-p/2916894]),我的理解是為了對應不同版本的LabVIEW RunTime,應該要使用labviewv.lib
首先由微軟的MSDN查看EnumWindows的使用說明:
BOOL WINAPI EnumWindows(
  _In_ WNDENUMPROC lpEnumFunc,
  _In_ LPARAM      lParam
);

Parameters

lpEnumFunc [in]
Type: WNDENUMPROC
A pointer to an application-defined callback function. For more information, seeEnumWindowsProc.
lParam [in]
Type: LPARAM
An application-defined value to be passed to the callback function.

Return value

Type:
Type: BOOL
If the function succeeds, the return value is nonzero.
由此可看出EnumWindows需要輸入的參數,lpEnumFunc 就是callback函數的原型,要傳入實作函數程式碼的位址,lParam 則是輸入的參數,要傳入未定的資料型態位址。
接著再看看EnumWindowsProc的函數原型定義:
BOOL CALLBACK EnumWindowsProc(
  _In_ HWND   hwnd,
  _In_ LPARAM lParam
);

Parameters

hwnd [in]
A handle to a top-level window.
lParam [in]
The application-defined value given in EnumWindows or EnumDesktopWindows.

Return value

To continue enumeration, the callback function must return TRUE; to stop enumeration, it must return FALSE.
由此可知EnumWindowsProc這個callback函數的原型宣告方式,當callback發生時系統會提供hwnd就是我們想要列舉的程式handle值,而lParam在這邊並沒有用到。
有了這些資訊就可以開始編寫提供給LabVIEWdll,首先先定義回傳給LabVIEW的資料型態,由於希望取得的是目標程式的類別字串和視窗名稱自串,因此宣告一個結構包和兩個字串:
typedef struct tagStrRec {
          LStrHandle Str1;
          LStrHandle Str2;
} StrRec, *StrRecPtr;
這樣的話在LabVIEW的部分就可以解成cluster包含兩個String的結構
接著宣告全局變數用於儲存LabVIEW UserEvent的位址和常數。
// 紀錄LabVIEW UserEvent的位址
LVUserEventRef EnumWindowsEvent;
// 預設最長字串長度為256 Bytes
#define STRING_LENGHT 256
然後再宣告dll輸出的函數
//用於註冊LabVIEW事件
MgErr __declspec(dllexport) __cdecl RegisterEnumWindowsEvent(LVUserEventRef *value);
//用於呼叫dllEnumerateWindows
void __declspec(dllexport) __cdecl EnumerateWindows(void);
實作這兩個函數與callback函數就完成dll的部分
MgErr __declspec(dllexport) __cdecl RegisterEnumWindowsEvent(LVUserEventRef *value)
{
          //用於紀錄LabVIEW程式內Event的位址
          EnumWindowsEvent = *value;
                    return 1;
}
void __declspec(dllexport) __cdecl EnumerateWindows(void)
{
          //透過dll呼叫EnumWindows,並傳入dll內宣告的callback函數位址。
          EnumWindows(EnumWindowsProc, NULL);
}

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
          char class_name[STRING_LENGHT];
          char title[STRING_LENGHT];
          StrRec returnData;
          // init labview string
          returnData.Str1=(LStrHandle)DSNewHandle(sizeof(int32)+STRING_LENGHT*sizeof(uChar));
          returnData.Str2=(LStrHandle)DSNewHandle(sizeof(int32)+STRING_LENGHT*sizeof(uChar));
          // get windows class and title data
          GetClassName(hwnd,class_name, sizeof(class_name));
          GetWindowText(hwnd,title,sizeof(title));
          // convert char arr to labview string
          PopulateStringHandle(returnData.Str1,class_name);
          PopulateStringHandle(returnData.Str2,title);
          // use labview event to send back data
          PostLVUserEvent(EnumWindowsEvent,(void *)&returnData);

          return TRUE;
}

LabVIEW程式的部分主要就是dll的呼叫與User Event的註冊,這邊就直接看圖說故事了




執行後的畫面如下,可以列舉到記事本的類字串與視窗字串:

2014年6月9日 星期一

[原創] LabVIEW wrapper Callback from dll (1) Introduction

LabIEW 的 Call Library Function.vi 提供了介面讓LabVIEW使用者得以呼叫其他語言開發的程式,網路上相關教學資源算是豐富,可以參考下面連結:
官方文件:
How Do I Call a Dynamic Link Library (DLL) from LabVIEW?
Call Library Function Returns the Wrong Function Prototype and Function Parameters for DLLs
Creating Dynamic Link Library (DLL) in Microsoft Visual C++ 6.0 for use in LabVIEW

官方論壇文章:
Calling C/C++ DLLs Containing Simple and Complex Datatypes from LabVIEW
這篇大概解決了90%遇到複雜的structure、pointer的傳遞方式

官方範例程式:
Windows Message Queue Library
這篇應該是最經典範例程式,範例程式教使用者如何用C wrapper LabVIEW內的Message Queue,讓開發人員了解如何打通傳遞dll內的資料到LabVIEW裡面的Queue元件,而且範例程式傳遞的資料不別的正是LabVIEW程式天生缺少的message loop訊息

雖然網路資源如此豐富,但還是缺少了較完整的wrapper callback函數的教學,雖然LAVA有一篇文章我認為已經說明得非常清楚,但對於C/C++/Visual C++相對陌生的LabVIEW開發人員而言還是挺困難的,因此本篇文章會把重心移到如何編寫wrapper callback 函數的部分

第一篇文章會說明callback函數的原理
第二篇文章為實際範例,在dll內掛載全局的鍵盤滑鼠的鉤子,並註冊到user event
這個程式可以讓user event回傳鍵盤滑鼠的資料,不論LabVIEW程式是否為當前視窗都可以,也就是說即使LabVIEW程式被最小化或是隱藏在桌面右下角的圖示,程式還是可以收到鍵盤滑鼠的資料。再來是攔截usb裝置插入/拔出的事件並註冊到user event。不知道為什麼網路上都沒有相關的程式碼資料,難道大家都是用polling偵測usb裝置的有無嗎?用了這個程式後未來靠它就可以知道甚麼時候裝置被插上或拔除了

何謂callback函數呢?
詳細的說明可以參考這篇
實作可以參考這篇
基本上就是我們除了可以用callback讓程式不需要藉由polling的方式等待某些函數的處理,而是等到函數處理完畢後發觸發設定的callback函數然後讓主線程(通常是UI Thread)來處理
例如windows Message傳遞訊息的機制或是driver處理處理與硬體的通訊,都不希望占用到程式執行時需要polling這些"事件",程式需要就去向系統或被呼叫的程式去"註冊"這些事件,一旦發生就轉到註冊好的函數裡執行

在LabVIEW裡的event structure就有點類似這個架構,在LabVIEW裡處理按鈕狀態改變(on cgange),可以靠while loop去polling這個訊息,也可以向event structure去註冊這個訊息,一旦發生就跳到對應的"event程式碼"裡面去處理。按鈕的事件再event程式碼帶出OldVal/NewVal等等按鈕的資料,callback函數也可以帶出函數觸發時需要被處理的參數,因此vc下callback函數的宣告會類似 int (WINAPI *PFCALLBACK)(int Param1,int Param2) ; Param1和Param2就是附帶的相關參數

基於以上所述我認為LabVIEW中最適合wrapper callback函數(所以還有其他方式可以wrapper )接下來教學的內容因此以event structure來wrapper callback

附帶一提,LabVIEW內 Call Library Function Node 裡面的 Callbacks跟本篇文章所談的callback一點關西都沒有

針對這個callbacks的說明參考下面連結:

其中的敘述如下:

Configuring Callbacks

When you configure a Call Library Function Node to call a function, you can use the Callback tab to specify other functions within the same library to call at the following times:
  • Reserve time—When the top-level VI that causes the Call Library Function Node to execute begins executing. Specify a Reserve callback when you need to perform initialization tasks before the primary function executes.
  • Unreserve time—When the top-level VI that caused the Call Library Function Node to execute stops executing. Specify an Unreserve callback when you save or analyze information or carry out clean-up operations.
  • Abort—The specified function executes if the VI that called the primary function aborts.
與就是說如果你想為LabVIEW寫專門的dll,可以寫三個函數Reserve()、Unreserve()、Abort()然後再Callbacks這邊指定,這樣LabVIEW在執行最上層程式碼的時候會自動在開始執行時的初始化階段呼叫Reserve(),在LabVIEW在執行最上層程式碼結束時呼叫Unreserve(),在按下Abort按鈕時執行Abort()所以一般dll應該是沒有機會用上的



[原創] LabVIEW wrapper Callback from dll (2) Example:Keyboard,Mouse,USB event callback

本篇為延續前一篇的內容,進行Callback函數的實作

在LabVIEW中若想要使用dll來註冊Event,dll必須加入extcode.h這個檔頭hextcode.h位於..\National Instruments\(LabVIEW 版本)\cintools目錄下。它定義了CIN和外部程序所用到的基本數據類型和許多函數等。


首先先列出要完成的函數:


// dll 給 LabVIEW呼叫來安裝Mouse Hook的函數
MgErr __declspec(dllexport) InstallMouseHook(LVUserEventRef *value);
// dll 給 LabVIEW呼叫來解除Mouse Hook的函數
MgErr __declspec(dllexport) UnInstallMouseHook(void);

其中LVUserEventRef  * 就是LabVIEW中Event reference傳給dll的資料型態。
MgErr 是NI定義在extcode.h檔頭的錯誤訊息。
實作程式碼非常單純:


MgErr __declspec(dllexport) InstallMouseHook(LVUserEventRef *value)


{

 MgErr bSuccess = noErr;

 userRec.MSEevent = *value;

 if (!ghMouseHook)

 {

  gPreMouseProc = (HOOKPROC) MouseHookProc ;

  bSuccess = (NULL != (ghMouseHook = 

   SetWindowsHookEx (WH_MOUSE_LL, gPreMouseProc,

   g_hInst, NULL)));

 }



 if(bSuccess)

  return noErr;

 else

  return mgArgErr;



}



MgErr __declspec(dllexport) UnInstallMouseHook(void)

{

 if(ghMouseHook != NULL)

 {

  if(UnhookWindowsHookEx(ghMouseHook))

  {

   ghMouseHook=NULL;

   return noErr;

  }else

   return mgArgErr;

 }

 else

  return fNotFound;



}



userRec是用來存放LVUserEventRef  的結構,除此之外都是標準掛載Hook的程式碼。 接下來就是主要提到的Callback函數:
typedef struct tagDataRec {

 LONG lParam;

 LONG wParam;

} DataRec, *DataRecPtr;



LRESULT CALLBACK MouseHookProc 

(int nCode, WPARAM  wParam, LPARAM lParam)

{

 DataRec returnData;

 PMSLLHOOKSTRUCT pmll = (PMSLLHOOKSTRUCT) lParam;

 returnData.lParam=(pmll->pt.x<<16 )+ pmll->pt.y;

 returnData.wParam=wParam;

 PostLVUserEvent(userRec.MSEevent,&returnData);

 return CallNextHookEx (ghMouseHook, nCode, wParam, lParam);



}



DataRec是用來存放當LabVIEW的Event Structure觸發到這個自訂的滑鼠事件時收到的資料型態。程式很單純,把滑鼠座標塞到lParam,把滑鼠按鍵狀態存到wParam,然後透過定義在extcode.h檔頭的函式PostLVUserEvent就將這個事件的消息傳到LabVIEW程式的Event Structure訊息回圈內,一旦程式執行到Event Structure,程式碼就得到這個事件的結果。 LabVIEW端程式碼如下:
範例程式碼內除了攔截滑鼠的資料外,還有鍵盤與USB裝置插拔的資料。


2013年11月27日 星期三

[原創] LabVIEW code security: Password Protect VI v.s. Removing Block Diagram VI

LabVIEW 開發環境提供了兩種方式保護程式原始碼的安全性 , 一個是設定VI的密碼.另一個是移除VI的Block Diagram ,也就是VI的程式邏輯部分.
NI官方網站的說明也非常詳細  可以參考"Security of LabVIEW VI Password Protection vs. Removing VI Block Diagrams" , 雖然此篇文章並沒有明確指出Password Protect VI加密的演算法類型 , 但經實驗已確定是使用了MD5 (Wiki MD5)的加密 , MD5目前已被證實可經由碰撞而產生另一組解 , 因此並非完美的加密方式 , 官方文章也提到 Password Protect VI 的加密流程也可能因LabVIEW本身程式碼被逆向工程,跳過了判斷驗證密碼的程式導致即使加密,卻被輕鬆破解的結果.

因此NI提供了 Removing Block Diagram VI的方式 ,讓該VI 只保存人機介面與編譯過的Binary Code.此方式仍被破解的可能性只有對方能解讀該Binary Code在該平台(x86 ...etc) 對應的邏輯,因效益太低可能性微乎其微,因該不會有人會想做這種事情.此方式的缺點是該VI僅能提供同樣的LabVIEW版本與使用的平台環境,例如 abc.vi是在LabVIEW 2012,x86 Win7底下進行Removing Block Diagram後, abc.vi未來只能在LabVIEW 2012,x86 Win7的環境下被呼叫使用 , 因此若要支援多個版本,則須提供相對應的abc.vi.

Removing Block Diagram VI的流程如下:

1.建立一個vi,範例用一個簡單的加法運算,並加入專案.

2.在專案的Builed Specifications項目右鍵選擇New,然後再選擇Source Distribution.

3.在Source File選項中將目標abc.vi設為Always Included.

4.在source File Settings選項中點到abc.vi , 然後在右邊選單會有個Remove block diagram , 把這個選項打勾.
(後面會再多做一個連Remove front panel打勾的比較)

5. 之後點 Build 按鈕後就會生成不含Block Diagram的vi了. 由上圖可以比較一下
abc.vi為原始vi...... 13KB
abc_RemovingBlackDiagram.vi 為移除Block Diagram.......6KB
abc_RemovAnything.vi 為移除Front Panel與Block Diagram...... 3KB

6.將移除Block Diagram的vi拿到另外一台電腦上用LabVIEW 2013的環境打開就會跳出這個畫面 , 底下紅色框框裡顯說Block Diagram已被移除,因此LabVIEW 2013無法進行轉換.

接著用之前文章 提到的程式稍微修改一下,可以讀取儲存vi的每個部分,簡單說明幾個部分的名字代表的意義(猜的), 
VICD = VI compiled Data , 這是該vi程式碼compile 成machine code的資料.
BDPW = Block Diagram PassWord , 這是該vi被設 Password Protect VI後的密碼生成MD5存放的位置 , 前32 byte 就是該vi 被設密碼後的MD5值 , 若該值為"D41D8CD98F00B204E9800998ECF8427E"代表該vi並未被加密. 後面64 byte可能用來計算vi資料是否遭竄改或毀損(猜的)
FPHb , FPSE 為Front Panel 資料 , 詳細意義不明
BDHb , BDSE 為Block Diagram資料 , 詳細意義不明


上面兩張圖是 abc.vi與 abc_RemovingBlackDiagram.vi 的比較
可以看到VICD並未改變, BDHb , BDSE都被移除.

上面這張圖是 abc_RemovAnything.vi 與 abc_RemovingBlackDiagram.vi 的比較
可以看到VICD並未改變, FPHb , FPSE和其他一些東西都被移除.
結論:
1.Password Protect VI 用MD5加密程式碼.
2.Removing Block Diagram VI無法還原成可編輯的vi
3.就保護程式碼而言Removing Block Diagram VI比Password Protect VI來的安全 , 但使用此vi進行開發非常不方便.

2013年11月13日 星期三

[原創] Constant inside/outside loops

測試For Loop內若使用到常數時,將常數移出迴圈外,程式執行效能是否有所改變.

編譯環境為:
LabVIEW 2013 (32 - bit)
Windows 8 64-bit


第一個程式的比較非常單純 , 迴圈轉1000次,每次累加20,差別只有20放回圈外面或裡面


直接比較binary,可以看出LabVIEW compiler後的machine code是有差別的

看起來差在記憶體位置的位移,使用不同的register做某種資料的運算 , 至於是甚麼運算就去沒有深入了解.到此為止的運算上所需時間是一樣的.



真正的主角是在後面這段 , 迴圈轉1000次 , 每次累加20 (14H) , 比較是否少於1000次,若成立就繼續做
PS. jl (jump on less than, signed)


第二個程式的比較稍微 , 迴圈轉1000次,每次累加20,多做其他運算

Binary的比較,可以看出跑的code size沒變 , 執行運算時間看來是一樣的.


列出所有差異的部分


結論:為了程式碼美觀,常數就放在迴圈裡面吧


2013年11月11日 星期一

[原創] Consider constants for index array...

撰寫 LabVIEW 程式時,若用Index array時常常會拉一堆Index的常數, 這是否會對LabVIEW效能造成影響?

在編譯環境為:
LabVIEW 2013 (32 - bit)
Windows 8 64-bit

比較LabVIEW compiler以下6個程式碼編譯結果的差異


1,2的差別在連接線的常數

3,4的差別是計算和的順序

5,6的差別是,5含有沒用到的常數

比較compile完後各組的machine code差異,差異如下
1與2完全一樣 ,以後拉一堆常數也不用在意了
另外比較一下1和3,看看Index值有改變會怎麼樣,code size長度一樣所以計算時間應該是一樣的,下面有些零散的紅點只是address的變化
3與4改變計算和的順序,大概也是5~7行assembly指令的差別
5與6只差在5的Block Diagram多了不必要的常數加法乘法, LabVIEW compiler果然會無視掉,以後修改程式時可以把舊的算是留著不接,反正沒影響 ...
結論:
Index Array 的常數看來拉多拉少是沒有影響的,程式碼中沒有用到的常數運算看起來也會被編譯器無視掉.

PS.事實上也有另外做過常數擺在迴圈內外的比較,結果是有差異但是編譯後的機械碼長度記得也是一樣的.