一、序言
初步看了看PowerBASIC编程,很类似用VC注册窗体后调用回调函数,先是一个Dialog new,然后添加组件 Control add ......, 然后在处理 Windows MSG和发给组件的消息,这种编程方式和早期DOS 25x80屏幕上编程一样,要自己处理显示内容的位置和区域大小。
PowerBASIC还自带了一个Dlgedit.exe , 它是微软的 Dialog Editor,用于设计窗口和摆放组件。
同时,PowerBASIC带了RC.exe和PBRES.exe用于资源编译,但设计器是16位的,在Win10和Win11上要处理一下才能运行。处理的方法是到github下载 OTVDM,解压到C盘根目录,执行Install向注册表填加16位程序转向即可。安装有OTVDM的计算机,可以安装VB3, VB6, Delphi2, Delphi3, 而且窗口样式和现在的一样,界面倍加美观。
后来,有了PBFORMS,类似Linux上Glade的界面设计器,属性方法框架基本上在上面完成了。
在菜单上,VIEW, DDT 代码, 全部考贝粘到程序代码页,编译就能运行这个框架。
#PBFORMS CREATED V2.01
'------------------------------------------------------------------------------
' The first line in this file is a PB/Forms metastatement.
' It should ALWAYS be the first line of the file. Other
' PB/Forms metastatements are placed at the beginning and
' end of "Named Blocks" of code that should be edited
' with PBForms only. Do not manually edit or delete these
' metastatements or PB/Forms will not be able to reread
' the file correctly. See the PB/Forms documentation for
' more information.
' Named blocks begin like this: #PBFORMS BEGIN ...
' Named blocks end like this: #PBFORMS END ...
' Other PB/Forms metastatements such as:
' #PBFORMS DECLARATIONS
' are used by PB/Forms to insert additional code.
' Feel free to make changes anywhere else in the file.
'------------------------------------------------------------------------------#COMPILE EXE
#DIM ALL'------------------------------------------------------------------------------
' ** Includes **
'------------------------------------------------------------------------------
#PBFORMS BEGIN INCLUDES
#RESOURCE "noname1.pbr"
%USEMACROS = 1
#INCLUDE ONCE "WIN32API.INC"
#INCLUDE ONCE "COMMCTRL.INC"
#INCLUDE ONCE "PBForms.INC"
#PBFORMS END INCLUDES
'------------------------------------------------------------------------------'------------------------------------------------------------------------------
' ** Constants **
'------------------------------------------------------------------------------
#PBFORMS BEGIN CONSTANTS
%IDD_DIALOG1 = 101
%IDC_TEXTBOX1 = 1001
%IDC_TEXTBOX2 = 1002
%IDC_TEXTBOX3 = 1003
%IDC_TEXTBOX4 = 1004
%IDC_TEXTBOX5 = 1005
%IDC_BUTTON1 = 1006
%IDR_IMGFILE1 = 102 '*
#PBFORMS END CONSTANTS
'------------------------------------------------------------------------------'------------------------------------------------------------------------------
' ** Declarations **
'------------------------------------------------------------------------------
DECLARE CALLBACK FUNCTION ShowDIALOG1Proc()
DECLARE FUNCTION ShowDIALOG1(BYVAL hParent AS DWORD) AS LONG
#PBFORMS DECLARATIONS
'------------------------------------------------------------------------------'------------------------------------------------------------------------------
' ** Main Application Entry Point **
'------------------------------------------------------------------------------
FUNCTION PBMAIN()PBFormsInitComCtls (%ICC_WIN95_CLASSES OR %ICC_DATE_CLASSES OR _%ICC_INTERNET_CLASSES)ShowDIALOG1 %HWND_DESKTOP
END FUNCTION
'------------------------------------------------------------------------------'------------------------------------------------------------------------------
' ** CallBacks **
'------------------------------------------------------------------------------
CALLBACK FUNCTION ShowDIALOG1Proc()SELECT CASE AS LONG CB.MSGCASE %WM_INITDIALOG' Initialization handlerCASE %WM_NCACTIVATESTATIC hWndSaveFocus AS DWORDIF ISFALSE CB.WPARAM THEN' Save control focushWndSaveFocus = GetFocus()ELSEIF hWndSaveFocus THEN' Restore control focusSetFocus(hWndSaveFocus)hWndSaveFocus = 0END IFCASE %WM_COMMAND' Process control notificationsSELECT CASE AS LONG CB.CTLCASE %IDC_TEXTBOX1CASE %IDC_TEXTBOX2CASE %IDC_TEXTBOX3CASE %IDC_TEXTBOX4CASE %IDC_TEXTBOX5CASE %IDC_BUTTON1IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THENMSGBOX "%IDC_BUTTON1=" + FORMAT$(%IDC_BUTTON1), _%MB_TASKMODALEND IFEND SELECTEND SELECT
END FUNCTION
'------------------------------------------------------------------------------'------------------------------------------------------------------------------
' ** Dialogs **
'------------------------------------------------------------------------------
FUNCTION ShowDIALOG1(BYVAL hParent AS DWORD) AS LONGLOCAL lRslt AS LONG#PBFORMS BEGIN DIALOG %IDD_DIALOG1->->LOCAL hDlg AS DWORDDIALOG NEW hParent, "Dialog1", 191, 173, 184, 181, %WS_POPUP OR _%WS_BORDER OR %WS_THICKFRAME OR %WS_CAPTION OR %WS_SYSMENU OR _%WS_MINIMIZEBOX OR %WS_MAXIMIZEBOX OR %WS_VISIBLE OR %DS_SYSMODAL OR _%DS_CENTER OR %DS_3DLOOK OR %DS_NOFAILCREATE OR %DS_SETFONT, _%WS_EX_CONTROLPARENT OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR _%WS_EX_RIGHTSCROLLBAR, TO hDlgCONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX1, "TextBox1", 40, 25, 70, 15CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX2, "TextBox2", 40, 55, 105, 15CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX3, "TextBox3", 40, 75, 105, 15CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX4, "TextBox4", 40, 95, 105, 15CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX5, "TextBox5", 40, 115, 105, 15CONTROL ADD BUTTON, hDlg, %IDC_BUTTON1, "Button1", 40, 145, 60, 15
#PBFORMS END DIALOGDIALOG SHOW MODAL hDlg, CALL ShowDIALOG1Proc TO lRslt#PBFORMS BEGIN CLEANUP %IDD_DIALOG1
#PBFORMS END CLEANUPFUNCTION = lRslt
END FUNCTION
'------------------------------------------------------------------------------
感觉这些东西挺不错的,代表着一种不同VB6的实现思想。PowerBASIC是32位编译器、没有64位编译器、现在已经停更了,但仍然在一些地方得到应用。PowerBASIC支持多线程,用它编写DLL体积小速度快。后来,又有好事者 FireFly 为它开发了 RAD 。
二、FireFly RAD powerBASIC
控件几十项,代码可以用PowerBASIC原装语句,也可以用它自己的语句,但编译器完全是PowerBASIC的,只是又做了一些封装。
这个习练程序是按如下步骤调用PowerBASIC COM对象的。
用FireFly带的 Type_lib browser 找到已经注册的COM对象
双击找到的COM对象,然后点击菜单上的Code
会自动生成Type library,将它们全部考贝到FireFly RAD中的一个模块中即可。
' ########################################################################################
' Library name: Prime
' Version: 1.0, Locale ID = 0
' Description: Prime
' Documentation string: Prime number detector
' Path: D:\PBASICWIN104\SAMPLES\OBJECTS\VB\PRIME.DLL
' Library GUID: {66098C79-60F3-44F5-B30C-845E1171E87B}
' Code generated by the TypeLib Browser 5.0.0 (c) 2011 by Jos?Roca
' Date: 20 9月 2023 Time: 18:35:24
' Options used to generate the code:
' - Use METHOD and PROPERTY GET/SET statements
' - Use parameter names in interface declarations
' - Use interface names with external objects
' - Include dispatch only interfaces
' - Retrieve license key
' - Use automation view
' ########################################################################################' ========================================================================================
' CoClass name: MATHCLASS
' ========================================================================================
' CLSID (Class identifier): {590B350A-D462-485E-A35C-40FACA1ABC16}
' Attributes: 2 [&H00000002] [Cancreate]
' ProgID (Program identifier): MATHCLASS
' Default interface: IPRIME
' Default interface IID: {19C9E340-205F-4AA6-A58F-A62179D0E26B}
' Default events interface: IPROGRESS
' Default events interface IID: {AB813267-178F-47A0-8EFC-CE0CCB06D359}
' Implemented interfaces:
' IPRIME (default interface)
' IPROGRESS (default events interface)
' ========================================================================================' ========================================================================================
' ProgIDs (Program identifiers)
' ========================================================================================' CLSID = {590B350A-D462-485E-A35C-40FACA1ABC16}
$PROGID_MATHCLASS = "MATHCLASS"' ========================================================================================
' ClsIDs (Class identifiers)
' ========================================================================================$CLSID_MATHCLASS = GUID$("{590B350A-D462-485E-A35C-40FACA1ABC16}")' ========================================================================================
' IIDs (Interface identifiers)
' ========================================================================================$IID_IPRIME = GUID$("{19C9E340-205F-4AA6-A58F-A62179D0E26B}")
$IID_IPROGRESS = GUID$("{AB813267-178F-47A0-8EFC-CE0CCB06D359}")' ########################################################################################
' Interface name = IPRIME
' IID = {19C9E340-205F-4AA6-A58F-A62179D0E26B}
' IPRIME is a custom interface for Direct VTable access.
' Attributes = 128 [&H00000080] [Nonextensible]
' Inherited interface = IUnknown
' #########################################################################################IF NOT %DEF(%IPRIME_INTERFACE_DEFINED)%IPRIME_INTERFACE_DEFINED = 1INTERFACE IPRIME $IID_IPRIMEINHERIT IAutomation' =====================================================================================METHOD ADDNUMBERS1 ( _ ' VTable offset = 12BYVAL NUM1 AS INTEGER _ ' [in] NUM1 VT_I2 <Integer>, BYVAL NUM2 AS INTEGER _ ' [in] NUM2 VT_I2 <Integer>, BYREF LRETURN AS LONG _ ' [in][out] *LRETURN VT_I4 <Long>) ' void' =====================================================================================PROPERTY GET ADDNUMBERS ( _ ' VTable offset = 16BYVAL NUM1 AS INTEGER _ ' [in] NUM1 VT_I2 <Integer>, BYVAL NUM2 AS INTEGER _ ' [in] NUM2 VT_I2 <Integer>, BYREF IN LRETURN AS LONG _ ' [in][out] *LRETURN VT_I4 <Long>) AS LONG ' VT_I4 <Long>' =====================================================================================PROPERTY GET ISPRIME ( _ ' VTable offset = 20) AS LONG ' [in] N VT_I4 <Long>' =====================================================================================END INTERFACE#ENDIF ' /* __IPRIME_INTERFACE_DEFINED__ */
全部考贝,一个字都不需要更改!
双击frmMAIN上Command1钮,试手操作它左边的Text1文本。在自动生成的框架上加一句:FF_Control_SetText(HWND_FrmMain_TEXT1,"中文" + "and English" ),运行程序后点击Command1,就会在Text1上显示 "中文 and English" 字样。这是在PowerBASIC原有的 Control Set Text hWnd, IDC, text 基础上封装的,调用方法基本上不变,但它们与 Text1.Text = "中文 and English" 是很大不同的。
'--------------------------------------------------------------------------------
Function FRMMAIN_COMMAND1_BN_CLICKED ( _ControlIndex As Long, _ ' index in Control ArrayhWndForm As Dword, _ ' handle of FormhWndControl As Dword, _ ' handle of ControlidButtonControl As Long _ ' identifier of button) As LongFF_Control_SetText(HWND_FrmMain_TEXT1,"中文" + "and English" )
End Function
再来看看一段代码,当程序运行后,将窗体置于屏幕中央。
先得到Desktop的尺寸,然后得到主窗体尺寸,相减后除以2作为窗体的top和left,窗体就在中央位置了。LanID可以是FF,也可以是PB,接着的判断根据FF或FB,决定用PowerBASIC和是FF的封装函数实现。两者样式差不多,实现的功能完全相同。
Function FRMMAIN_WM_CREATE ( _hWndForm As Dword, _ ' handle of FormByVal UserData As Long _ ' optional user defined Long value) As LongLocal nScreenX As Long, nScreenY As LongLocal nfrmMainWidth As Long, nfrmMainHeight As Long Local LanID As String Local hWndDeskTop As Dword hWndDesktop = GetDesktopWindow() '%HWND_DESKTOPLanID = "FF" 'PB for powerbasic statements, and FF for FireFly statementsIf LanID = "PB" ThenDesktop Get Size To nScreenX, nScreenY 'Get screen sizeDialog Get Size hWndForm To nfrmMainWidth, nfrmMainHeight 'Get frmMain sizeDialog Set LoC hWndForm, (nScreenX - nfrmMainWidth)/2, (nScreenY - nfrmMainHeight)/2 'Center frmMainElse FF_Control_GetSize hWndDesktop, nScreenX, nScreenY '%HWND_DESKTOP = hWndDesktop = 0FF_Control_GetSize hWndForm, nfrmMainWidth, nfrmMainHeightFF_Control_SetLoc hWndForm, (nScreenX - nfrmMainWidth)/2, (nScreenY - nfrmMainHeight)/2End If
End Function
最后是调用COM对象代码
IPRIME是Class的实现接口,在Class内部完成。在程序中 Local Glossary As IPRIME,Let Glossary = NewCom $PROGID_MATHCLASS 完成接口对象创建。原COM中用了Property GET和Method两种方式实现了AddNumbers,用Method方法要用 call 调用。参数回传要用 byref, 不能用byval,否则收不到COM的数据。
Function FRMMAIN_COMMAND2_BN_CLICKED ( _ControlIndex As Long, _ ' index in Control ArrayhWndForm As Dword, _ ' handle of FormhWndControl As Dword, _ ' handle of ControlidButtonControl As Long _ ' identifier of button) As LongLocal Glossary As IPRIMELet Glossary = NewCom $PROGID_MATHCLASS'If IsFalse(IsObject(Glossary)) Then Exit FunctionIf IsObject(Glossary) ThenLocal iParam1 As IntegerLocal iParam2 As IntegerLocal lReturn As LongiParam1 = 3: iParam2 = 5'Method using Property GETGlossary.AddNumbers(ByVal iParam1, ByVal iParam2, ByRef lReturn)'Methond using Method routine'Call Glossary.AddNumbers1(ByVal iParam1, ByVal iParam2, ByRef lReturn)FF_Control_SetText(HWND_FrmMain_TEXT2, Format$(lReturn))Glossary = Nothing End IfEnd Function
三、Prime COM对象代码
主要函数一个是method方式,一个是property get方式
'------------------------------------------------------------------------------
'
' PowerBASIC COM Server and Visual Basic 6 client example
' Copyright (c) 2009-2011 PowerBASIC, Inc.
' All Rights Reserved.
'
' Be sure to register the COM Server (prime.dll) before
' using it in a Visual Basic application!
'
' See the ReadMe.txt file!
'
'------------------------------------------------------------------------------#COMPILE DLL
#DIM ALL' Include the Type Library file as a resource file in the DLL
#RESOURCE TYPELIB, 1, "prime.tlb"' The #COM DOC statement sets the description of this server Visual
' Basic does not display the servers name in it's project references
' dialog box, it only displays the description.
#COM DOC "Prime number detector" ' Description of the server
#COM NAME "Prime", 1.0 ' Server name and version number
#COM TLIB ON ' Generate a type library file' Event Interface definition. This is required so that the event interface
' is saved in the type library file and so that VB knows how to receive the events.
INTERFACE IProgress GUID$("{AB813267-178F-47A0-8EFC-CE0CCB06D359}") AS EVENTINHERIT IAUTOMATIONMETHOD sOutString(BYVAL p AS WSTRING)METHOD Value(BYVAL p AS LONG)
END INTERFACE' MathClass that conatins the IPrime interface
CLASS MathClass GUID$("{590B350A-D462-485E-A35C-40FACA1ABC16}") AS COMINTERFACE IPrime GUID$("{19C9E340-205F-4AA6-A58F-A62179D0E26B}")INHERIT IUNKNOWNMETHOD AddNumbers1(BYVAL Num1 AS INTEGER, BYVAL Num2 AS INTEGER, BYREF lReturn AS LONG)lReturn = Num1 + Num2END METHODPROPERTY GET AddNumbers(BYVAL Num1 AS INTEGER, BYVAL Num2 AS INTEGER, BYREF lReturn AS LONG) AS LONGlReturn = Num1 + Num2END PROPERTY' While not the fastest way to determine if a number is prime,' it is good for an sample server with an event.PROPERTY GET IsPrime(BYVAL n AS LONG) AS LONGLOCAL w AS LONGLOCAL h AS LONGLOCAL i AS LONGLOCAL k AS WSTRINGIF n < 2 THEN' Any value less than two is not a prime numberPROPERTY = 0EXIT PROPERTYELSEIF n = 2 THEN' Two is a prime numberPROPERTY = 1EXIT PROPERTYEND IF' Test the value and see if the value can be divided' by any number between two and the value minus 1FOR i = 2 TO n-1IF (n MOD i) = 0 THEN' Not a prime numberPROPERTY = 0EXIT PROPERTYEND IFIF (i MOD 300) = 0 THEN' Call the event handler and inform it of our progress' This is the Sub prime_Value(ByVal p As Long) routine in VBk = "A message from ActiveX component!"RAISEEVENT IProgress.sOutString(k)RAISEEVENT IProgress.Value(i/n * 100)END IFNEXT i' The value is a prime numberPROPERTY = 1END PROPERTYEND INTERFACE' Specify that this class is an event source and sends' events to the IProgress interfaceEVENT SOURCE IProgressEND CLASS
代码是在原Prime基础上修改的,另外做了Event接口提供两个 Event, 在VB6上使用的话可以自加 vbPrime_Value和vbPrime_sOutString函数,COM对象会按条件要求自动触发VB6相应的功能。
运行结果
习练至此,其它功能没练,调用webservice和socket至少可以通过DLL封装实现,因此,感觉这套基于PowerBASIC的RAD开发环境做简单应用开发是可行的。