PowerBuilder Native Interface(PBNI)

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
 

在谈什么是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的示意图:

此主题相关图片如下:1.jpg


在上面这张图中,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++的应用程式中


建立PB extensions步骤
 

之前有跟各位读者提过,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之间的关
系示意图:

此主题相关图片如下:2.jpg


下面是完整的程式码,这个程式码中,在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档案的示意图:


此主题相关图片如下:3.jpg


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程式的流程示意图:


此主题相关图片如下:4.jpg


内嵌PBVM


在一开始的时后提到,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程
式。


此主题相关图片如下:5.jpg


结论


PowerBuilder Native Interface,这个PowerBuilder9功能强大的新程式设计介面,
大大的改变了世人对PowerBuilder的认知,透过PBNI的支援,可将原来
PowerBuilder应用程式的功能,延伸到C++及Java应用程式中,为这些程式
开启新的世界和市场。尤其是在这个资讯以倍速成长的竞争环境下,企业如何能
一方面保有原来的投资,另一方面又可让系统更具扩充性和延展性,而且兼具高
效能和高生产力,相信市场领导开发工具—PowerBuilder已经为这样子的需求做
了最好的解释。

posted @ 2011-01-06 10:56  lenya  阅读(1303)  评论(0编辑  收藏  举报