FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象

一、序言

初步看了看PowerBASIC编程,很类似用VC注册窗体后调用回调函数,先是一个Dialog new,然后添加组件 Control add ......, 然后在处理 Windows MSG和发给组件的消息,这种编程方式和早期DOS 25x80屏幕上编程一样,要自己处理显示内容的位置和区域大小。

PowerBASIC还自带了一个Dlgedit.exe , 它是微软的 Dialog Editor,用于设计窗口和摆放组件。

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第1张图片

同时,PowerBASIC带了RC.exe和PBRES.exe用于资源编译,但设计器是16位的,在Win10和Win11上要处理一下才能运行。处理的方法是到github下载 OTVDM,解压到C盘根目录,执行Install向注册表填加16位程序转向即可。安装有OTVDM的计算机,可以安装VB3, VB6, Delphi2, Delphi3, 而且窗口样式和现在的一样,界面倍加美观。

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第2张图片

后来,有了PBFORMS,类似Linux上Glade的界面设计器,属性方法框架基本上在上面完成了。

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第3张图片

在菜单上,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.MSG
        CASE %WM_INITDIALOG
            ' Initialization handler

        CASE %WM_NCACTIVATE
            STATIC hWndSaveFocus AS DWORD
            IF ISFALSE CB.WPARAM THEN
                ' Save control focus
                hWndSaveFocus = GetFocus()
            ELSEIF hWndSaveFocus THEN
                ' Restore control focus
                SetFocus(hWndSaveFocus)
                hWndSaveFocus = 0
            END IF

        CASE %WM_COMMAND
            ' Process control notifications
            SELECT CASE AS LONG CB.CTL
                CASE %IDC_TEXTBOX1

                CASE %IDC_TEXTBOX2

                CASE %IDC_TEXTBOX3

                CASE %IDC_TEXTBOX4

                CASE %IDC_TEXTBOX5

                CASE %IDC_BUTTON1
                    IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                        MSGBOX "%IDC_BUTTON1=" + FORMAT$(%IDC_BUTTON1), _
                            %MB_TASKMODAL
                    END IF

            END SELECT
    END SELECT
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Dialogs **
'------------------------------------------------------------------------------
FUNCTION ShowDIALOG1(BYVAL hParent AS DWORD) AS LONG
    LOCAL lRslt AS LONG

#PBFORMS BEGIN DIALOG %IDD_DIALOG1->->
    LOCAL hDlg  AS DWORD

    DIALOG 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 hDlg
    CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX1, "TextBox1", 40, 25, 70, 15
    CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX2, "TextBox2", 40, 55, 105, 15
    CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX3, "TextBox3", 40, 75, 105, 15
    CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX4, "TextBox4", 40, 95, 105, 15
    CONTROL ADD TEXTBOX, hDlg, %IDC_TEXTBOX5, "TextBox5", 40, 115, 105, 15
    CONTROL ADD BUTTON,  hDlg, %IDC_BUTTON1, "Button1", 40, 145, 60, 15
#PBFORMS END DIALOG

    DIALOG SHOW MODAL hDlg, CALL ShowDIALOG1Proc TO lRslt

#PBFORMS BEGIN CLEANUP %IDD_DIALOG1
#PBFORMS END CLEANUP

    FUNCTION = lRslt
END FUNCTION
'------------------------------------------------------------------------------

感觉这些东西挺不错的,代表着一种不同VB6的实现思想。PowerBASIC是32位编译器、没有64位编译器、现在已经停更了,但仍然在一些地方得到应用。PowerBASIC支持多线程,用它编写DLL体积小速度快。后来,又有好事者 FireFly 为它开发了 RAD 。

二、FireFly RAD powerBASIC

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第4张图片

控件几十项,代码可以用PowerBASIC原装语句,也可以用它自己的语句,但编译器完全是PowerBASIC的,只是又做了一些封装。

这个习练程序是按如下步骤调用PowerBASIC COM对象的。

用FireFly带的 Type_lib browser 找到已经注册的COM对象

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第5张图片

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第6张图片

双击找到的COM对象,然后点击菜单上的Code

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第7张图片

会自动生成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 = 1

INTERFACE IPRIME $IID_IPRIME

   INHERIT IAutomation

   ' =====================================================================================
   METHOD ADDNUMBERS1 ( _                               ' VTable offset = 12
     BYVAL NUM1 AS INTEGER _                            ' [in] NUM1 VT_I2 
   , BYVAL NUM2 AS INTEGER _                            ' [in] NUM2 VT_I2 
   , BYREF LRETURN AS LONG _                            ' [in][out] *LRETURN VT_I4 
   )                                                    ' void
   ' =====================================================================================
   PROPERTY GET ADDNUMBERS ( _                          ' VTable offset = 16
     BYVAL NUM1 AS INTEGER _                            ' [in] NUM1 VT_I2 
   , BYVAL NUM2 AS INTEGER _                            ' [in] NUM2 VT_I2 
   , BYREF IN LRETURN AS LONG _                         ' [in][out] *LRETURN VT_I4 
   ) AS LONG                                            ' VT_I4 
   ' =====================================================================================
   PROPERTY GET ISPRIME ( _                             ' VTable offset = 20
   ) AS LONG                                            ' [in] N VT_I4 
   ' =====================================================================================

END INTERFACE

#ENDIF   ' /* __IPRIME_INTERFACE_DEFINED__ */

全部考贝,一个字都不需要更改!

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第8张图片

双击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 Array
                                   hWndForm         As Dword, _  ' handle of Form
                                   hWndControl      As Dword, _  ' handle of Control
                                   idButtonControl  As Long   _  ' identifier of button
                                   ) As Long
                                   
                                   FF_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 Form
                         ByVal UserData As Long _  ' optional user defined Long value
                         ) As Long

    Local nScreenX As Long, nScreenY As Long
    Local nfrmMainWidth As Long, nfrmMainHeight As Long  
    Local LanID As String      

    Local hWndDeskTop As Dword 
    hWndDesktop = GetDesktopWindow()                                                            '%HWND_DESKTOP
    
    LanID = "FF"    'PB for powerbasic statements, and FF for FireFly statements
    
    If LanID = "PB" Then
        Desktop Get Size To nScreenX, nScreenY                                                  'Get screen size
        Dialog Get Size hWndForm To nfrmMainWidth, nfrmMainHeight                               'Get frmMain size
        Dialog Set LoC hWndForm, (nScreenX - nfrmMainWidth)/2, (nScreenY - nfrmMainHeight)/2    'Center frmMain
    Else    
        FF_Control_GetSize hWndDesktop, nScreenX, nScreenY                                      '%HWND_DESKTOP = hWndDesktop = 0
        FF_Control_GetSize hWndForm, nfrmMainWidth, nfrmMainHeight
        FF_Control_SetLoc hWndForm, (nScreenX - nfrmMainWidth)/2, (nScreenY - nfrmMainHeight)/2
    End 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 Array
                                     hWndForm         As Dword, _  ' handle of Form
                                     hWndControl      As Dword, _  ' handle of Control
                                     idButtonControl  As Long   _  ' identifier of button
                                     ) As Long


    Local Glossary As IPRIME
    Let Glossary = NewCom $PROGID_MATHCLASS

    'If IsFalse(IsObject(Glossary)) Then Exit Function
    
    If IsObject(Glossary) Then
        Local iParam1 As Integer
        Local iParam2 As Integer
        Local lReturn As Long
        iParam1 = 3: iParam2 = 5
        
        'Method using Property GET
        Glossary.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 If
        
End 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 EVENT
  INHERIT IAUTOMATION

  METHOD 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 COM
  INTERFACE IPrime GUID$("{19C9E340-205F-4AA6-A58F-A62179D0E26B}")
    INHERIT IUNKNOWN

    METHOD AddNumbers1(BYVAL Num1 AS INTEGER, BYVAL Num2 AS INTEGER, BYREF lReturn AS LONG)
        lReturn = Num1 + Num2
    END METHOD

    PROPERTY GET AddNumbers(BYVAL Num1 AS INTEGER, BYVAL Num2 AS INTEGER, BYREF lReturn AS LONG) AS LONG
        lReturn = Num1 + Num2
    END 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 LONG
      LOCAL w AS LONG
      LOCAL h AS LONG
      LOCAL i AS LONG
      LOCAL k AS WSTRING

      IF n < 2 THEN
        ' Any value less than two is not a prime number
        PROPERTY = 0
        EXIT PROPERTY
      ELSEIF n = 2 THEN
        ' Two is a prime number
        PROPERTY = 1
        EXIT PROPERTY
      END IF

      ' Test the value and see if the value can be divided
      ' by any number between two and the value minus 1
      FOR i = 2 TO n-1
        IF (n MOD i) = 0 THEN
          ' Not a prime number
          PROPERTY = 0
          EXIT PROPERTY
        END IF

        IF (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 VB
          k = "A message from ActiveX component!"
          RAISEEVENT IProgress.sOutString(k)
          RAISEEVENT IProgress.Value(i/n * 100)
        END IF

      NEXT i

      ' The value is a prime number
      PROPERTY = 1

    END PROPERTY

  END INTERFACE

  ' Specify that this class is an event source and sends
  ' events to the IProgress interface
  EVENT SOURCE IProgress

END CLASS

代码是在原Prime基础上修改的,另外做了Event接口提供两个 Event, 在VB6上使用的话可以自加 vbPrime_Value和vbPrime_sOutString函数,COM对象会按条件要求自动触发VB6相应的功能。

运行结果

FireFly PowerBASIC RAD编程,调用PowerBASIC COM对象_第9张图片

习练至此,其它功能没练,调用webservice和socket至少可以通过DLL封装实现,因此,感觉这套基于PowerBASIC的RAD开发环境做简单应用开发是可行的。

你可能感兴趣的:(FireFly,RAD,PowerBASIC,RAD,PowerBASIC,COM,FF,designer)