FreeBasic provides a simple DLL build and call under the folder .\examples\DLL. The mydll.bas program is compiled first, to make myddl.dll. Then you can compile test.bas, which creates Test.exe, which actually calls the function in mydll.dll. The testload.bas program can be compiled next, and it also calls the function in mydll.dll, but in this case, it only needs to be able to find mydll.dll when the program is actually run, not at the time when the program is built. This approach is useful when not all client machines will have a copy of the necessary DLL files, or certain freatures can be triggered only if a given DLL file with a given procedure embedded exists.
To ensure everybody is on the same page here, I'm including the source code for the three files from FreeBasic:
mydll.bas:
''
'' mydll -- simple dll test
''
'' compile as: fbc -dll mydll.bas (will create mydll.dll and libmydll.dll.a under Win32,
'' or libmydll.so under Linux)
''
'' note: libmydll.dll.a is an import library, it's only needed when creating
'' an executable that calls any of mydll's functions, only distribute
'' the DLL files with your apps, do not include the import libraries,
'' they are useless to end-users
''
#include once "mydll.bi"
''
'' note: do not add any executable code to the main module (ie: outside
'' any function), because that code will never be executed as only DllMain
'' is invoked by Windows at the initialization
''
''::::::
''
'' simple exported function, the full prototype is at mydll.bi (the EXPORT clause must be used here)
''
FUNCTION AddNumbers ( BYVAL operand1 AS INTEGER, BYVAL operand2 AS INTEGER ) AS INTEGER EXPORT
FUNCTION = operand1 + operand2
END FUNCTION
test.bas:
''
'' test -- calls a mydll's function and prints the result
''
'' compile as: fbc test.bas (couldn't be simplier, eh?)
''
#include "mydll.bi"
randomize Timer
Color 15,1
cls
dim as integer x = rnd * 10
dim as integer y = rnd * 10
print x; " +"; y; " ="; addnumbers( x, y )
Do While InKey$=""
Loop
testload.bas:
''
'' testload -- loads mydll at runtime, calls a mydll's function and prints the result
''
'' compile as: fbc testload.bas
''
'' note: requires the compiled mydll dynamic library to be available in current
'' directory; see mydll.bas for info on how to create this.
''
dim library as any ptr
dim addnumbers as function( byval operand1 as integer, byval operand2 as integer ) as integer
'' Note we specify just "mydll" as library file name; this is to ensure
'' compatibility between Windows and Linux, where a dynamic library
'' has different file name and extension.
''
library = dylibload( "mydll" )
if( library = 0 ) then
print "Cannot load the mydll dynamic library, aborting program..."
end 1
end if
addnumbers = dylibsymbol( library, "AddNumbers" )
if( addnumbers = 0 ) then
print "Cannot get AddNumbers function address from mydll library, aborting program..."
end 1
end if
randomize Timer
Color 15,1
cls
dim as integer x = rnd * 10
dim as integer y = rnd * 10
print x; " +"; y; " ="; addnumbers( x, y )
'' Done with the library; the OS will automatically unload libraries loaded by a process
'' when it terminates, but we can also force unloading during our program execution to
'' save resources; this is what the next line does. Remember that once you unload a
'' previously loaded library, all the symbols you got from it via dylibsymbol will become
'' invalid, and accessing them will cause the application to crash.
''
dylibfree library
Do While InKey$=""
Loop
mydll.bi:
''
'' all FB functions are by default STDCALL on Windows and also PUBLIC,
'' so nothing else has to be added (note that FBC will not include "mydll"
'' to linker's list when creating the DLL, only when using it on an .exe)
''
declare function AddNumbers lib "mydll" alias "AddNumbers" ( byval operand1 as integer, byval operand2 as integer ) as integer
That's four files, not three, right? Actually, the mydll.bi file might be called a header file in some cases. The .BI extension likely stands for BASIC INCLUDE file, and often might be named .INC instead. It provides useful information to the compiler at compile time, but often may not contain any executable code itself. The equivalent in C or C++ would be files that end with .h for an extension.
Alright, so we have four files rather than three, and if you stick them in the same folder, then compile them with FreeBasic as indicated in the comments in each, You should be able to compile and run the resulting programs test.exe and testload.exe.
Now here are the same four files, but modified to compile and run with PB/CC instead. If you have access to PB/Win rather than PB/CC, you can comment out the COLOR and CLS statements, and replace the PRINT statement with a MSGBOX statement. Since MSGBOX is not able to print numeric values directly, you would also have to wrap the iResult or returned value from AddNumbers in a STR$() and append these to other string elements.
Now let's look at the same files, but modified for PowerBasic:
mydll.bas:
#COMPILE DLL
''
'' mydll -- simple dll test
''
'' compile as: fbc -dll mydll.bas (will create mydll.dll and libmydll.dll.a under Win32,
'' or libmydll.so under Linux)
''
'' note: libmydll.dll.a is an import library, it's only needed when creating
'' an executable that calls any of mydll's functions, only distribute
'' the DLL files with your apps, do not include the import libraries,
'' they are useless to end-users
''
'#INCLUDE once "mydll.bi" 'supported in FreeBasic
#INCLUDE "mydll.bi" 'best equivalent in PowerBasic
''
'' note: do not add any executable code to the main module (ie: outside
'' any function), because that code will never be executed as only DllMain
'' is invoked by Windows at the initialization
''
''::::::
''
'' simple exported function, the full prototype is at mydll.bi (the EXPORT clause must be used here)
''
'FUNCTION AddNumbers ( BYVAL operand1 AS INTEGER, BYVAL operand2 AS INTEGER ) AS INTEGER EXPORT 'FreeBasic
FUNCTION AddNumbers ( BYVAL operand1 AS INTEGER, BYVAL operand2 AS INTEGER ) EXPORT AS INTEGER 'PowerBasic
FUNCTION = operand1 + operand2
END FUNCTION
test.bas:
''
'' test -- calls a mydll's function and prints the result
''
'' compile as: fbc test.bas (couldn't be simplier, eh?)
''
#INCLUDE "mydll.bi"
#IF %DEF(%PB_CC32) OR %DEF(%PB_WIN32)
FUNCTION PBMAIN
#ENDIF
RANDOMIZE TIMER
COLOR 15,1
CLS
DIM x AS INTEGER: x = RND * 10
DIM y AS INTEGER: y = RND * 10
PRINT x; " +"; y; " ="; addnumbers( x, y )
DO WHILE INKEY$=""
LOOP
#IF %DEF(%PB_CC32) OR %DEF(%PB_WIN32)
END FUNCTION
#ENDIF
testload.bas:
''
'' testload -- loads mydll at runtime, calls a mydll's function and prints the result
''
'' compile as: fbc testload.bas
''
'' note: requires the compiled mydll dynamic library to be available in current
'' directory; see mydll.bas for info on how to create this.
''
DECLARE FUNCTION addnumbers(BYVAL operand1 AS INTEGER, _ 'this is PowerBasic syntax
BYVAL operand2 AS INTEGER) AS INTEGER
#IF %DEF(%PB_CC32) OR %DEF(%PB_WIN32)
$INCLUDE "c:\win32api\win32api.inc"
FUNCTION PBMAIN
DIM fAddr AS DWORD, iResult AS INTEGER 'a couple of items for PowerBasic
#ENDIF
'DIM library AS ANT PTR 'not allowed as PowerBasic type
DIM library AS DWORD PTR 'what powerbasic will accept instead
'DIM addnumbers AS FUNCTION( BYVAL operand1 AS INTEGER, _ 'not allowed in PowerBasic syntax
' BYVAL operand2 AS INTEGER ) AS INTEGER
'PowerBasic does not permit DECLARE statements inside procedures, so the
'equivalent DECLARE for addnumbers was moved up above.
'' Note we specify just "mydll" as library file name; this is to ensure
'' compatibility between Windows and Linux, where a dynamic library
'' has different file name and extension.
'library = dylibload( "mydll" ) 'not according to BowerBasic syntax
library = loadlibrary("mydll") 'PowerBasic using loadlibrary function
IF (library = 0) THEN
PRINT "Cannot load the mydll dynamic library, aborting program..."
END 1
END IF
'addnumbers = dylibsymbol( library, "AddNumbers" ) 'not supported in PowerBasic syntax
fAddr = GetProcAddress(library, "AddNumbers") 'PowerBasic's use of GetProcAddress API
'IF (AddNumbers = 0) THEN 'FreeBasic allows function name to be overloaded
IF (fAddr = 0) THEN 'PowerBasic wants a separate variable for entry address
PRINT "Cannot get AddNumbers function address from mydll library, aborting program..."
END 1
END IF
RANDOMIZE TIMER
COLOR 15,1
CLS
'DIM AS INTEGER x = RND * 10 'FreeBasic supports dim and assign in 1 statement
'DIM AS INTEGER y = RND * 10 'FreeBasic supports dim and assign in 1 statement
DIM x AS INTEGER: x = RND * 10 'PowerBasic requires two statements for same thing
DIM y AS INTEGER: y = RND * 10 'PowerBasic requires two statements for same thing
'PRINT x; " +"; y; " ="; addnumbers( x, y ) 'with inbedded dynlibrary support, easier
CALL DWORD fAddr USING AddNumbers( x, y ) TO iResult 'you call the function using declare & parms
PRINT x; " +"; y; " ="; iResult 'PowerBasic is just a bit more awkward
'' Done with the library; the OS will automatically unload libraries loaded by a process
'' when it terminates, but we can also force unloading during our program execution to
'' save resources; this is what the next line does. Remember that once you unload a
'' previously loaded library, all the symbols you got from it via dylibsymbol will become
'' invalid, and accessing them will cause the application to crash.
''
'dylibfree library 'syntax not supported by PowerBasic
FreeLibrary library 'PowerBasic does it with FreeLibrary API
DO WHILE INKEY$=""
LOOP
#IF %DEF(%PB_CC32) OR %DEF(%PB_WIN32)
END FUNCTION
#ENDIF
mydll.bi:
''
'' all FB functions are by default STDCALL on Windows and also PUBLIC,
'' so nothing else has to be added (note that FBC will not include "mydll"
'' to linker's list when creating the DLL, only when using it on an .exe)
''
DECLARE FUNCTION AddNumbers LIB "i:\pbwin90\samples\FreeBasic\mydll.dll" ALIAS "AddNumbers" ( BYVAL operand1 AS INTEGER, BYVAL operand2 AS INTEGER ) AS INTEGER
Since most of the changes had to be made in testload.bas, it is the only source file that is heavily commented. The same reasons given there will apply to the other source files.
If you have PB/CC instead of PB/Win, you will probably immediately groan as you note the #COMPILE DLL metastatement in mydll.bas. PowerBasic's Console Compiler does not produce DLLs, right? So you feel that leaves you out. Well, here is an eyeopener for you. First, change that #COMPILE DLL to #COMPILE EXE if you want, or just remove it altogether. Then drop to the bottom of the source code and add two lines of text:
FUNCTION MAIN
END FUNCTION
Now you can compile it. Yes, it creates an exe file, fut the use of EXPORT in your defined FUNCTIONs and SUBs means that the system can link to them externally. You can even put code in FUNCTION MAIN and use it as a regular EXE file, or not , as it suits you. You will have to give the EXE extention in the LIB clause when trying to DECLARE any functions or subs for use in other programs.
Make sure you either change the #INCLUDE and library load statements to provide the path to the folder where you place these files. or use the PowerBasic's IDE Options button under Windows on the toolbar, select the Compiler Tab, then modify the PB Include path so that the folder will be searched (note the 3 dot (...) button on the right side for making change here).
Once you have played with this for awhile, you will undoubtedly feel that the
FreeBasic syntax and simpler form puts it somewhat ahead of PowerBasic. I
won't argue with that finding, but don't rush to condemn PowerBasic. It has
many strong features going for it, including extended precision floating point,
currency types, along with the types associated with FreeBasic. However, the dynamic, variable length strings are organized in memory in a different way. Jose posted this information earlier for FreeBasic:
128 typedef struct _FBSTRING {
129 char *data; /**< pointer to the real string data */
130 int len; /**< String length. */
131 int size; /**< Size of allocated memory block. */
132 } FBSTRING;
That structure is what is reported on by SIZEOF(), which is 12 for FreeBasic.
Each field is 32-bits (4 bytes) long, and SIZEOF() is giving you the size of the
structure as 12 bytes. PowerBasic, on the other hand, has this internal structure for the dynamic, variable length strings:
length AS DWORD 'length of string (number of bytes)
location AS DWORD 'corresponds to STRPTR() for string
But in PowerBasic, SIZEOF() returns 4, whereas this structure has 8 bytes. The PowerBasic Help file explains this by stating that SIZEOF() returns the length of the handle for the string, which is an internal representation used by PowerBasic.
The question is, are the DLLs created by FreeBasic and PowerBasic capable of being used with the other language? Now starting out, I depended on several sites for downloading and installing FreeBasic, and the same for FBEdit, and I set up my own directory treee with FBEdit under FreeBasic, then found that the FBEdit bundle I later downloaded had FreeBasic installed under it. The mishmash that resulted was proving problematic, and I finally deleted the whole directory tree, then installed the FBEdit bundle in its place. Everything seems to be working better as a result. Now before that time, I had already tried to use DLLs produced in the two languages with the other, and it wasn't working. So using the code above, I am in the process of trying again, now that everything seems to be working better.
My time trial on PE Explorer ran out today, but rather than pay $129 to get a license, I decided to try DumpPE, which came up on a thread I was reading. I searched the Internet for it, then found out it had been bundled with MASM32,
something I had acquired earlier. So in searching my own PC, I found DumpPE located at \masm32\bin\. typing DumpPE /?, I got some basic info on the program, but it turns out you just need to use a filename to have it perform the dump to a console screen. Only the screen closes immediately. So here is what I did. I wrote a BAT file and put it on my desktop, and this is what it has in it:
i:\masm32\bin\dumppe %1 > dumpdll.txt
type dumpdll.txt
pause
Now I just grab the DLL file in question, and drag and drop it onto the BAT file icon, then click on "Open With" when the dialog box comes up. I can then walk the resulting file with the mouse.
Like the PE Explorer, DumpPE shows that in the FreeBasic version, then function name AddNumbers in the DLL shows up as AddNumbers@8. Using WinDiff, another available download that compares the difference between two files, I tried to see how many differences there were in the dumps from the two DLLs, one created by FreeBasic and the other by PowerBasic. There were a great many differences, so that did not prove fruitful. What I am going to do now is see if the Test.bas program in one language will work with the DLL made from the other. I found a post on PowerBasic about DEF files for Microsoft languages that may have a bearing here, and will research that further if there proves to be a problem still.
I'm no expert in this stuff, so expect errors as I go along, and I will continue to make corrections as I learn of those errors.