Writing a Plugin with PowerBASIC

#1
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]ahoo.ca"
  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
May 28, 2008
40
1
SC
#2
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
May 28, 2008
40
1
SC
#4
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
  
  IF ISTRUE g_AlreadyLoaded THEN
    FUNCTION = 0&
  ELSE
    g_AlreadyLoaded = 1&
    g_lngRplIPFuns = %TRUE
    g_intTcmdLoaded = %FALSE
    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
Implements: RESOLVE,_RESOLVE,@RESOLVE,@IPADDRESS,@IPNAME
Version: 1.8 Build 22


--
Gerald
 
#5
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]
Web:         http://sites.google.com/site/jlcprogrammingstuff/
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
May 28, 2008
40
1
SC
#6
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
May 28, 2008
40
1
SC
#7
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

[C:\App-Dev\PBWIN\Times2]echo.%@times2[$200.000.25]
*ERR_INVALID_INPUT*

[C:\App-Dev\PBWIN\Times2]echo.%@times2[-2]
-4
--
Gerald W. Gaston
 
#8
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
 
#9
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
May 28, 2008
40
1
SC
#10
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.
 
#11
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