This series of articles is about getting started in using the PowerBasic Windows native code compiler. It is a tutorial about what the compiler is for and how it is used to write desktop applications, as well as standard Windows DLLs usable by other languages like C.
A Little History
For programmers who have been around for a while, they may be familiar with the name Turbo Basic. Back in the days of DOS, Borland International was a big player in the programming language market. Eventually, they decided to sell off their programming tools line, most of it going to Embarcadero (see: Embarcadero web site). That is where tools like Turbo C and Turbo Pascal ended up. Turbo Basic though had a different path. Borland did not originally create Turbo Basic (which competed directly with Microsoft's famous Quick Basic and PDS 7.1 languages). Instead, they purchased an existing product called Basic/Z created by Bob Zale and renamed it TurboBasic. When Borland sold off their programming tools, they sold back the rights to TurboBasic to its original developer, Bob Zale and he renamed it PowerBasic.
What is Native Code Compiling?
Most programmers today are familiar with either .NET programming languages or scripting languages (i.e., HTML5, JavaScript, etc.). In the Windows world, .NET currently “rules the roost” for many while old fashioned WIN32 programming has gone by the wayside. Native coding for Windows programmers means coding directly to the WIN32 API. Why would anyone want to do that, one may ask? One word “Performance”! To better appreciate how native coding affects performance, watch Herb Sutter’s talk “Why C++?” on Microsoft's Channel 9. No matter what programming language you currently use, his talk about C++ is a must watch video. Simply put, native coding directly to the operating system produces the best performance possible.
.NET is a framework between the operating system and one's application, a middleman one could say. So would not talking directly to the operating system provide better performance? Actually it does. But who cares about performance anymore, since computers are so much faster today? Today's small Windows tablets for one require high performance applications since they usually have minimal hardware. Developers of performance critical apps, like graphic editors, look for every bit of performance they can get. Embedded programmers also may want high performance apps so they can use minimal hardware.
OK, so maybe you are interested in a native code compiler. So what are the key things one should know about the PowerBasic compiler before using it?
How Does the Compiler Work Under the Hood?
The first thing to understand about PowerBasic is that the core compiler is written in assembler. There are two reasons for this:
- Fast compile speeds
- Fast (and small) executable code
Even hundreds of thousands of lines of code can be compiled in seconds rather than minutes. Executable sizes rival anything that can be done using raw C (not C++, but pure C), producing executable files in kilobytes, rather than megabytes. Executables have no runtimes required, but only the core Windows operating system DLLs (i.e., user32.dll, kernal32.dll, etc.). No .NET runtimes. No C runtimes.
The compiler is capable of producing both standard Windows EXEs and DLLs. This means you can also use it to write DLLs for any language which allows access to standard Windows DLLs. PowerBasic EXEs themselves can access standard Windows DLLs too, so you can use DLLs written in C or other languages in your apps.
PowerBasic is also a language which lacks a GUI framework, but instead relies directly on the WIN32 API for user interfaces, but handles this in two ways. First, one can write directly using the WIN32 API calls (headers are provided in PowerBasic code format). Second, one can use PowerBasic’s shortcut method via its Dynamic Dialog Tools (Called DDT). This is not a GUI framework, but instead is a GUI command set which simplifies many user interface WIN32 API tasks via a lean set of command wrappers using the Windows Dialog engine as the basis. For example, the WIN32 CreateWindowEx
function is replaced with a DDT counterpart called CONTROL ADD
. It works similar to CreateWindowEx
, but is easier to implement.
PowerBasic supports the most commonly used system controls in its DDT command set, while of course everything is available if you want to use the WIN32 API directly. DDT even provides a unique Canvas control (which is not available in system controls), by using a simple trick. The DDT Canvas control is actually a WIN32 STATIC class control which uses OwnerDraw
. The DDT command set simply provides a memory buffer to draw into and the compilers embedded runtime code handles the drawing of the memory buffer to the STATIC control during the WM_DRAWITEM
message processing. This is all done under the hood, so it appears like you have a real Canvas control.
Most BASIC programmers are used to working with the variable length string
data type and PowerBasic not only supports this, but it does so in a unique way. Rather than use Global memory and zero terminated strings, PowerBasic uses the WIN32 OLE string
APIs internally. OLE strings do not require they be zero (null) terminated, so even 100% binary data can be stored in a string
variable (i.e., CHR$(0)
). The latest version of PowerBasic also supports Unicode string
s as well. One nice thing about the OLE string
variables is that PowerBasic always appends them with a NULL
character just in case you need to pass it to a C DLL. You can just pass the string
by a pointer and it already has a NULL
termination in it (not needed for PowerBasic itself, but is there for multi-language support). The PowerBasic string
command set is extremely rich and is one benefit for programmers who write a lot of text string
parsers.
Lastly, PowerBasic allows inline assembler. So when performance is critical, one can switch to assembler at any time even mixing BASIC with Assembler.
Let’s Start With a Hello World App
#COMPILE EXE
#DIM ALL
FUNCTION PBMAIN () AS LONG
MSGBOX "Hello World"
END FUNCTION
The above EXE is only 7 kilobytes in size, most of that being embedded runtime code (which is still small).
Now Let's Create a Real Window (Dialog)
In this example, I will create a window (Dialog
) with some controls on it and some menus:
#COMPILE EXE
#REGISTER NONE
#DIM ALL ' This is helpful to prevent errors in coding
#INCLUDE "win32api.inc" ' Win 32 API include file !
' ----------------------------------------------------------
' ----------------------------------------------------------
%Form1_FILE = 500
' ----------------------------------------------------------
%Form1_NEWFILE = 505
%Form1_OPENFILE = 510
%Form1_SAVEFILE = 515
%Form1_SAVEAS = 520
%Form1_SEPARATOR_525 = 525
%Form1_EXIT = 530
' ----------------------------------------------------------
%Form1_EDIT = 600
' ----------------------------------------------------------
%Form1_CUT = 605
%Form1_COPY = 610
%Form1_PASTE = 615
' ----------------------------------------------------------
%Form1_HELP = 700
' ----------------------------------------------------------
%Form1_HELP1 = 705
%FORM1_LABEL1 = 100
%FORM1_BUTTON1 = 105
%FORM1_CHECK1 = 110
%FORM1_LISTBOX1 = 115
%FORM1_COMBOBOX1 = 120
' --------------------------------------------------
DECLARE SUB CreateDialog_Form1(BYVAL hParent&)
DECLARE CALLBACK FUNCTION Form1_DlgProc
' --------------------------------------------------
DECLARE SUB Form1_NEWFILE_Select()
DECLARE SUB Form1_OPENFILE_Select()
DECLARE SUB Form1_SAVEFILE_Select()
DECLARE SUB Form1_SAVEAS_Select()
DECLARE SUB Form1_EXIT_Select()
DECLARE SUB Form1_CUT_Select()
DECLARE SUB Form1_COPY_Select()
DECLARE SUB Form1_PASTE_Select()
DECLARE SUB Form1_HELP1_Select()
DECLARE CALLBACK FUNCTION CBF_FORM1_BUTTON1()
DECLARE CALLBACK FUNCTION CBF_FORM1_CHECK1()
DECLARE CALLBACK FUNCTION CBF_FORM1_LISTBOX1()
DECLARE CALLBACK FUNCTION CBF_FORM1_COMBOBOX1()
' --------------------------------------------------
GLOBAL App_CurrentDlg AS DWORD
GLOBAL hForm1& ' Dialog handle
GLOBAL hForm1_Menu0& ' Global Handles for menus
GLOBAL hForm1_Menu1&
GLOBAL hForm1_Menu2&
GLOBAL hForm1_Menu3&
' *************************************************************
' Application Entrance
' *************************************************************
FUNCTION PBMAIN ()AS LONG
LOCAL Count&
CreateDialog_Form1 0
' Dialogs Handle in variable - hForm1&
DIALOG SHOW MODELESS hForm1& , CALL Form1_DlgProc
DO
DIALOG DOEVENTS TO Count&
LOOP UNTIL Count&=0
END FUNCTION
' *************************************************************
' Application Dialogs
' *************************************************************
SUB CreateDialog_Form1(BYVAL hParent&)
LOCAL Style&, ExStyle&
LOCAL N&, CT& ' Variables used for Reading Data in Arrays for Listbox and Combobox
Style& = %WS_POPUP OR %DS_MODALFRAME OR %WS_CAPTION OR %WS_MINIMIZEBOX OR %WS_SYSMENU OR %DS_CENTER
ExStyle& = 0
DIALOG NEW hParent&, "Your Dialog", , , 280, 200, Style&, ExStyle&, TO hForm1&
' ---------------------------
MENU NEW BAR TO hForm1_Menu0&
' ---------------------------
' ---------------------------
MENU NEW POPUP TO hForm1_Menu1&
MENU ADD POPUP, hForm1_Menu0& ,"&File", hForm1_Menu1&, %MF_ENABLED
' ---------------------------
MENU ADD STRING, hForm1_Menu1&, "&New File", %Form1_NEWFILE, %MF_ENABLED
MENU ADD STRING, hForm1_Menu1&, "&Open File", %Form1_OPENFILE, %MF_ENABLED
MENU ADD STRING, hForm1_Menu1&, "&Save File", %Form1_SAVEFILE, %MF_ENABLED
MENU ADD STRING, hForm1_Menu1&, "Save File &As", %Form1_SAVEAS, %MF_ENABLED
MENU ADD STRING, hForm1_Menu1&, "-", %Form1_SEPARATOR_525, %MF_ENABLED
MENU ADD STRING, hForm1_Menu1&, "E&xit", %Form1_EXIT, %MF_ENABLED
' ---------------------------
MENU NEW POPUP TO hForm1_Menu2&
MENU ADD POPUP, hForm1_Menu0& ,"&Edit", hForm1_Menu2&, %MF_ENABLED
' ---------------------------
MENU ADD STRING, hForm1_Menu2&, "Cu&t", %Form1_CUT, %MF_ENABLED
MENU ADD STRING, hForm1_Menu2&, "&Copy", %Form1_COPY, %MF_ENABLED
MENU ADD STRING, hForm1_Menu2&, "&Paste", %Form1_PASTE, %MF_ENABLED
' ---------------------------
MENU NEW POPUP TO hForm1_Menu3&
MENU ADD POPUP, hForm1_Menu0& ,"&Help", hForm1_Menu3&, %MF_ENABLED
' ---------------------------
MENU ADD STRING, hForm1_Menu3&, "&Contents", %Form1_HELP1, %MF_ENABLED
MENU ATTACH hForm1_Menu0&, hForm1&
CONTROL ADD LABEL, hForm1&, %FORM1_LABEL1, "Label 1", 28, 136, 216, 32, _
%WS_CHILD OR %WS_VISIBLE OR %SS_CENTER OR %WS_BORDER, 0
CONTROL ADD "Button", hForm1&, %FORM1_BUTTON1, "Button 1", 12, 8, 80, 24, _
%WS_CHILD OR %WS_VISIBLE OR %BS_PUSHBUTTON OR %WS_TABSTOP, 0 CALL CBF_FORM1_BUTTON1
CONTROL ADD CHECKBOX, hForm1&, %FORM1_CHECK1, "Check 1", 12, 40, 92, 24, _
%WS_CHILD OR %WS_VISIBLE OR %BS_AUTOCHECKBOX OR %WS_TABSTOP, 0 CALL CBF_FORM1_CHECK1
' - - - - - - - - - - - - - - - - - - - - - - - - -
DIM LISTBOX1_List(4) AS LOCAL STRING
DATA "Item 1","Item 2","Item 3","Item 4","Item 5"
FOR N&=0 TO 4
CT&=CT&+1
LISTBOX1_List(N&)=READ$(CT&)
NEXT N&
' - - - - - - - - - - - - - - - - - - - - - - - - -
CONTROL ADD LISTBOX, hForm1&, %FORM1_LISTBOX1, LISTBOX1_List(), 12, 72, 108, 48, _
%WS_CHILD OR %WS_VISIBLE OR %LBS_NOTIFY OR %LBS_SORT OR %LBS_NOINTEGRALHEIGHT OR %WS_VSCROLL OR %WS_TABSTOP, _
%WS_EX_CLIENTEDGE CALL CBF_FORM1_LISTBOX1
' - - - - - - - - - - - - - - - - - - - - - - - - -
DIM COMBOBOX1_List(4) AS LOCAL STRING
DATA "Item 1","Item 2","Item 3","Item 4","Item 5"
FOR N&=0 TO 4
CT&=CT&+1
COMBOBOX1_List(N&)=READ$(CT&)
NEXT N&
' - - - - - - - - - - - - - - - - - - - - - - - - -
CONTROL ADD COMBOBOX, hForm1&, %FORM1_COMBOBOX1, COMBOBOX1_List(), 136, 16, 112, 52, _
%WS_CHILD OR %WS_VISIBLE OR %CBS_DROPDOWNLIST OR %CBS_SORT OR %WS_VSCROLL OR %CBS_NOINTEGRALHEIGHT OR %WS_TABSTOP, _
%WS_EX_CLIENTEDGE CALL CBF_FORM1_COMBOBOX1
END SUB
' *************************************************************
' Dialog Window Procedure
' for Form Form1
' uses Global Handle - hForm1&
' *************************************************************
CALLBACK FUNCTION Form1_DlgProc
SELECT CASE CBMSG
CASE %WM_COMMAND
' Process Messages to Controls that have no Callback Function
' and Process Messages to Menu Items
SELECT CASE LOWRD(CBWPARAM) ' Parse by Ctrl ID #
CASE %Form1_NEWFILE ' Popup Menu Item Selected
Form1_NEWFILE_Select
CASE %Form1_OPENFILE ' Popup Menu Item Selected
Form1_OPENFILE_Select
CASE %Form1_SAVEFILE ' Popup Menu Item Selected
Form1_SAVEFILE_Select
CASE %Form1_SAVEAS ' Popup Menu Item Selected
Form1_SAVEAS_Select
CASE %Form1_EXIT ' Popup Menu Item Selected
Form1_EXIT_Select
CASE %Form1_CUT ' Popup Menu Item Selected
Form1_CUT_Select
CASE %Form1_COPY ' Popup Menu Item Selected
Form1_COPY_Select
CASE %Form1_PASTE ' Popup Menu Item Selected
Form1_PASTE_Select
CASE %Form1_HELP1 ' Popup Menu Item Selected
Form1_HELP1_Select
CASE ELSE
END SELECT
CASE %WM_SHOWWINDOW
IF CBWPARAM THEN
App_CurrentDlg=CBHNDL
ELSE
App_CurrentDlg=0
END IF
CASE %WM_ACTIVATE
IF LOWRD(CBWPARAM)<>0 THEN
App_CurrentDlg=CBHNDL
ELSE
App_CurrentDlg=0
END IF
CASE %WM_DESTROY
CASE ELSE
END SELECT
FUNCTION = 0
END FUNCTION
' *************************************************************
' Application Callback Functions (or Procedures) for Controls
' *************************************************************
' ------------------------------------------------
SUB Form1_NEWFILE_Select()
END SUB
' ------------------------------------------------
SUB Form1_OPENFILE_Select()
END SUB
' ------------------------------------------------
SUB Form1_SAVEFILE_Select()
END SUB
' ------------------------------------------------
SUB Form1_SAVEAS_Select()
END SUB
' ------------------------------------------------
SUB Form1_EXIT_Select()
END SUB
' ------------------------------------------------
SUB Form1_CUT_Select()
END SUB
' ------------------------------------------------
SUB Form1_COPY_Select()
END SUB
' ------------------------------------------------
SUB Form1_PASTE_Select()
END SUB
' ------------------------------------------------
SUB Form1_HELP1_Select()
END SUB
' ------------------------------------------------
CALLBACK FUNCTION CBF_FORM1_BUTTON1
IF CBCTLMSG=%BN_CLICKED THEN
END IF
IF CBCTLMSG=%BN_SETFOCUS THEN
END IF
IF CBCTLMSG=%BN_KILLFOCUS THEN
END IF
END FUNCTION
' ------------------------------------------------
CALLBACK FUNCTION CBF_FORM1_CHECK1
IF CBCTLMSG=%BN_CLICKED THEN
END IF
IF CBCTLMSG=%BN_SETFOCUS THEN
END IF
IF CBCTLMSG=%BN_KILLFOCUS THEN
END IF
END FUNCTION
' ------------------------------------------------
CALLBACK FUNCTION CBF_FORM1_LISTBOX1
LOCAL CVal&
' Return Current Selection in CVal&
CONTROL SEND CBHNDL , CBCTL, %LB_GETCURSEL, 0,0 TO CVal&
IF CBCTLMSG=%LBN_SELCHANGE THEN
END IF
IF CBCTLMSG=%LBN_DBLCLK THEN
END IF
IF CBCTLMSG=%LBN_SETFOCUS THEN
END IF
IF CBCTLMSG=%LBN_KILLFOCUS THEN
END IF
END FUNCTION
' ------------------------------------------------
CALLBACK FUNCTION CBF_FORM1_COMBOBOX1
LOCAL CVal&
' Return Current Selection in CVal&
CONTROL SEND CBHNDL , CBCTL, %CB_GETCURSEL, 0,0 TO CVal&
IF (CBCTLMSG=%CBN_SELCHANGE) OR (CBCTLMSG=%CBN_EDITCHANGE) OR (CBCTLMSG=%CBN_EDITUPDATE) THEN
END IF
IF CBCTLMSG=%CBN_DBLCLK THEN
END IF
IF CBCTLMSG=%CBN_SETFOCUS THEN
END IF
IF CBCTLMSG=%CBN_KILLFOCUS THEN
END IF
END FUNCTION
The above example EXE compiles to about 52 Kilobytes. The DDT commands used, simplify some of the API tasks for a Dialog, but you can still see the API style similarity in it though. It uses a Dialog Procedure, just like you do with a pure WIN32 app. Some of the WM_COMMAND
notification messages though are parsed out by the DDT embedded runtime code and the calls are automatically forwarded to the CallBack subroutines , so you don’t have to process WM_COMMAND
for all control types. The notification codes for WM_COMMAND
are pushed into some system variables (i.e., CBCTLMSG
) already, so you can test for the notification messages just by using these system variables. Something similar is done with the Dialog procedure with the normal parameters of a dialog procedure pushed to system variables (i.e., CBMSG
). PowerBasic’s DDT also can do something similar with some WM_NOTIFY
notification messages.
DDT just simplifies things a bit, rather than totally shield you from the WIN32 API. You can also integrate the WIN32 API into an application written using the DDT command set.
So When Would I Find PowerBasic Useful?
Programmers still using classic Visual Basic will find PowerBasic very useful. First, it is BASIC also, so an easier transition than say C. Second, it can be used to write DLLs for your Visual Basic programs to improve performance. It is better suited to writing WIN32 code, so at times when you require accessing the WIN32 API from Visual Basic, you may find PowerBasic helpful. PowerBasic is better geared toward accessing the WIN32 API than is classic Visual Basic. It supports things like code pointers and data pointers. It can do Threading. It directly supports TCP and UDP communications. It supports serial communications. It can do inline assembler.
Programmers who find C a challenge, but still would like to be able to write tiny, fast, WIN32 applications and DLLs will find PowerBasic helpful. PowerBasic is actually easier to work with than C, when it comes to writing WIN32 stuff. It has so many tricks in it which rival C’s raw power, but are easier to implement. Again, it is BASIC.
In future articles, I plan to write more about working with the WIN32 API using PowerBasic.