Creating PB Userlibraries in C++

Just starting out? Need help? Post your questions and find answers here.
A.I
User
User
Posts: 16
Joined: Wed Jul 27, 2005 2:09 pm
Location: Finland

Creating PB Userlibraries in C++

Post by A.I »

This subject is so old, but still active. Despite my many attempts, I just fail finding a good and clear tutorial on how to extend PureBasic with C++ code. Of course I first used the forum search, but found only one topic that could provide actual help:

viewtopic.php?t=15150

Unfortunately, I still have problems I can't overcome. I'm using VC++, and try to make a simple userlib to see if it's actually possible. The code is based upon an example found at the link above.

So I naturally started an empty (simple) dynamic DLL-project, and write this:

PureTest.cpp

Code: Select all

// PureTest.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    return TRUE;
}



#include <windows.h> 

////////////////////////////////////////////////////////////////////////////// 
// Step (1) Include PB's debugger stuff. 
//          With SendDebuggerError() the debugger can be stopped with some 
//          warning message. Useful in "_DEBUG" functions. 
////////////////////////////////////////////////////////////////////////////// 

char *PB_DEBUGGER_ErrorDescription; 
HWND  PB_DEBUGGER_Window; 
int   PB_DEBUGGER_Control; 


void SendDebuggerError(char*); 

void SendDebuggerError(char *text) 
{ 
   if (PB_DEBUGGER_ErrorDescription == 0) 
   { 
      PB_DEBUGGER_ErrorDescription = text; 
      PB_DEBUGGER_Control = 2; // Halt the PureBasic program flow... 
      SendMessage(PB_DEBUGGER_Window, WM_COMMAND, 6, 1); 
   } 
} 
////////////////////////////////////////////////////////////////////////////// 

////////////////////////////////////////////////////////////////////////////// 
// Step (2) Write the C functions you want to export to PureBasic. 
//          These functions must be prefixed with "PB_" for PureBasic to 
//          recognize them. 
// 
// Note:    Functions have to use STDCALL calling convention. 
////////////////////////////////////////////////////////////////////////////// 
#define PBCOMMAND   extern  

PBCOMMAND int _stdcall PB_SampleCFunction(); 

PBCOMMAND int _stdcall PB_SampleCFunction() 
{ 
          return 1234; 
} 

////////////////////////////////////////////////////////////////////////////// 
// Step (3) Write the C debug functions. They must be suffixed with "_DEBUG". 
//          This function for example will raise an error in PB's debugger, 
//          if one of the three arguments are NULL (0). 
// 
// Note:    Functions have to use CDECL calling convention and must use void 
//          as return value. 
////////////////////////////////////////////////////////////////////////////// 
#define PBDEBUG     extern void 

PBDEBUG PB_SampleCFunction_DEBUG(); 

PBDEBUG PB_SampleCFunction_DEBUG() 
{ 

}
Nothing unusual, I successfully create a dll. Also, I double-checked that this DLL works in PB with the OpenLibrary()-command.

Then I open the LibraryMaker.exe, and provide it a Desc-file as follows:

PureTest.Desc

Code: Select all

; 
; Langage used to code the library: ASM or C 
C 
; 
; Number of windows DLLs our library needs. 
; This is used to tell the PureBasic compiler / 
; linker which libraries are needed to link with. 
1 
MSVCRT 
; 
; Library type (Can be OBJ or LIB). 
OBJ 
; 
; Number of PureBasic libraries needed by your library. 
; (additional PureBasic libraries that are needed for linking). 
0 
; 
; Help directory name. 
SampleLib 
; 
; Library functions (FunctionName, Arg1, Arg2, ...); 
; First the function name. Its the name of the C function 
; that should be exported to PB, without the "PB_" Prefix 
; used in C. After function name a comma and a list of 
; arguments seperated by comma aswell. in parentheses 
; a argument description that is showed by the PureBasic IDE 
; as quick help. After the parentheses a general description 
; can follow. 
; 
; Translation table for some arguments: 
; ANSI C         | PureBasic 
; -------------------------- 
; int *          |  Long 
; byte *         |  Long 
; int  (32 bits) |  Long 
; char *         |  Long / String 
; long (32 bits) |  Long 
; float          |  Float 
; double         |    -  (must be casted to float in C for now) 
; char           |  Byte 
; void           |  None 
; short(16 bits) |  Word 
; 
SampleCFunction () - My Sample Function. 
; 
; Return value: 
; Type of return value and flags for: 
; 'InitFunction' (a function which is called automatically 
; at the start of a PureBasic program), 
; 'EndFunction' (called at the end, for cleaning) 
; and 'DebuggerCheck' (a debugger handler function for this command.) 
Long | DebuggerCheck
It goes through, no error messages. I restart PB compiler, and SampleCFunction is now recognized as PB-command. Then I try to execute this code:

PureTest.pb

Code: Select all

MessageRequester("",Str(SampleCFunction()))
If debugger enabled, I get:

PureBasic - Linker Error
POLINK: warning: Multiple '.data' sections found with different flags (0xc0000040 and 0x60000040).
POLINK: error: Unresolved external symbol '_PB_SampleCFunction_DEBUG'.
POLINK: error: Unresolved external symbol '_PB_SampleCFunction'.
POLINK: fatal error: 2 unresolved external(s).

If debugger disabled, I get:

PureBasic - Linker Error
POLINK: warning: Multiple '.data' sections found with different flags (0xc0000040 and 0x60000040).
POLINK: error: Unresolved external symbol '_PB_SampleCFunction'.
POLINK: fatal error:1 unresolved external(s).

I've tried to solve this for several hours now, and I'm falling into despair. Since there are userlib-writers here any help would be appriciated.
Specialized in "AI Programming Games".
A.I
User
User
Posts: 16
Joined: Wed Jul 27, 2005 2:09 pm
Location: Finland

Post by A.I »

OK, did some further tests. I decided to go even simplier. Here's the C-code (ignore stdafx.h):

Code: Select all

#include "stdafx.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    return TRUE;
}


extern "C" int _stdcall PB_SampleCFunction() 
{ 
          return 1234; 
} 

extern "C" void _stdcall PB_SampleCFunction_DEBUG() 
{ 

} 
And the .def -file:

Code: Select all

LIBRARY PURETEST

EXPORTS
PB_SampleCFunction @1
PB_SampleCFunction_DEBUG @2
And the .Desc -file:

Code: Select all

C 
1 
MSVCRT 
LIB
0 
Extension1 
;
SampleCFunction () - My Sample Function. 
Long | DebuggerCheck
This goes through beautifully without errors and the userlib is created to <PureLibraries>

Now, when I try to run:

Code: Select all

MessageRequester("",Str(SampleCFunction()))
.. I get:

PureBasic - Linker Error
POLINK: warning: Multiple '.data' sections found with different flags (0xc0000040 and 0x60000040).
POLINK: error: Unresolved external symbol '_PB_SampleCFunction_DEBUG'.
POLINK: fatal error: 1 unresolved external(s).

If I turn off the debugger, I just get an error message saying that PureTest.dll can't be found. So it actually (tries) to run.

Erm.. Am I missing something here? I thought that LibraryMaker creates userlibs so WE WON'T NEED THE DLL??!

Please, someone, elaborate me how to get this to work. I just need a simple example on how to create a simple userlib for PB.
Specialized in "AI Programming Games".
A.I
User
User
Posts: 16
Joined: Wed Jul 27, 2005 2:09 pm
Location: Finland

Post by A.I »

Noticed something...

1) Do not put .def-file for the project
2) Build the DLL in VC++
3) VC++ now generates a .OBJ file - not .LIB
4) Use LibraryMaker.exe
5) The library will be made, reset PB Compiler or IDE

And tadaa.. it works.. but only if the debugger is disabled. Any advice on that?
Specialized in "AI Programming Games".
KarLKoX
Enthusiast
Enthusiast
Posts: 681
Joined: Mon Oct 06, 2003 7:13 pm
Location: France
Contact:

Post by KarLKoX »

It is really easy to create userlib with C++ :

1 - Link your project as Multithreaded DLL,
2 - Add MSVCPRT.LIB into your project,
3 - Add MSVCRT in your desc file.
"Qui baise trop bouffe un poil." P. Desproges

http://karlkox.blogspot.com/
A.I
User
User
Posts: 16
Joined: Wed Jul 27, 2005 2:09 pm
Location: Finland

Post by A.I »

All done, but I still can't get this to work if the debugger is ON. I keep getting this error every time I hit F5 and debug enabled:

POLINK: warning: Multiple '.data' sections found with different flags (0xc0000040 and 0x60000040).
POLINK: error: Unresolved external symbol '_PB_SampleCFunction_DEBUG'.
POLINK: fatal error: 1 unresolved external(s).

Running without debugger executes OK, no problem there. Now, I understand there's a special calling convention for the PB debugger. Can anybody post a little C++ snippet to demonstrate?

EDIT
Never mind. I managed to get it to work.
I only needed to discard the _stdcall from the _DEBUG -function because __CDECL is the default calling convention. Also, included

void SendDebuggerError(char*);

and its corresponding function.

And now it seems to operate properly in both debugged and "publish" mode :)
Specialized in "AI Programming Games".
vanleth
User
User
Posts: 79
Joined: Sat Jun 28, 2003 4:39 am
Location: Denmark - Valby

Post by vanleth »

Thanks A.I , it's good to have some working experience posted on creating libraries, for us who havn't dared that exercise yet.
A.I
User
User
Posts: 16
Joined: Wed Jul 27, 2005 2:09 pm
Location: Finland

Post by A.I »

Tutorial

This is a simple tutorial on how to create a simple Userlib in Microsoft Visual C++ 6.0. We'll make a DLL that has only one function: HelloWorld(), which takes one integer parameter and returns it double back to PureBasic. For example, if you passed integer 6 it would return 12 and so on.

I'm a bit of a beginner in C++ as well and there's probably many PB users that would do things differently. Feel free to correct my mistakes or post advices.

This tutorial will advance step by step just in case you don't have that much knowkedge on VC++ and/or its user interface.

Step 1: Create a new project

1) Open VC++
2) Click File/New
3) Select Win32 Dynamic-Link Library
4) Choose a project name. I use "PBHello" here.
5) You'll be asked what type of DLL project you'd want create. Select "A simple DLL project"

Now, you should have a single project created for you. At the left you see ClassView and FileView. Click on FileView and then the project name (PBHello). DoubleClick on PBHello.cpp so we can start coding...

Step 2: Code skeleton

Add these (leave the APIENTRY):

Code: Select all

char *PB_DEBUGGER_ErrorDescription; 
HWND  PB_DEBUGGER_Window; 
int   PB_DEBUGGER_Control; 


void SendDebuggerError(char*); 

void SendDebuggerError(char *text) 
{ 
   if (PB_DEBUGGER_ErrorDescription == 0) 
   { 
      PB_DEBUGGER_ErrorDescription = text; 
      PB_DEBUGGER_Control = 2; // Halt the PureBasic program flow... 
      SendMessage(PB_DEBUGGER_Window, WM_COMMAND, 6, 1); 
   } 
}
Step 3: Project setup

In order to link and Build the DLL correctly (from the view of PureBasic) we need to set a few things up.

1) Select the project PBHello from the list on the left
2) Go Build/Configurations
3) Select "Win32 Debug" and remove it
4) Confirm and Close
5) Go Project/Settings
6) Select C/C++ tab
7) Select Category-combo/Code Generation
8 ) Set Calling Convention to __cdecl (if it's not)
9) Set Runtime-library to "Multithreaded DLL"
10) Select the Link-tab
11) Add MSVCPRT.LIB to "Object/Library modules"
12) The output file name can be just "PBHello.dll"
13) OK


Step 4: Writing the code

Each procedure/function must come in pairs: the procedure itself and its corresponding debug-version for PureBasic Debugger.

The actual procedure must use _stdcall in order to work. However, the debug function should use __cdecl convention (but don't you worry about that, we've already chosen it to be the default).

Also, the debug-function must be the type of void, so it wouldn't return anything. I'm not exactly sure how the debug thingy works, but I just check if the parameters are valid, or something... You can simply write an empty debugger function.

Always remember to put PB_ prefix to the function names. Write the following code:

Code: Select all

extern "C" int _stdcall PB_HelloWorld(int x) 
{ 
	return x*2; 
} 

extern "C" void PB_HelloWorld_DEBUG(int x) 
{ 
} 
Notice:

1) the PB_ prefix
2) _stdcall for the actual functoin (omitted in the debugger function)
3) debugger function postfix _DEBUG
4) debugger function void type (and not returning a value)

Step 5: Compile

Save and hit F7 to build. There should be no errors.
You could get warning "all references to "USER32.dll" discarded by /OPT:REF" but you can ignore it (Hit F7 again).

Go to:
"C:\Program Files\Microsoft Visual Studio\MyProjects\PBHello\Release"
(or whatever the path is)

..and see there are at least PBHello.dll and PBHello.obj

Step 6: The .Desc-file

Create a new file called PBHello.Desc into that directory.

Write this:

Code: Select all

C 
1 
MSVCRT 
OBJ 
0 
Extension1 
; 
HelloWorld, Long (integer) - Doubles the given integer
Long | DebuggerCheck
The Readme.txt has description of the .Desc file format, but I find it quite a mess (no offence, Fred ;) ) If you want, you can take a look at it here:
viewtopic.php?t=15150

Our Desc-file suggests that PBHello -function requires one parameter (Long), and returns Long. For some reason I can't compile Userlibs that omit the "|DebuggerCheck" -part, so I wrote it here, too.

If you wish to omit the quick command help, just discard all starting from "-" (still include the parentheses).

Step 7: LibraryMaker

You can find it in PureBasic\Library SDK

The objects path should be the same (notice the tailing backlash):
"C:\Program Files\Microsoft Visual Studio\MyProjects\PBHello\Release\"

Enable compression, it's cool.

Hit Start and select the Desc-file from the right directory. If everything went OK, you should get no messages of any kind. Go and see PureBasic\PureLibraries. There should be PBHello. If you wish, you can move it to the Userlibraries -folder.

Step 8: Test

Now, restart PB compiler or the IDE in order to get the PureBasic compiler to recognize our new command HelloWorld.

Test with this code (debugger on and off):

Code: Select all

i=HelloWorld(34)
MessageRequester("",Str(i))
------------------------

There's still lots of questions I need to find an answer. This was my first attempt of creating a Userlib for PureBasic. This is just how I did it. I hope this proofs usefuls for all you who want to start expanding PureBasic with C++ code :)

See ya.
Specialized in "AI Programming Games".
User avatar
IceSoft
Addict
Addict
Posts: 1699
Joined: Thu Jun 24, 2004 8:51 am
Location: Germany

Post by IceSoft »

Maybe we can get an official documentation (like above) from Fantaisie Software how we can generating PB Userlibs written in C(C++)
Last edited by IceSoft on Thu Jan 03, 2008 5:13 pm, edited 1 time in total.
Belive! C++ version of Puzzle of Mystralia
Bug Planet
<Wrapper>4PB, PB<game>, =QONK=, PetriDish, Movie2Image, PictureManager,...
freak
PureBasic Team
PureBasic Team
Posts: 5948
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Post by freak »

There are examples in the LibrarySDK folder for C. C++ should not be much different.
quidquid Latine dictum sit altum videtur
Post Reply