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裝置插拔的資料。