A lot of people have been asking how to do this in the Future Software message board
recently. So, out of the goodness of my heart ;), here's a short tutorial showing you
how to do just that. Naturally, I'm not going to explain how to code in C, that takes
a lot of time. Also, you should have a DOS Real-Mode C/C++ compiler capable of generating
OBJ files. A common example would be Borland's Turbo C++ 3.0.
I will outline all of the steps involved in using C routines in QuickBasic and will be taking you
through an example of how to put what we learnt to the test...
An easy way to use our C routines in QB is through a library. Libraries are
simply a collection of sub-procedures and functions. Basically, an EXE file without a "main"
function or any startup code. We build libraries by writing the routines in our favourite language
and then compiling or assembling the code to produce an OBJ (object) file. We can then build a LIB
file using our OBJ(s). Similarly, when we write a standard program, we compile the source code into
an OBJ file. It's LINK.EXE that turns the OBJ into an EXE.
So you can see that all we really have are a bundle of OBJ files (from the library and from our program). The OBJ
file is a standard format. That is, an OBJ file generated by a C compiler is the same produced by BC,
the QuickBasic compiler.
This is easy! All we need to do is write our C routines, compile them
into an OBJ, build a LIB file from it and then link it with
our QuickBasic program. But wait, there's trouble ahead..
Getting around incompatibilities
When calling a Sub or Procedure, it's quite common to pass parameters. For example, when you use PSET
you will need to pass an X and Y coordinate plus a colour.
Unfortunately for us, C uses a different parameter order to QuickBasic.
That is, QuickBasic
passes our arguments from right to left (the X first then the Y and
finally the colour). If PSET
was a C routine it would pass the colour first then the Y then X. To
the programmer, this is completely transparent and you don't have to
worry about it if you're not using mixed-language programming. But we are! It's a
very important issue.
Fortunately, we can get around this quite easily. One way is, when Declaring our external procedures, to use the CDECL keyword. This tells QuickBasic to pass
the parameters backwards as this is a C routine. Another way is to tell the C routine that QuickBasic will be calling it. I like this way better
as it hides the fact you're using C to speed up your program! :D. We can force the C routine to be compatible with
QuickBasic by using the pascal keyword. (It gets it's name from the fact that Pascal passes it's parameters the same way as QuickBasic).
Ok, let's write a function that takes a number, adds 10 to it, and returns the result.
Let's write the C code first:
int far pascal AddTen(int number)
x = number + 10; // add 10
return (x); // return the result
IMPORTANT: You must always remember to use the far keyword in your C procedure. If you do not, your machine will crash or
do something quite nasty.
Copy and paste that into a file. Now, this is important: YOU MUST GIVE THE FILE A .C EXTENSION!. If you give it
a .CPP extension, it will compile in C++ mode which completely mangles our procedure names making them unusable outside of C++.
I'll assume you have named the file:
Now, for Borland Turbo C++ 3.0, we compile the file like so:
tcc -c test.c
This is the command line version of the compiler so we don't need to get involved in any complicated IDE's.
The '-c' option means we want to compile only. Now, you should have the file 'test.obj'.
Now, onto building the .LIB file. Enter this at the DOS prompt:
It will respond with:
Library does not exist. Create? (y/n)
Choose 'yes'. It will then prompt you for Operations. This is where you input your OBJ files. We only have one at the moment
but if in the future you need to add more, simply append a '+' symbol to the OBJ filename. Enter the following:
It will then prompt you with List file:. Just press enter; we don't need this.
You should now have a lib file called 'Mylib.Lib'! Now we can go ahead and use this to link with our
QuickBasic programs, but to use in the QuickBasic IDE, we'll need a QLB file. For this we use LINK:
LINK/QU mylib.lib, mylib.qlb, NULL, BQLB45.LIB
LINK might prompt you for the exact path to BQLB45.LIB. By the way,
BQLB45 is a required run-time library for use inside QuickBasic.
The /QU switch means 'QuickLibrary'.
Ok, let's create the BAS file. Start QuickBasic like so: "qb/l mylib". Enter the following
declaration at the top of the file:
DECLARE FUNCTION AddTen% (BYVAL number%)
Then do a little test:
The output should be 15! If not..don't blame me!
Here, I'll show you how you can very easily pass an array to a C function
and have it read/write it's contents. You can even access the array
in a normal fashion; just as if you had created the array in C.
Well, first off, we don't actually pass the entire array (this goes for any language/compiler).
What we do is pass the start address of the array so both functions have access to the data.
This is called Passing By Reference. (The other way is Passing By Value. In QuickBasic you use BYVAL).
Now if you're a C programmer, you should be familiar with pointers. What we'll do is declare the function's
parameter to be an int far *. For this example, we'll write some C that sets the array's contents to
a few numbers..
void far pascal SetArray(int far *array)
int i; // counter
for (i=0; i<10; i++)
array[i] = i * 2; // standard stuff
Note that the C function can't do any error checking, so make sure you have the
space to write to the array!
Ok, now compile this file and build a library in the same way as the last
example. Start QuickBasic with the appropriate library and enter this code:
DECLARE SUB SetArray(SEG array%)
FOR t% = 0 TO 9
You should see 0,2,4,6,8,10,12,14,16,18 appear on the screen!
The SEG keyword in the declaration tells QuickBasic to pass a far address
of a variable or array. You must always use this when passing data to external functions. Alternatively,
you could also pass the Segment and Offset individually, but this means you have to type more ;)
When calling the function, we pass the first element of the array (MyArray%(0)).
I hope this has helped you some. No doubt, I've left a critical piece of information out ;). If
so send me a mail or leave a message on the Future Software Messageboard and I'll gladly help.
Thanks for putting up with my seemingly random inane ramblings.