How to wrap a DLL

Previous topic - Next topic

Kitty Hello

keywords:
DLL, wrapper, INLINE, DECLARE

Hi, and wellcome to something veeery sophisticated, which will turn out to be easy as sending an email, later: Wrapping an external DLL library to be used with GLBasic.

First, we must descide which DLL we might use. As I don't have any other one here, I suggest we take a look at some built in Windows(R)(tm) dlls, and in order to see something quickly we wrap the MessageBoxA function.

OK, first we need to know a few things:
- which dll contains that function
For our case, that's easy. Just look here:
http://msdn.microsoft.com/en-us/library/ms645505.aspx
Which says:
Quote
MessageBox Function

Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message, such as status or error information. The message box returns an integer value that indicates which button the user clicked.

Syntax

    int MessageBox(     
        HWND hWnd,
        LPCTSTR lpText,
        LPCTSTR lpCaption,
        UINT uType
    );
...
Function Information
    Minimum DLL Version:   user32.dll
    Header:   Declared in Winuser.h, include Windows.h
    Import library:   User32.lib
    Minimum operating systems:   Windows 95, Windows NT 3.1
    Unicode:   Implemented as ANSI and Unicode versions.
- what are the parameters and the return value
We have a set of parameters here like:
HWND , LPCTSTR and UINT.
You can google for them (search for "typedef HWND"), and will find that all parameters can be limited to a few basic types:
strings: char* or const char*. "const char*" means a string, that's contents will not be changed inside the dll function.
integers: 8 bit(char), 16 bit(short), 32 bit(long or int - it's the same), [optional: unsigned]
floating points: 32bit (float), 64 bit(double)
pointer to something: mostly (void*)
We will learn more about pointers, later. No worries, just easy basics.

So we can reduce this parameter list to the basic types:
void*, const char*, const char*, unsigned int
and the return value is an "int".

Aces.

- The function name in the DLL
Did you notice
QuoteUnicode   Implemented as ANSI and Unicode versions.
? Microsoft names their functions with "A" at the end for ANSI and "W" for Unicode. We need the ansi version. Sometimes functions can look totally different, though. So in order to be safe, we download the DependencyWalker from here:
http://www.dependencywalker.com/
and open the "user32.dll" from the system32 directory.


At the left side, they cyan box informs us, that this function has a "C" linking (as oposed to C++ linking)., thus we should wrap our "DECLARE" statement in an
Code (glbasic) Select
extern "C"{ DELCARE... } later.

It's not obvious if the calling convention is "cdecl" or "stdcall" from the dependency walker. I _know_ that all Microsoft DLLs are stdcall, and if you can load a DLL from VisualBasic, it _must_ be stdcall, so try this first. If it crashes, try with a cdecl convention later.

So, fine. We now have everything we need to get started!

Loading that function
Open your GLBasic project, and insert a new source file in the file tabs. Make sure you use a separate file for each dll you wrap, to make things a bit more modular.
Now type this code in the new, 2nd file of your project:
Code (glbasic) Select

// A MessageBox wrapper
INLINE
extern "C" { // it's a C linking, as dependency walker told us
DECLARE_ALIAS(exportMessageBoxA, "user32.dll", "MessageBoxA", (void*, const char*, const char*, unsigned int), int);
} // close the extern "C"
ENDINLINE

The DECLARE_ALIAS statement (notice to write it all capital letters) takes the name of the function to be used in the GLBasic context, then the dll name, then the name of the function in the dll. This can be something complicated like "_kungfu@12" for other calls - just take the name from the dependency walker.

The next argument is the parameter list in braces!. Last parameter is the return value.
For beginners, please use DELCARE_ALIAS instead of DECLARE.

Wrapping it
Now that we loaded the function, we should urge to call it. Therefore, we need a GLBasic function to conveniently call it from our program. Thus we make the wrapper function:

Code (glbasic) Select

FUNCTION Win32MessageBox: text$, caption$

ENDFUNCTION

Make sure, that the function name does not equal the first parameter of the DECLARE instruction, or you will have quite some fun with the compiler.

Now we must fill that function with our wrapped dll.
Code (glbasic) Select

INLINE // of course, GLBAsic does not know about "exportMessageBoxA" - only INLINE does
   if(exportMessageBoxA)
      return exportMessageBoxA(0, text_Str.c_str(), caption_Str.c_str(), 0);
ENDINLINE

Whoa! Waitaminute!
This is a lot of new stuff for you, so we see ein detail:
Code (glbasic) Select
INLINE
well, GLBasic does not know about "exportMessageBoxA", but the INLINE C++ part does, so we must go there.
Code (glbasic) Select
if(exportMessageBoxA)
exportMessageBoxA is a pointer to a function that you created with the DECLARE statement above. GLBasic does the implementation and loading automatically for you. However, if you have a typo, the function cannot be loaded. Then the pointer has the value "0", which means, if you call the function this pointer is pointing at your program will sort of "inform" you about it. :noggin:

So, to be sure that the function was loaded correctly, we check if it's a valid pointer and then call the function with
Code (glbasic) Select
exportMessageBoxA(
Last step is to provide the arguments.

Code (glbasic) Select
text_Str.c_str()
OK, this is a bit hard to explain now. The GLBasic string "text$" is in INLINE called "text_Str". The $ gets "_Str". If you have a number, there's no such problem. Next, the GLBasic string is of type "DGStr", which is a class needed to have you write things like:
a$= "test" + 5 + "foo:" +foo().
Very convenient for you, but in order to get a const char* to the first character, you need to call the "c_str()" function of that class. Just do it, K?
Another thing is, that if you need the char* to this string, you should do:
Code (glbasic) Select

my_Str.Alloc(1024); // make sure that string has space for 1024 bytes
GetTempPathA(my_Str.GetStrData() );
my_Str.CalcLen(); // have GLBasic find the '\0' character and internally set the string length -> if you manipulate with GetStrData, this must be called!


The MessageBoxA function takes a HWND as the first parameter, which is a pointer to the parent window of that message box. You can use 0 here to say the desktop is the parent. If you want to refer to the GLBasic window, the HWND pointer is gained by calling:
Code (glbasic) Select

void* hwnd = GLBASIC_HWND();
  exportMessageBoxA(hwnd, "test", "test", 0);


The last parameter is the MessageBox appearance. You need a constant integer for this, which you can get my googling for: "#define MB_OK", which turns out to be 0.
Make sure you end the call with a ';', but don't do a ';' after the "if" statement.

So, now we can call that wrapper from our program:
Code (glbasic) Select

Win32MessageBox("Test\nText", "Yippieh!")


I think that should be enough to get you started. When you experience trouble, the forum's full of people willing to help you out.

If someone might translate this into the German forum, I'd be very thankful.








bigsofty

Excellent little tutorial... as a non C++ programmer I find this area the most confusing.

Many thanks,


Ian
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

Kitty Hello

DLL search order:
-program directory
-system directory
-Windows directory
-current directory (SETCURRENTDIR)
-PATH environment variable (left to right)


Leos

Sorry, but I couldn't make it work :/ could you give me a code example for this DLL for instance?
(I'm trying to follow this tutorial but still doesn't work, I'm sure I'm missing something...)
Code (glbasic) Select
;//its Purebasic code

ProcedureDLL MyTest(arg$)
   MessageRequester("MyTitle", arg$)   
EndProcedure


I'ts not decorated inside the dll, I think... it's just "MyTest"
Leo.

GLBasic fan!

MrTAToad

If I remember correctly, with PureBasic you use ProcedureCDLL

Leos

It's working, thank you!
Leo.

GLBasic fan!

Gary

Could someone tell me where I am going wrong in this code? I thought I had followed all the example properly

Code (glbasic) Select
// --------------------------------- //
// Project: test
// Start: Tuesday, May 26, 2015
// IDE Version: 12.308


// SETCURRENTDIR("Media") // go to media files

INLINE
// A MessageBox wrapper

extern "C" { // it's a C linking, as dependency walker told us
DECLARE_ALIAS(exportMessageBoxA, "user32.dll", "MessageBoxA", (void*, const char*, const char*, unsigned int), int);
} // close the extern "C"

ENDINLINE

main:


FUNCTION Win32MessageBox: text$, caption$
INLINE // of course, GLBAsic does not know about "exportMessageBoxA" - only INLINE does
   if(exportMessageBoxA)
      return exportMessageBoxA(0, text_Str.c_str(), caption_Str.c_str(), 0);
ENDINLINE
ENDFUNCTION

FUNCTION main:
Win32MessageBox("Test\nText","Hello")
ENDFUNCTION


but it compiles with these errors

C:\Users\Gary\AppData\Local\Temp\glbasic\gpc_temp0.cpp: In function `int __GLBASIC__::__MainGameSub_()':
C:\Users\Gary\AppData\Local\Temp\glbasic\gpc_temp0.cpp:65: error: expected unqualified-id before string constant
C:\Users\Gary\AppData\Local\Temp\glbasic\gpc_temp0.cpp: In function `DGInt __GLBASIC__::Win32MessageBox(__GLBASIC__::DGStr, __GLBASIC__::DGStr)':
C:\Users\Gary\AppData\Local\Temp\glbasic\gpc_temp0.cpp:175: error: `exportMessageBoxA' was not declared in this scope
*** FATAL ERROR - Please post this output in the forum

kanonet

You either need to place this into a 2ndary gbas file, or you need to close GLBasic's main function. To do so, add a dummy GLBasic function before your INLINE code:
Code (glbasic) Select
FUNCTION Foo:
ENDFUNCTION

INLINE
// your stuff below here

This rule applies to any INLINE code. Now try again. ;)
Lenovo Thinkpad T430u: Intel i5-3317U, 8GB DDR3, NVidia GeForce 620M, Micron RealSSD C400 @Win7 x64

MrTAToad

The code needs to be in a source file and not the main program.  The main function however, should be in the main program.

Gary

cheers guys, thats got the example code running.

Im trying to wrap a 3rd party dll and have a few functions to add

the first one has no parameters passed to it but returns an int. I thought the code would be as follows

Code (glbasic) Select
INLINE


extern "C" { // it's a C linking, as dependency walker told us
DECLARE_ALIAS(BPConnect, "GameSocket.dll", "BP_Connect", (void*), int);
} // close the extern "C"
ENDINLINE

FUNCTION Connect:
INLINE
if(BPConnect)
return BPConnect();
ENDINLINE
ENDFUNCTION


but this fails with the following error
compiling:
CMD: >>"C:\Program Files\GLBasic_v11\Compiler\platform\Win32\Bin\g++.exe" -B"C:\Program Files\GLBasic_v11\Compiler\platform\Win32\Bin" -pipe -O3 -w -c -x c++ -mwindows -I"C:\Program Files\GLBasic_v11\Compiler\platform\Include" -I"C:\Documents and Settings\Owner\Desktop\GL_DLL" -D_WINDOWS_ -DNDEBUG -DHAVE_OPENGL "C:\DOCUME~1\Owner\LOCALS~1\Temp\glbasic\gpc_tempg.cpp" "C:\DOCUME~1\Owner\LOCALS~1\Temp\glbasic\gpc_temp0.cpp" "C:\DOCUME~1\Owner\LOCALS~1\Temp\glbasic\gpc_temp1.cpp"    -DWIN32=1<<
C:\DOCUME~1\Owner\LOCALS~1\Temp\glbasic\gpc_temp1.cpp: In function `DGInt __GLBASIC__::Connect()':
C:\DOCUME~1\Owner\LOCALS~1\Temp\glbasic\gpc_temp1.cpp:42: error: too few arguments to function
*** FATAL ERROR - Please post this output in the forum

Any more tips?

I was given the following C code to connect the main functions

bool UnlimitedConnect(void)
{
   bool RetVal;
   bool connected;
   connected = false;
   DllModule = LoadLibrary( TEXT( "GameSocketDll.dll" ) );
   if (DllModule)
   {
       // Find all of the addresses
BP_StartDLL =(bp_bool_hwnd) GetProcAddress( DllModule, "BP_StartDLL" );
BP_EndDLL =   (bp_bool_void) GetProcAddress( DllModule, "BP_EndDLL" );
BP_Connect =   (bp_bool_void) GetProcAddress( DllModule, "BP_Connect" );
BP_Disconnect =   (bp_bool_void) GetProcAddress( DllModule, "BP_Disconnect" );
BP_EndGame =  (bp_bool_void) GetProcAddress( DllModule, "BP_EndGame" );
BP_UseCredit =   (bp_int_int) GetProcAddress( DllModule, "BP_UseCredit" );
BP_GetCredit =   (bp_int_bool) GetProcAddress( DllModule, "BP_GetCredit" );
BP_AddBank =   (bp_int_int) GetProcAddress( DllModule, "BP_AddBank" );
BP_GetBank =   (bp_int_bool) GetProcAddress( DllModule, "BP_GetBank" );
BP_PayoutAtLimit =   (bp_void_void) GetProcAddress( DllModule, "BP_PayoutAtLimit" );
BP_Payout =    (bp_bool_int) GetProcAddress( DllModule, "BP_Payout" );
BP_IsButtonDown =   (bp_bool_int) GetProcAddress( DllModule, "BP_IsButtonDown" );
BP_SetLamp =   (bp_void_int_bool) GetProcAddress( DllModule, "BP_SetLamp" );

       if (BP_StartDLL && BP_Connect)
       {
          RetVal = (*BP_StartDLL)(hWndThread);
          if (RetVal)
          {
             RetVal = (*BP_Connect)();
             if (RetVal)
             {
                 connected = true;
}
}
}
}
   return (connected);
}

MrTAToad

Try using just (void) and not (void *) - as the latter means it expects a value, it just doesn't care what it is...

Gary

that seems to have worked, strange thing is that it does not seem to be loading the dll file though, getting a DECLARE module not found : game socket.dll error in debug. Ive put the dll in the system 32 folder, program folder, and media folder and still the same error. Very strange

MrTAToad

Is it a 64bit DLL and your OS is 32bit?
It may also require other DLLs too.


Gary

#13
Just tried it on a 32 bit and 64 bit system and same result on both. I was told it does not need another DLL but dependancy walker says otherwise, think I have it running now, cheers

MrTAToad

Good to hear its working  :happy: