PowerBuilder 9開發技術講座-PowerBuilder Native Interface(PBNI)

PowerBuilder 9現在對於其他開發語言的支援,有了全新的突破,在以往使用PowerBuilder開發程式時,要和C++或是Java程式互通有無是有一些折衷的辦法,但是總是沒有辦法做到簡易而且全面性的支援。現在只要透過PowerBuilder 9的PBNI技術,就可以讓PowerBuilder的程式呼叫Java,或是在一個C++的程式中引用PowerBuilder NVO物件函數。

 

以往的PowerBuilder程式只能夠透過外在函數呼叫的方式來存取C/C++的函數,但在PowerBuilder 9.0之中擴增了一項強而有力的介面-「PowerBuilder Native Interface」,簡稱PBNI。透過PBNI的開發方式,PowerBuilder開發人員不僅可以使用物件導向的方式來存取C/C++函數,而且還可反向地讓C/C++程式呼叫PowerBuilder之中的物件,達到應用程式的整合。更甚者,在藉由JNI與PBNI兩者的結合,Java應用程式也可雙向地與PowerBuilder程式溝通。

 

在談什麼是PBNI之前,我們先來談談下面三個問題: 
1.開發人員有辦法用PowerBuilder程式呼叫C或是C++的程式嗎? 
2.開發人員有辦法用PowerBuilder程式呼叫一些外部元件像是Java EJB元件、 Web Service元件、Java Class程式等諸如此類的元件嗎? 
3.開發人員有辦法用反過來,用C或是C++呼叫已經使用PowerBuilder開發好的程式嗎?

 
上述三個問題,在過去的PowerBuilder其實都可以做到某種程度的地步,只是都有些問題。傳統上使用PowerBuilder開發上述的程式時,如果要呼叫C或是C++ 的程式,是可以使用宣告外部函數的方式來使用一個已經撰寫好的DLL函數, 

 

例如: 
FUNCTION ulong GetSysColor (int index) LIBRARY "USER32.DLL” 
FUNCTION boolean sndPlaySoundA (string SoundName, uint Flags) LIBRARY "WINMM.DLL"


可是如果是下面的程式呢: 
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM, lParam); 
這個可是個大問題了,因爲這個Windows DLL Function中會用到所謂的「Callback」 函數的技術,所謂的Callback Function指的是今天有A和B兩個物件,在程式中A物件呼叫B物件的Function,而在該B物件的Function又會回頭呼叫A物件的其他Function,這就叫「Callback」。在PowerBuilder呼叫C的Function後,在這個C的Function中要再回頭呼叫PowerBuilder的函數是不可能用引用外部函數的方式來達到這個目地的。除了Callback Function使用困難之外,使用外部函數也有資料型態的限制,以及沒有辦法使用物件導向的方式開發等種種的困難及問題。 


再來談談PowerBuilder呼叫外部的元件的方法,在以前能夠讓PowerBuilder呼叫EJB元件,就只能透過一些協力廠商開發的「COM Bridge」,讓PowerBuilder程式透過COM元件來呼叫Java程式。至於要讓Java或是C++來呼叫PowerBuilder 程式的話,過去最常見的方法就是把這個PowerBuilder的程式包裝成爲「OLE automation server」。這些方法都不是一個真正解決的好方法,說穿了,這些方法
跟本就沒有辦法直接和PowerBuilder的核心「PowerBuilder Visual Machine」做溝通,所以在過去的版本的PowerBuilder,是一直有這種和其他語言程式不能溝通的困擾,這也是大家一直認爲,PowerBuilder是一個封閉不開放的開發工具。 PowerBuilder 9這個版本有幾個突破性的技術,而PBNI就是其中一個。所謂的PBNI (PowerBuilder Native Interface),指的是PowerBuilder提供一個「原生介面
(Native Interface)」,透過這個介面可以使得PowerBuilder提高了對其他程式語言的擴展能力,比方說透過該介面可以存取任何類型的外部應用應用程式,或是讓外界其他的程式語言存取或是呼叫PowerBuilder開發的程式,下面是一個簡單的PBNI的示意圖: 

此主題相關圖片如下:


在上面這張圖中,PBNI提供了兩道讓外界可以和PowerBuilder核心(PBVM)的介面窗口,第一個對外的窗口是指在圖的右半邊,我們可以開發「PB Extension」, PB Extension其實最後會變成DLL,透過該技術,C或是C++的DLL程式可以包裝成爲一個「PBD」的檔案,而該PBD的檔案就可以在開發程式時,加到Library Search Path中,讓PowerBuilder直接存取PBD裏的物件函數,你可以把它當作是一個很像PowerBuilder NVO的東西來對待它。第二個對外的窗口是指在圖的左半邊,你可以把PowerBuilder Virtual Machine 「內嵌」到一個C++的應用程式中,在C++程式中就可以直接呼叫PowerScript Function。


PBNI的元素


PBNI提供了一些基本的元素,透過這些元素,程式開發人員可以快速的引用外部程式語言,下面是常見到的PBNI元素: 
PBNI提供的介面(Interface): 
IPB_VM:這個介面的作用,在於當你要用C++或是其他的程式語言來呼叫PowerBuilder開發的程式,或是你希望要和PowerBuilder的核心「PBVM」進行互動,或是溝通協調,你可以使用這個介面。 
IPB_Session:這是一個抽象的介面,這個介面可以用來定義諸如存取PowerScript裏面的資料、建立PowerBuilder物件和呼叫PowerScript函數操作的方法
IPB_Value:這個介面你可以把它想像成是它就是代表PowerBuilder的值。這些值可以是PowerBuilder的標準資料型態,例如String、Long、Integer、Char等等。所以這個介面提供了關於每個變數的資訊,包括變數的類型、標記、存取權限(Public、Private和Protected)、變數值或參數存取方式(例如Call by Value或是Reference)。 
IPB_Arguments:這個介面可以讓使用者在PowerBuilder VM和「PB Extension」間傳遞參數。 
IPBX_NonVisualObject和IPBX_VisualObject:這兩介面很意思,因爲它們可以在C++程式中實作出來,而且是放在PB Extension裏面,你在PowerBuilder中就可以用PBD的方式看到你實作出來的物件,而要寫這些可見或是不可見的物件,靠的就是IPBX_NonVisualObject和IPBX_VisualObject介面。 
IPBX_Marshaler:這個介面是當你要出一個「PB marshaler extension 」時, 一定要實作出IPBX_Marshaler這個`介面。這個介面尤其是你要由PowerBuilder呼叫Java程式時,一定要用到的一個介面。 

PBNI提供的Structures: 
PBCallInfo:這個Structure可以在開發PBNI程式時,讓PBNI和PowerBuilder之間呼叫的函數保持參數和回傳值的資訊。如果要存取在
PBCallInfo中的資訊,可以使用IPB_Arguments介面來獲得PBCallInfo。 
PBArrayInfo:PBArrayInfo是一個C++的structure,這個Structure可以在陣列中保持一些資訊。 
PBNI提供的Globle Function: 如果你要寫一個PowerBuilder extension的程式(說穿了就是用C++寫一個DLL檔啦),這個物件必須要匯出兩個Global Functions,讓這個程式可以「內嵌」 PowerBuilder VM並且建立實體出來。下面是PBNI提供的Globle 
Function: 
PBX_GetDescription() 
PBX_CreateNonVisualObject() 
PBX_CreateVisualObject() 
PBX_InvokeGlobalFunction() 
PBNI提供的Helper classes: 
Helper Classes指的是一些輔助的類別物件,PBNI提供像是PBObjectCreator、 PBArrayAccessor和PBEventTrigger等輔助類別,透過這些輔助類別物件可使PBNI在開發上更簡單。


PBNI的開發方式


在瞭解PBNI有那些元素後,讀着應該也瞭解到何謂PBNI,並且知道PBNI能幫我們做什麼。在針對不同的目地,PBNI也有不同的開發方式,常見的PBNI開發目地爲下列四個,在後面的部份會祥細的說明PBNI的開發方式爲何: 
建立PB extensions 
建立PB marshaler extensions 
建立PB visual extensions 
內嵌PBVM到C++的應用程式中

 

之前有跟各位讀者提過,PBNI提供了兩個對外的方法,其中一種就是將C或是C++寫好的DLL檔案,透過PBNI提供的介面來包裝成一個PowerBuilder認得的PBD檔案,這種方式稱之爲建立「PB Extensions」。在開發一個PB Extensions的程式時,我們必須先設想好,最後我們要產生的PBD中,會有那些物件。比方說,我現在手頭上正在寫一個C++的程式,我希望這個C++的程式最後透過PBNI 
的幫助,產生一個PBD檔案,而且在這個PBD裏面有一個Funtion物件,而這個Function物件會對照於我在C++裏面寫好的Function,讓我只要呼叫該Function 物件,就等於是執行C++裏的程式。剛纔的設想中,開發的步驟如下: 
1.使用C++的開發工具建立一個C++專案。 
2.在C++的程式中,匯入PBNI SDK提供給C++的相關表頭檔(h檔案)。 
3.在C++的程式中,透過PBX_GetDescription()這個PBNI提供的函數,告知到時後會匯出一個Globle Function。 
4.因爲要做的是一個Globle Function物件,所以在C++的程式中,透過PBX_InvokeGlobalFunction()這個PBNI提供的函數,實作該Function的程式出來。 
5.將開發好的C++程式編輯成DLL檔。 
6.透過PBNI提供的「pbx2pbd90.exe」小工具,將這個DLL檔案轉換爲PBD檔。 
7.打開PowerBuilder後,將這個PBD檔加到Library Search Path中。 
8.開發相關的PowerBuilder程式,並且呼叫這個PBD檔的Globle Function。


定義要匯出的物件類別


在上述的步驟中,PBX_GetDescription()這個PBNI提供的函數是一定要有的,因爲這個函數是用來產生相關的類別定義,而這個類別定義最後會在將DLL檔案轉換成PBD檔時,跟據你在PBX_GetDescription()函數中的定義,產生相對應的PowerBuilder物件。下面在C++的程式,最後會產生一個PB的Globle Function物件,這個Globle Function物件名稱是GetUserName(),而它的回傳值是String資料型態: 
PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription() 

static const TCHAR desc[] = { 
"globalfunctions /n" 
"function string GetUserName()/n" 
"end globalfunctions /n" 
}; 

return desc ; 

再舉一個例子,下面寫在C++裏的PBX_GetDescription()函數程式,最後會產生一個PowerBuilder的可視物件「flagext」,在這個可視物件中,有兩個事件爲分別爲「onclick」Event和「ondoubleclick」 Evnet;在可視物件中,還有兩個物件Function 「settext(string txt)」和「setflag(int flag)」: 
PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription() 

static const TCHAR desc[] = { 
"class flagext from usero b j e c t/n" 
"event int onclick()/n" 
"event int ondoubleclick()/n" 
"subroutine settext(string txt)/n" 
"subroutine setflag(int flag)/n" 
"end class/n" 
}; 
return desc; 
}


實做類別物件程式碼


之前提到的步驟中,除了PBX_GetDescription()之外,我們還會步驟四中看到一個PBNI提供的Function,叫PBX_InvokeGlobalFunction(),這是因爲我們要實作出Globle Function的程式,所以就必須要使用PBX_InvokeGlobalFunction()函數;相同的道理,如果在PBX_GetDescription()中我們準備建立的是一個NVO物件,那就要用PBX_CreateNonVisualObject()函數實作出NVO物件的程式;如果在PBX_GetDescription()中我們準備建立的是一個可視的PowerBuilder物件,那就要用PBX_CreateVisualObject()函數實作出這一個可視物件的程式。下面是一個在C++中使用PBX_InvokeGlobalFunction()來實作出一個Globle Function程式的例子: 
PBXEXPORT PBXRESULT PBXCALL PBX_InvokeGlobalFunction 

IPB_Session* pbsession, 
LPCTSTR functionName, 
PBCallInfo* ci 


if ( strcmp( functionName, "getusername" ) == 0 ) 

CWinAPI *WinAPI = new CWinAPI( pbsession ) ; 
WinAPI->PBNIGetUserName ( ci ) ; 
if ( WinAPI != NULL ) delete WinAPI ; 
return PBX_OK ; 
} ; 
return PBX_E_NO_SUCH_CLASS ; 

在上面的程式碼中,讀者可以發現,程式PB Extension和PB的核心PBVM的溝通是透過IPB_Session這個指標變數來保持C++和PowerBuilder程式的連結,而在PBNI和PowerBuilder之間呼叫的函數保持參數和回傳值的資訊就是透過之前介紹的PBCallInfo這個指標結構來保存,下面是PBVM和PB Extension之間的關係示意圖: 

此主題相關圖片如下:


下面是完整的程式碼,這個程式碼中,在pbniwinapi.cpp程式實作出類別「CWinAPI」,在這個Class中,有一個PBNIGetUserName()函數會透過Windows 作業系統的API取得電腦的使用者名稱,而main.cpp中會匯出一個PB Globle Function叫「GetUserName()」: 
pbniwinapi.cpp 
#include <WINDOWS.H> 
#include <stdio.h> 
#include "PBNIWINAPI.h" 
CWinAPI::CWinAPI( IPB_Session * pSession ) 
: m_pSession( pSession ) 


CWinAPI::~CWinAPI(void) 


void CWinAPI::PBNIGetUserName ( 
PBCallInfo *ci ) 

LPTSTR lpszSystemInfo; 
DWORD cchBuff = 256; 
TCHAR tchBuffer[1024]; 
lpszSystemInfo = tchBuffer; 
GetUserName ( lpszSystemInfo, &cchBuff) ; 
ci->returnValue->SetString ( lpszSystemInfo ) ; 

void CWinAPI::Destroy() 

delete this ; 

main.cpp 
#include <windows.h> 
#include <pbext.h> 
#include "pbniwinapi.h" 
BOOL APIENTRY DllMain( 
HANDLE hModule, 
DWORD reasonForCall, 
LPVOID lpReserved 


switch( reasonForCall ) 

case DLL_PROCESS_ATTACH: 
case DLL_THREAD_ATTACH: 
case DLL_THREAD_DETACH: 
case DLL_PROCESS_DETACH: 
break; 

return TRUE; 

PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription() 

static const TCHAR desc[] = { 
"globalfunctions /n" 
"function string GetUserName()/n" 
"end globalfunctions /n" 
}; 
return desc ; 

PBXEXPORT PBXRESULT PBXCALL PBX_InvokeGlobalFunction 

IPB_Session* pbsession, 
LPCTSTR functionName, 
PBCallInfo* ci 


if ( strcmp( functionName, "getusername" ) == 0 ) 

CWinAPI *WinAPI = new CWinAPI( pbsession ) ; 
WinAPI->PBNIGetUserName ( ci ) ; 
if ( WinAPI != NULL ) delete WinAPI ; 
return PBX_OK ; 
} ; 
return PBX_E_NO_SUCH_CLASS ; 
}


產生PowerBuilder延伸物件


在上面的C++程式完成後,便可以編輯一個DLL檔案,可是這個DLL檔案並不是要讓PowerBuilder直接拿來用,因爲這樣子一來,就又回到使用外部函數的做法,比較好的方式是要產生PB Extension,也就是說把這個DLL「再包一層」,用一個PBD幫這個DLL檔案做一個PowerBuilder看的懂的外皮,然後讓PowerBuilder透過PBD來「認識」你寫好的DLL程式。 在PowerBuilder9安裝目錄下%Sybase9%/PowerBuilder 9.0/SDK/PBNI,你會發現有一個叫「pbx2pbd90.exe」的檔案,如果要幫DLL再包一層PBD檔案,必須透過pbx2pbd90.exe這一個檔案,它的語法如下: 
pbx2pbd90 your.pbd your.dll 
比方說有一個DLL檔案叫「pbniwinapi.dll」,要把這個檔案轉成PBD檔,就可以這樣子下: 

pbx2pbd90 pbniwinapi.pbd pbniwinapi.dll 
如此一來,它會產生「pbniwinapi.pbd」檔,並且根據你原先寫在C++程式中的PBX_GetDescription()函數內容,在這個PBD檔案產生出相對應的PowerBuilder 可以認得的物件,讓開發人員取得該物件後,用PowerBuilder原生語法PowerScript就可以呼叫該物件的函數來做事情。 


下圖是一個產生PBD檔案的示意圖:


此主題相關圖片如下:

PowerBuilder使用PB Extension開發程式


產生好PB Extension後,讀者一定很好奇兩件事,第一件事就是我可不可以只要用產生好的PBD物件,而把原來的DLL檔案刪除?答案是不行,因爲PBD物件只是一個幫DLL檔案產生出來的一個「空殼」,藉由這個PBD空殼,PowerBuilder 才行使用DLL程式;第二個問是就是使用PB Extension的步驟爲何?其實使用PB Extension很簡單,只要把它當作是一般的PowerBuilder程式物件來用就可以
了,下面是它的使用步驟: 
1.將產生好的.PBD檔案加到你的PowerScript Target,也就是把這個PBD檔案加到Library Search Path中。 
2.將PB extension DLL檔案拷貝到開發程式目錄下面

3.使用PowreScript語法呼叫物件的函數假設現在有一個PB Extension檔案加到Library Search Path中,在這個PBD裏面有一個NVO物件叫「SimpleExt」,在該物件有一個「hello()」函數,在PowerBuilder的程式中,就會這樣子寫: 
SimpleExt ext 
ext = create SimpleExt 
String str 
Str = ext.hello( “Hello, what’s your name? ”) 
Messagebox( “hello ”, str); 
下面是這次呼叫PB Extension程式的流程示意圖:


此主題相關圖片如下:

在一開始的時後提到,PBNI提供了兩扇對外的門戶,一個是可以將C或C++的程式轉成PB Extensions,也就是轉成PBD的方式;另一個對外的門戶是可以在C++程式中「內嵌PBVM」。關於第一種方法在上面介紹過了,而接下來就是要介紹第二種對外的門戶,內嵌PBVM。 

相信讀者在開發其他的語言程式時,一定時常會有一個希望:「啊,如果我的Java 程式可以使用DataWindow物件就好了。 」 ;或是「上次的專案我已經用PowerBuilder開發好一些模組,在這次的C++專案中,真不想再寫一次。 
如果讀者有這種需求,這時後最好的方式,就是使用「內嵌PBVM」的作法,透過這個PBNI的技術,C++的程式也可以順利的呼叫PowerBuilder開發好的物件,具體的開發方式爲: 
1.在C++程式中載入PBVM。 
2.在C++程式中利用IPB_VM介面來取得C++和PB的連結。 
3.建立該PBL或是PBD的Library Session(其實也是透過IPB_Session介面)。 
4.在C++裏面建立這個NVO物件的實體。 
5.呼叫這個NVO物件的Function。 


舉例來說,我有一個「trypbni.pbl」,在這個PBL檔案中,有一個NVO物件叫作「n_ben」物件,在該物件中有一個foo()函數,現在在開發一個C++的程式時, 就可以把PBVM給內嵌到C++程式中,並且在C++程式呼叫這個n_ben.foo()函數,相關的的部份程式碼如下: 
trypbni.cpp 
1. int main(int argc, char* argv[]) 
2. { 
3. HINSTANCE hinst = LoadLibrary("pbvm90.dll"); 
4. P_PB_GetVM getvm = (P_PB_GetVM)GetProcAddress(hinst, "PB_GetVM"); 
5. IPB_VM* vm = NULL; 
6. getvm(&vm); 
7. static const char *liblist[] = { "trypbni.pbl" }; 
8. IPB_Session* session = NULL; 
9. vm->CreateSession("trypbni", liblist, 1, &session); 
10. pbgroup group = session->FindGroup("n_ben", pbgroup_usero b j e c t); 
11. pbclass clz = session->FindClass(group, "n_ben"); 
12. pbmethodID mid = session->GetMethodID(clz, "foo", PBRT_FUNCTION, "IS"); 
13. pbo b j e c t obj = session->NewObject(clz); 
14. PBCallInfo ci; 
15. session->InitCallInfo(clz, mid, &ci); 
16. ci.pArgs->GetAt(0)->SetString("Calling PowerScript from C++"); 
17. session->InvokeObjectFunction(obj, mid, &ci); 
18. session->FreeCallInfo(&ci); 

19. session->Release(); 
20. FreeLibrary(hinst); 
21. return 0; 
22. } 
讀者可以在第三行發現在C++要「內嵌」PBVM,會使用LoadLibrary()這個函數把pbvm90.dll加到C++程式中,在第七行指定要使用trypbni.pbl;在第十一行找到n_ben物件;在第十二行呼叫n_ben.foo()函數。在完成上面的程式後,就可以把這支C++程式編輯,變成一支可以呼叫PowerBuilder物件的程式。 如果是要在Java中呼叫PowerBuilder的程式呢?這比較麻煩一點,簡單的來說,
還是要用到「內嵌」PBVM的技術,可是現在有一個問題,就是理論上,Java 是沒有辦法內嵌PowerBuilder的程式,如此一來,想要讓Java直接呼叫或使用PowerBuilder的程式是有點難度的。關於這一點,其實可以用Java呼叫C++程式的技術「Java JNI」方式達到我們的目地,也就是說,首先可以先用C++寫一個呼叫PowerBuilder的DLL程式,當然,這支程式一定是用PBNI的「內嵌」 PBVM技術做出該程式,接着再用「JNI」的方式,讓Java呼叫C++的DLL程式。


結論


PowerBuilder Native Interface,這個PowerBuilder9功能強大的新程式設計介面, 大大的改變了世人對PowerBuilder的認知,透過PBNI的支援,可將原來PowerBuilder應用程式的功能,延伸到C++及Java應用程式中,爲這些程式開啓新的世界和市場。尤其是在這個資訊以倍速成長的競爭環境下,企業如何能一方面保有原來的投資,另一方面又可讓系統更具擴充性和延展性,而且兼具高效能和高生產力,相信市場領導開發工具—PowerBuilder已經爲這樣子的需求做了最好的解釋。

發佈了32 篇原創文章 · 獲贊 83 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章