General:
C/C++ DLL's
By: Jack Hoxley (with help from Torsten
Damberg and David Goodlad)
Written: June 2000
Download:
GM_CppDll.Zip
(20kb) GM_DLLDemo
(363kb)
I've been programming in VB for
several years and am a firm supporter of the language; however, every so often
you get a really good idea in your head that needs raw power to be done. Then
you hit the boundaries of the VB language - you've gone so far and it wont take
you any faster. In my experience this comes when you're doing maths, algorithms
and array manipulation (including memory arrays). You just need that extra speed.
Visual Basic is and always will
be the easier and more fun language to write; the equivilant game written in
C++ is much harder, much less friendly to read and will take longer. VB with
the power of directX behind it has become a serious contender for making games;
It's just several small things that limit your creativity, and these are the
things that C++ developers enjoy the ability of using.
Without a doubt C/C++ and assembler
can wipe the floor with VB's array and mathematics functions. There is a way
however to keep the bulk of your code in VB and use another (faster) language
to the time-critical maths functions. The easiest way is to write a simple C/C++
DLL and link your VB project to it.
This is what this tutorial will
be discussing. There are two points you will need to bare in mind first though:
- You will need a basic knowledge
of C/C++. I'm no expert of the language, but I can just about pull off most
of the things I try. This tutorial can't and won't attempt to teach you C/C++,
It will demonstrate how to get started - the rest is up to you.
- You will need a C++ compiler.
All the examples here will be saved in Visual C++ format, I do believe that
the code will work in other compilers, but I have no way of checking. For
the record, I will be using Visual C++ 6.0 professional. Everything will work
fine in this version and the enterprise edition, but I'm not sure how it will
in the learning edition.
If you do some simple tests with
equivelant VB and C++ code with full optimizations you will find that C++ can
sometimes double VB's speed:
C++ DLL; 307,200 calculations: <=1ms
VB DLL; 307,200 calculations: 170ms
C++ DLL; counting 0 to 1000: <1ms
VB DLL; counting 0 to 1000: 28ms
For the record, these tests were done on an AMD 500mhz computer.
As you can see, in the first test the C++ DLL was an amazing 170x faster. In
the second test it was 28x faster - still a substantial improvement. These tests
cannot be taken as standards as they were done once on my computer - and are
likely to be different from computer to computer. Even if these differences
aren't entirely accurate, with even a wide margin you can see that C++ out-performs
VB quite dramatically.
Now we can get onto some code.
The first part is understanding the differences in variables between C/C++ and
Visual Basic. You will need to bare this in mind when it comes to writing your
DLL.
C/C++
Variable name |
Visual Basic
Variable Name |
Short |
Integer |
Long |
Long |
LPLong |
Long |
Float |
Single |
Double |
Double |
unsigned
__int8 |
Byte |
BStr |
String |
This will be needed when you
write the entry point for your DLL and when you declare you're DLL in VB. For
example:
void _stdcall DoSomething(short
Val); In C++ becomes:
Private Declare Sub DoSomething Lib "dllname.dll" (ByVal Val as Integer)
Note the ByVal part - this is
important. If you didn't have it here, VB would pass "0", or Nothing.
As a general rule you'll need byVal for everything, except pointers that need
ByRef.
You can then use the table above
to create the function that you need. The next part of this tutorial will deal
with the C++ side of things; If you don't understand this you're best not going
any further.
- Launch Visual C++ developer
Studio - This should be easy
- Create a new "Win32 Dynamic
Link Library" project from the new project library
- When the AppWizard screen
appears, select "Empty Application" - We want to add the files ourself.
- Visual C++ will then present
you with an empty project. On the project menu, select add file.
- In the dialog select "C++
source File" and give it a name
- You will now have a blank
document to play with.
- At the same time create another
blank document and save it as "projectName.Def" - replace ProjectName
with the real project name. This will be discussed later
Now open up the blank C++ source
file and add this code:
#include "Windows.h"
//Any other files that you want can go here...
//We must prototype all the functions that we wish
to use.
short _stdcall Multiply(short Num1, short Num2);
//Now we can create the function
short _stdcall Multiply(short Num1, short Num2)
{
//This code here is executed when VB calls this
function
return Num1*Num2; //Multiply the numbers and return
them
} |
|
|
|
Okay; so this DLL would do nothing
useful whatsoever - VB can do multiplications on it's own. Although C++ may
well do it faster; when we only want it done once we'll probably slow the program
down as we have to call the DLL, and wait for it to return information.
There is one last part though:
The .Def file. This is important, and can be thought of as a DEFinition file.
LIBRARY dllname
EXPORTS
Multiply |
|
|
|
This is very simple and very
small; but it will get bigger as you add more procedures. You will have to change
the part after LIBRARY to suit your project's name, but that;s it. After EXPORTS
you need to name all of the procedures in your project that you want other applications
to use, currently for us that only includes "Multiply".
Compile you're DLL and copy it
from the folder to the C:\windows\System folder. Note: you may as well do a
straight release version, you're not going to get any Debug data when using
it in VB. The Release version can be 4-7x faster as well.
Now open up VB and start a new
Standard EXE project. One form will appear, open it up in the code window and
add these lines of code.
'This goes in
the declarations section.
Private Declare Function Multiply Lib "dllname.dll" (ByVal Num1 as Integer,
ByVal Num2 as Integer) as integer
Private Sub Form_Load()
'We need a value to hold it.
dim RetVal as integer
RetVal = Multiply(10,170)
'RetVal now holds the number 1700 = 10*170
'We now convert it to a string for output as a message box.
msgbox CStr(RetVal)
End Sub |
|
|
|
Done! You now have a working
DLL. It is quite easy to modify this to suit your needs - but you'll need a
little bit of C++ knowledge. A word of warning; if you generate an error it
may well crash VB - losing everything. Save your project first.
Using Strings
Having a function that returns strings has two main uses as far as I can
see:
1. Encryption - You could write a DLL that encrypts a string or decrypts a string.
This encryption could be quite complicated, as C++ will be able to run through
it very quickly
2. File access; you could pass it a Filename and it could return an entry in
the file; or you could use it with the first option and make it decrypt an encrypted
level file and give you're program the data it wants.
Open the DLL project that you
should just have created - and open the source file.
//Remember to
prototype this function
LONG _stdcall GetStringFromDLL ( BSTR S )
{
int i;
LPSTR pS;
pS = (LPSTR) S;
for (i=0; i<26; i++)
{
*pS++='A'+i;
}
return 26;
} |
|
|
|
Add an entry in the .Def File
to allow VB to access this function. Then open up you're VB project and add
this code in:
'You'll have
to declare the DLL in the declaration section - You should be able to do
this.
'The VB string must be big enough to hold the data that the C++ DLL will
put in this. The only way of
'doing this is to fill it with stuff - the C++ DLL will then overwrite whatever
rubbish we put in it.
Dim S as String
Dim length as Long
S = Space(200) 'you should be more precise; but we'll
give ourselves a 200 character limit
Length = GetStringFromDLL(S)
'Output what the DLL returned...
MsgBox (Left$(S,Length)) |
|
|
|
Using Arrays
This is the important part, and also where it gets fun. The example project
above is fairly useless; it does nothing new, and it wouldn't give us any speed
advantages. Array manipulation is where this method will shine. Through arrays
you could pass hundreds of thouands of values, even millions. Arrays can also
represent picture data - in particular, you could pass a C++ DLL the array of
data behind a DirectDraw surface (GetLockedArray) - and allow C++ to modify
your surface. This suddenly opens up the door to an amazing amount of possibilities.
This example will show you how to invert the colours of A DirectDraw surface
(in theory). It also doubles up as a benchmarking program. This First part is
the C++ side of things:
#include "windows.h"
//Had to change this to stop it
//messing up the HTML!!
//This is our Prototype
//Note the use of pointers
void _stdcall BltNot(unsigned __int8 *myarray, short width, short height);
//This is our actual procedure.
void _stdcall BltNot(unsigned __int8 *myarray, short width, short height)
{
int x=0; //Create us a variable
x= width * height; //This is the total number of entries
in the array.
unsigned __int8 Val; //Temporary Value
for(int i = 0; i x; i++) //Run a loop through the
data. There should
//be a less-than sign in the prev. line - but it messed
up the HTML...
{
Val = *myarray; //Copy our variable
Val = 255 - Val; //Invert our variable
*myarray = Val; //Put our variable back
myarray++; //Increment the address by one.
} |
|
|
|
Now; accessing the array probably
isn't quite what you expected. So I'll explain. In memory, the 2D array:
0 1 2
0 1 2
0 1 2
is represented like this:
012012012
or, -FirstRow-SecondRow-Thirdrow-
This makes it slightly difficult to access it. You could, if you knew C++, write
a loop that did it as a
for(x)
for(y)
loop, but we're not going to do that here - just for simplicity. We'll just
change the whole lot in order - which is fine if you don't mind what the X/Y
position the entry represents.
You cant actually pass an array
in a DLL call. Mostly on the grounds that VB will include lots of junk other
languages wont like. Instead we use a pointer, this points to where the array
starts - it says "This is where the array starts, keep reading from this
point onwards". This means that we rely on the VB programmer to give us
the correct size - overwise we may screw up.
When we get into our loop it's
fairly simple. On the first run we'll get the value behind the first value -
the one that the function is given; then we just keep on going through incrementing
the memory address by one each time.
Now onto VB. This is fairly easy.
When it comes to declaring the the DLL use this:
private declare sub BltNot
lib "Bltnot.Dll" (ByRef myarray as byte, byVal width as integer,
ByVal height as integer) |
|
|
|
It is important that you pass
the "myarray" as ByRef - otherwise it wont work. ByRef simply means
By Reference - ie, give the DLL it's address. As far as both parts are concerned
we are only passing one variable - that's why at no point is there an array
declaration.
When it comes to using the DLL,
use this:
BltNot Array(0,0),640,480 |
|
|
|
We want the myarray variable
to be where you start from; most cases this will be the first variable in the
array (0,0). You could set it so it started elsewhere, but for now we'll let
it start at the beginning. Then we specify the height and width. These are 1-x
entries, not 0-x. Basically, start counting as normal (we count from 1), not
like computers which count starting from 0. Be careful here - if you specify
a number greater than the size of the array you'll crash everything.
You could quite easily modify
this to take a 3 dimensional array, or do many other things - it's up to you
to experiment; but be careful - one error and you could say goodbye to your
computer (until you restart).
Download the example from the
top of this page, or get it from the downloads page.
Make sure you read the Info.txt file included. Note, the C++ code is not included,
but it is all on this page here. NB: there is a demo application to show you
how to use the DLL with DirectDraw surfaces.
|