# Writing a Plugin with PowerBASIC

#### Joe Caverly

I have been using Delphi 2.0 to write my plugins, and it has been going okay.

I’ve been taking some of my more useful Pascal routines, and putting them into plugin form.

I also have quite a few routines written in PowerBASIC, and would like to incorporate them into plugin form.

Instead of translating the PowerBASIC code to Delphi 2.0, I was looking at doing one of two things.

The first would be to leave the PowerBASIC routines in the present PowerBASIC DLL, and then just call them from the Delphi 2.0 plugin.

The second, which would also be a learning opportunity, would be to convert my PowerBASIC DLL into a plugin.

So, I gave it a go, and have written a test plugin, thus;

Code:
#COMPILE DLL
#DIM ALL
#INCLUDE "WIN32API.INC"

TYPE PLUGININFO
pszDll AS ASCIIZ PTR                  '  name of the DLL
pszAuthor AS ASCIIZ PTR               '  author's name
pszEmail AS ASCIIZ PTR                '  author's email
pszWWW AS ASCIIZ PTR                  '  author's web page
pszDescription AS ASCIIZ PTR          '  (brief) description of plugin
pszFunctions AS ASCIIZ PTR            '  comma-delimited list of functions in the
'    plugin (leading _ for internal vars, @ for
'    var funcs, * for keystroke function,
'    otherwise it's a command)
nMajor AS LONG                        '  plugin's major version #
nMinor AS LONG                        '  plugin's minor version #
nBuild AS LONG                        '  plugin's build #
hModule AS LONG                       '  module handle
pszModule AS ASCIIZ PTR               '  module name
END TYPE

TYPE KEYINFO
nKey AS LONG                          '  key entered
nHomeRow AS LONG                      '  start row
nHomeColumn AS LONG                   '  start column
nRow AS LONG                          '  current row in window
nColumn AS LONG                       '  current column in window
pszLine AS ASCIIZ PTR                 '  command line
pszCurrent AS ASCIIZ PTR              '  pointer to position in line
fRedraw AS LONG                       '  if != 0, redraw the line
END TYPE

FUNCTION InitializePlugin ALIAS "InitializePlugin" () EXPORT AS BYTE
InitializePlugin = 0
END FUNCTION

FUNCTION ShutdownPlugin ALIAS "ShutdownPlugin" (EndProcess AS BYTE) EXPORT AS BYTE
ShutdownPlugin = 0
END FUNCTION

FUNCTION GetPluginInfo ALIAS "GetPluginInfo" (BYVAL piInfo AS PLUGININFO) EXPORT AS LONG
piInfo.pszDLL = UCODE$("PBDemo") piInfo.pszAuthor = "Joe Caverly" piInfo.pszEmail = "[email protected]" piInfo.pszWWW = "" piInfo.pszDescription = "A demonstration Plugin for 4NT/TCC, written with PowerBASIC" piInfo.pszFunctions = "@TIMES2" piInfo.nMajor = 1 piInfo.nMinor = 0 piInfo.nBuild = 1 GetPluginInfo = piInfo END FUNCTION FUNCTION Times2 ALIAS "Times2" (BYVAL x AS LONG) EXPORT AS LONG Times2 = x * 2 END FUNCTION FUNCTION LIBMAIN(BYVAL hInstance AS DWORD, BYVAL lReason AS LONG, BYVAL lReserved AS LONG) AS LONG SELECT CASE AS LONG lReason CASE %DLL_PROCESS_ATTACH ' This DLL has been mapped into the memory context of ' the calling program, and can be initialized as required. ' Here we return a non-zero LIBMAIN result to indicate success. LIBMAIN = 1 EXIT FUNCTION CASE %DLL_PROCESS_DETACH ' This DLL is about to be unloaded EXIT FUNCTION CASE %DLL_THREAD_ATTACH ' A [New] thread is starting (see THREADID) EXIT FUNCTION CASE %DLL_THREAD_DETACH ' This thread is closing (see THREADID) EXIT FUNCTION END SELECT END FUNCTION I am getting a “Relational operator expected” at; piInfo.pszDLL = ("PBDemo") I have tried changing it to; piInfo.pszDLL = UCODE$("PBDemo")

but same problem. I thought it was something to do with conversion to Unicode, but I am not sure.

I just need to get the “GetPluginInfo” working, if that is indeed where my problem is.

The PowerBASIC Manual is online at http://www.powerbasic.com/support/help/pbwin/index.htm

If the SDK had contained only the C++ example, I probably would never have even considered writing a plugin, but since it had an example for Delphi, I had no problem putting my own functions into the template, and it works quite well.

Adding a plugin template for PowerBASIC to the SDK may also entice others to consider writing their own plugins.

Joe

#### gwgaston

##### Senior IT Security Consultant
Some of my plugins are written in PowerBasic. In fact the first plugin I wrote (resolve) was in PowerBasic. On a lunch break now, bu I'll look at your code later tonight when I have more time.

#### gwgaston

##### Senior IT Security Consultant
Sorry Joe for the delay. I've been working a lot lately and I've had very little time for much else.

I was hoping the reason why I did what I did in the PowerBasic (PB) versions of my plugins would be more obvious now. It has been a while, and I am using older versions of the PB compilers; PBCC v3.04 that I used mostly for little console utils, and PBWin v7.04 that I used just to write plugins. If Bob will sell me a license for the new PB v10 compilers, I may get them and try to get back into using PB for plugins.

Anyway, I initially had a little bit of a time working out how to do a plugin in PB from the C++ example. There were issues with data types and unicode and so forth, and IIRC little things like plugin.cpp having GetPluginInfo taking no actual parameters, while the header file seemed to indicate you passed the module handle... or something like that. Don't have the SDK in front of me now.

Anyway, something like this should work:

Code:
TYPE PLUGININFO
pszDll                      AS WORD PTR
pszAuthor                   AS WORD PTR
pszEmail                    AS WORD PTR
pszWWW                      AS WORD PTR
pszDescription              AS WORD PTR
pszFunctions                AS WORD PTR
nMajor                      AS LONG
nMinor                      AS LONG
nBuild                      AS LONG
hModule                     AS DWORD
END TYPE

...

STATIC FUNCTION GetPluginInfo ALIAS "GetPluginInfo" () EXPORT AS LONG
STATIC piInfo AS PLUGININFO
STATIC DLLname AS STRING
STATIC DLLauth AS STRING
STATIC DLLmail AS STRING
STATIC DLLwww  AS STRING
STATIC DLLdesc AS STRING
STATIC DLLfuns AS STRING

FUNCTION = 0&
ELSE
g_lngRplIPFuns = %TRUE
DLLname   = UCODE$("Resolve") DLLauth = UCODE$("Gerald W. Gaston")
DLLmail   = UCODE$("[email protected]") DLLwww = UCODE$("http://www.itservicepro.com/Free_Utils.html")
DLLdesc   = UCODE$("Resolves a hostname or IP address to its counterpart") DLLfuns = UCODE$("RESOLVE,_RESOLVE,@RESOLVE,@IPADDRESS,@IPNAME")
piInfo.pszDll = STRPTR(DLLname)
piInfo.pszAuthor = STRPTR(DLLauth)
piInfo.pszEmail = STRPTR(DLLmail)
piInfo.pszWWW = STRPTR(DLLwww)
piInfo.pszDescription = STRPTR(DLLdesc)
piInfo.pszFunctions = STRPTR(DLLfuns)
piInfo.nMajor = 1&
piInfo.nMinor = 8&
piInfo.nBuild = 22&
FUNCTION = VARPTR(piInfo)
END IF
END FUNCTION

C:\App-Dev\PBCC\Resolve>plugin /I resolve
Name: Resolve
Author: Gerald W. Gaston
Email: [email protected]
Web: http://www.itservicepro.com/Free_Utils.html
Description: Resolves a hostname or IP address to its counterpart
Version: 1.8 Build 22

--
Gerald

#### Joe Caverly

Thankyou again for your assistance. I now have a Plugin, written in PowerBASIC, that will load, unload, show info, and show functions;

Code:
TCC:3736: 83 > plugin /f
PBPlugin:       @TIMES2

TCC:3736: 84 > plugin /i
Module:      pbplugin.dll
Name:        PBPlugin
Author:      Joe Caverly
Email:       [email protected]
Description: A demonstration Plugin for 4NT/TCC, written with PowerBASIC
Implements:  @TIMES2
Version:     2011.1  Build 2

TCC:3736: 85 > plugin /u *
I cannot get my test function to work, though.

In the GetPluginInfo function, I have the following;

Code:
    DLLfuns   = UCODE$("@TIMES2") I have the function defined as follows; Code: FUNCTION f_Times2 ALIAS "Times2" (BYVAL x AS STRING) EXPORT AS LONG DIM StrX AS STRING DIM ValX AS LONG OPEN "debug.txt" FOR APPEND AS #1 PRINT #1, TIME$ " ";
PRINT #1, "Times2"
CLOSE #1

'Convert argument from Unicode
StrX = ACODE$(x) ValX = VAL(StrX) ValX = ValX * 2 StrX = STR$(ValX)

'Convert to Unicode
x = UCODE$(StrX) f_Times2 = 0& END FUNCTION When I try the function, I get the following; Code: TCC:3736: 89 > echo %@Times2[1] TCC: (Sys) Incorrect function. "%@Times2[1]" My debug code tells me that I am not even executing the function, for nothing is written to debug.txt. I've only written plugins use Delphi 2.0, so I am trying to do in PB what I did in Delphi. While I have the FUNCTION as f_Times2, I am giving it the Alias of Times2, and I am EXPORTing the function, so not sure what else I need to do. What exactly do I need to do in order for the Plugin to recognize the function? Thanks from Joe #### gwgaston ##### Senior IT Security Consultant Function alias can't be Times2... I'll post working code for "Times2" in just a few minutes. Time to eat. #### gwgaston ##### Senior IT Security Consultant This works: Code: $DLLVersion = "1.2"

...

FUNCTION ACodeZ (BYVAL pUnicodeZ AS DWORD) AS STRING
LOCAL pwSt AS WORD PTR

IF pUnicodeZ THEN
pwSt = pUnicodeZ
DO WHILE @pwSt
INCR pwSt
LOOP
FUNCTION = ACODE$(PEEK$(pUnicodeZ, pwSt - pUnicodeZ))
END IF
END FUNCTION

FUNCTION PBPluginVer ALIAS "_TIMES2" (BYVAL pstrArgs AS WORD PTR) EXPORT AS LONG
DIM SUReturn AS STRING

SUReturn = UCODE$($DLLVersion) + CHR$(0,0) CALL MoveMemory(BYVAL pstrArgs, BYVAL STRPTR(SUReturn), LEN(SUReturn)) FUNCTION = 0& END FUNCTION FUNCTION IsNumeric(BYVAL Value AS STRING) AS LONG ' Expects that . is the decimal point and only allows 1 ' Allows sign char as first char only FUNCTION = 0 ' Assume no good IF ISFALSE(VERIFY (Value, "+-.0123456789") = 0) THEN ELSEIF INSTR(-1, Value, ANY "-+") > 1 THEN ' sign char only at first char ' Uncomment below if you prefer no trailing . be allowed 'ELSEIF RIGHT$(Value, 1) = "." THEN         ' . cannot be last char
ELSEIF TALLY(Value, ".") > 1 THEN           ' . Highlander
ELSE
FUNCTION = -1  ' Is valid after all
END IF
END FUNCTION

FUNCTION f_times2 ALIAS "f_TIMES2" (BYVAL pstrArgs AS WORD PTR) EXPORT AS DWORD
DIM StrX AS STRING
DIM SUReturn AS STRING
DIM ValX AS EXT

StrX = ACodeZ(pstrArgs)
'STDOUT "Passed in: "  + StrX
StrX = REMOVE$(StrX, ",") StrX = LTRIM$(StrX, ANY "$+ ") IF IsNumeric(StrX) THEN ValX = VAL(StrX) ValX = ValX * 2 StrX = LTRIM$(STR$(ValX)) ELSE ' Return 0 or whatever you prefer for incorrect input 'StrX = "0" StrX = "*ERR_INVALID_INPUT*" END IF SUReturn = UCODE$(StrX) + CHR$(0,0) CALL MoveMemory(BYVAL pstrArgs, BYVAL STRPTR(SUReturn), LEN(SUReturn)) FUNCTION = 0 END FUNCTION Some output: Code: [C:\App-Dev\PBWIN\Times2]filever TIMES2.DLL --a-- W32i DLL ENU 1.2.0.6 shp 15,872 01-02-2011 times2.dll [C:\App-Dev\PBWIN\Times2]plugin /l times2 [C:\App-Dev\PBWIN\Times2]plugin /i times2 Module: C:\App-Dev\PBWIN\Times2\times2.dll Name: Times2 Author: Joe Caverly Email: [email protected] Web: http://sites.google.com/site/jlcprogrammingstuff/ Description: A demonstration Plugin for 4NT/TCC, written with PowerBASIC Implements: _TIMES2,@TIMES2 Version: 1.2 Build 6 [C:\App-Dev\PBWIN\Times2]echo.%_times2 1.2 [C:\App-Dev\PBWIN\Times2]echo.%@times2[yourmama] *ERR_INVALID_INPUT* [C:\App-Dev\PBWIN\Times2]echo.%@times2[$200,000.25]
400000.5

StrX = ACODE$(StrX) which works, but I used the arbitrary number of 20, which is not acceptable. How do I determine the length of the argument passed into the function? Thanks from Joe #### Joe Caverly Thankyou for the very detailed example. I am using PB/WIN 8.05, and it has no ACodeZ function, as in Code: StrX = ACodeZ(pstrArgs) So, I used the following; Code: 'Convert argument from Unicode StrX = PEEK$(pstrArgs,20)
StrX = ACODE\$(StrX)
which works, but I used the arbitrary number of 20, which is not acceptable. How do I determine the length of the argument passed into the function?

Thanks from Joe
Joe, please read the original post, and you will see the ACodeZ function.

Sorry.

Joe

#### gwgaston

##### Senior IT Security Consultant
Joe, please read the original post, and you will see the ACodeZ function.

Sorry.

Joe
Posting to yourself eh? :D

Yes I edited my post a few minutes after I submitted it and added in the ACodeZ function, as on proof reading it I realized I didn't include it originally.

#### Joe Caverly

Posting to yourself eh? :D
Hello Gerald,
Well, I figure since I talk to myself, I may as well post to myself as well.

Thankyou very much for helping me establish a PowerBASIC plugin template.

One thing that I am having trouble with, though, is using internal TCC commands in PowerBASIC.

For example, in Delphi, I used the following;

Code:
procedure QPuts(TextToDisplay: PWideChar); stdcall; external 'TakeCmd.dll';
Here is how I translated it to PowerBASIC;

Code:
DECLARE FUNCTION QPuts LIB "TakeCmd.dll" ALIAS "QPUTS" (BYVAL pstrArgs AS WORD PTR) EXPORT AS DWORD
but this just gives me a syntax error. I believe the problem is with pointers, but I'm not sure in which direction to go from here.

I can print to the TCC from a plugin using this routine;

Code:
FUNCTION QPrint(TheString AS STRING) AS STRING
DIM h AS DWORD
h = GetStdHandle(%STD_OUTPUT_HANDLE)
DIM n AS DWORD
n = FREEFILE

OPEN HANDLE h FOR OUTPUT AS #n

PRINT #n, TheString

CLOSE #n
END FUNCTION
but I would still like to learn how to use the internal TCC commands from PowerBASIC.

Thanks from Joe