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
在谈什么是 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 元素:
z 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 程序时,一定要用到的一个接口。
z PBNI 提供的 Structures:
PBCallInfo:这个 Structure 可以在开发 PBNI 程序时,让 PBNI 和 PowerBuilder 之间呼叫的函数保持参数和回传值的信息。如果要存取在 PBCallInfo 中的信息,可以使用 IPB_Arguments 接口来获得 PBCallInfo。
PBArrayInfo:PBArrayInfo 是一个 C++ 的 structure,这个 Structure 可以 在数组中保持一些信息。
z PBNI 提供的 Globle Function:
如果你要写一个 PowerBuilder extension 的程序(说穿了就是用 C++写一个 DLL 文件啦),这个对象必须要汇出两个 Global Functions,让这个程序可以「内 嵌」 PowerBuilder VM 并且建立实体出来。下面是 PBNI 提供的 Globle Function:
PBX_GetDescription()
PBX_CreateNonVisualObject()
PBX_CreateVisualObject()
PBX_InvokeGlobalFunction()
z 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 userobject/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 程序的流程示意图:
内嵌 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
int main(int argc, char* argv[])
{
HINSTANCE hinst = LoadLibrary("pbvm90.dll");
P_PB_GetVM getvm = (P_PB_GetVM)GetProcAddress(hinst, "PB_GetVM");
IPB_VM* vm = NULL;
getvm(&vm);
static const char *liblist[] = { "trypbni.pbl" };
IPB_Session* session = NULL;
vm->CreateSession("trypbni", liblist, 1, &session);
. pbgroup group = session->FindGroup("n_ben", pbgroup_userobject);
pbclass clz = session->FindClass(group, "n_ben");
pbmethodID mid = session->GetMethodID(clz, "foo", PBRT_FUNCTION, "IS");
pbobject obj = session->NewObject(clz);
PBCallInfo ci;
session->InitCallInfo(clz, mid, &ci);
ci.pArgs->GetAt(0)->SetString("Calling PowerScript from C++");
session->InvokeObjectFunction(obj, mid, &ci);
session->FreeCallInfo(&ci);
session->Release();
FreeLibrary(hinst);
return 0;
}
读者可以在第三行发现在 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 已经为这样子的需求做 了最好的解释。
本文介绍了PowerBuilder 9引入的PBNI技术,允许PowerBuilder程序与C++和Java之间进行对象导向的交互。PBNI解决了过去通过外部函数调用的限制,如回调函数和数据类型约束,现在可以创建PB扩展,让C++程序调用PowerBuilder对象,反之亦然。此外,通过JNI与PBNI结合,Java应用也能与PowerBuilder程序实现双向通信。PBNI包含接口、结构体、全局函数和辅助类,提供灵活的开发方式,如创建PB扩展、PB marshaler扩展、PB visual扩展以及在C++应用中内嵌PBVM。
955

被折叠的 条评论
为什么被折叠?



