Mixed Language Programming - Some assembly required =P
I posted months ago that if I found the time I would write a tutorial on using asm and visual basic in mixed language programming. So it took me a while to get around to it but better late then never. As you may have noticed I'm posting this in the vb6 section, though this method will work in the .net versions as well. I still haven't jumped on the .net ship because I'm waiting on vs.net version 5. Mainly due to much needed upgrades for C++.
If you have ever thought about writing a program that requires speed or needs access to low level hardware, most visual basic programmers think again. The reason is simple, you simply cannot do that in visual basic. No matter how good of a visual basic programmer you are, and how well you have optimized an algorithm, it's slow just because its done in visual basic. Those who do try to do this anyway, end up creating slow and sometimes very buggy code because they try to wield visual basic in a way that cannot be done. A good example of this is direct x sound plugins that some people attempt to create. I talked to a friend of mine just last night, whom was trying to con me into creating some plugins. He said that some of those plugins are done in visual basic and he can simply load them into a program and his processor usage goes up 60% (And he has a fast computer).
When you create a program of this nature, you have two choices.
1.You do this program in another language like C++.
2.You use visual basic and do the critical code in another language and call it from visual basic.
Now if you want to go with #1 that is fine, but I'm sure your going to miss visual basic when you are designing the GUI (I know I do). One thing I wish Microsoft would do is give C++ programmers the ability to design an interface like that. However at the same time allow c++ programmers see the skeleton that creates it, allowing us to tweak it or add to it. I think productivity in C++ would go up 10 fold at least!
Now if we go the #2 way, we can speed up development quite a bit. After all we can do just about all the coding in visual basic, we just have the need to speed up certain parts that are critical to our application. So in a nutshell we decide to use mixed language programming. The idea of mixed language programming comes from people who want to go route #2. In fact Microsoft has been known to go route #2 with their operating systems. They code mostly in C and do some of the critical stuff in assembly. This allows Microsoft to create operating systems so complex as they do today. This wouldn't be possible if they did it all in assembly and would be as fast if they did it all in C.
I've already written one tutorial on how to use mixed language programming with c++ and visual basic, this one is going to show you how to do it with assembly.
Assembly language is a low level language that has a 1 to 1 correspondence with machine language. That means that every instruction you create in asm will correspond to an instruction in machine language. When you do your first hello world in assembly, you will be stunned about how much work is required to do something so simple. That is pretty much what we are going to accomplish with this tutorial, with the exception that we are going to use visual basic to create our front end. This way you will know how to port high speed algorithms in asm into your visual basic projects.
One thing your gong to need of course is an assembler. I highly recommend MASM32, which you can download for free at www.masm32.com. This assembler makes it easier for us to do what we are going to do today, create win32 dlls. Which is what we have to create if we are going to be able to call this from visual basic. It has many built in features and so fort that make working with the api from asm, and the like very easy. This allows us to create the dll itself from a higher level perspective and worry about the details of the functions that it exports in a low level detail.
Now after you have masm32 downloaded, lets create our first dll file. Open up the masm32 editor then go code/Create Dll from the menu. As you can see, they nicely include a template for us which saves us time from having to do that same **** over and over again with each new dll we create. I suppose I should still explain a few things:
.486 – this directive tells what processor family we are going to be programming for. The bare minimal processor will be the 486. I should also point out that the code we run will work on both amd and intel as they both use x86.
.model flat, stdcall – This directive tells what kind of memory model we are going to use. Windows uses the flat memory model. The stdcall is what type of calling convention we are going to use, which deals with function names and how the parameters are passed and of course who gets the fun of cleaning up the stack. Visual basic uses stdcall so we are going to leave that alone. However in different languages, you may need to change that. Some use the c calling convention and others the PASCAL calling convention.
Option casemap :none – This simply tells the assembler to make things case sensitive.
On down the code we see libraries and include files. This is basically to give our asm program access to the win32 api.
Then we come to this curious looking directive:
data?
hInstance dd?
Data? Is a directive we use for uninitialized data. IE: Data that will be decided at runtime. Then hInstance dd is our variable which we declare to be of size double word (Would be a long in visual basic, a 32 bit number).
This is the dll entry point. Here you can put any cleanup or initialization code that your dll requires. As you can see by this function, we make use of some high level features to work with the dll, it makes creating the dll skeleton a lot easier.
At the very bottom of the page you will see this: end LibMain. This should always be at the very bottom. It signals the end of our dll file. Between that and our main function is where our exported functions go (Functions that are going to be called from our visual basic program).
So lets create a function to export:
Code:
TestFunction proc number1:DWORD, number2:dword
mov eax, number1 ;Move number 1 into the eax register
add eax, number2 ;Add the first number with the 2nd.
ret ;Return the answer to visual basic.
TestFunction endp
As you can see we are creating a function called TestFunction. This function has two parameters which are DWORD, or a 32 bit number. Assembly doesn't have characters or integers or particular types of variables, in fact they're all the same as far as the assembler is concerned, the only difference being their size.
Here is a list of some visual basic variables and how they correspond to assembly language variables:
Visual basic – Assembly
byte – BYTE
boolean – WORD
Integer – WORD
Long – DWORD
Short – DWORD
String – DWORD Variable is passed by pointer.
Char – BYTE
The rest can always be looked up via MSDN.
When you return a variable to visual basic, it is always done via eax. In fact that is pretty much universal that eax is used for returns.
Now before we create the dll, we must add our TestFunction to the export list. Open up the projectnamehere.def file in notepad and you should see something that looks like this:
LIBRARY mydll
EXPORTS
Just add TestFunction to the bottom of it so it looks like this:
LIBRARY mydll
EXPORTS
TestFunction
Safe the .def file and then compile your dll file. You can do this by going to project/run makeit.bat in your menu. If there is no errors then we can continue to setting up the call from visual basic =)
Now to call this from visual basic, we simply do this:
Code:
Private Declare Function TestFunction Lib "c:\mydll.dll" (ByVal number1 As Long, ByVal number2 As Long) As Long
Private Sub Command1_Click()
MsgBox (Str(TestFunction(10, 10)))
End Sub
The only note here is that lib “c:\mydll.dll” should reflect the location and name of the dll on your hard drive.
Output:
Displays 20 in a message box.
You may notice that the dll file created in this example is so small, on my machine it created a 2.5kb file, which is needless to say, very small!
I've also included the files that I used in this example as an attachment. For those of you who are too lazy to copy and paste =P
Last edited by Maven; Nov 28th, 2004 at 09:25 PM.
Education is an admirable thing, but it is well to remember from time to time that nothing that is worth knowing can be taught. - Oscar Wilde
Re: Mixed Language Programming - Some assembly required =P
Update:
If you ever want to pass arrays in visual basic, know that visual basic uses SAFEARRAY data types for all of their arrays. With the following exception: Strings. When you pass a string to a dll byval, it will only send a pointer to a null terminated string. However if you have an array of strings, it will be passed via method described below.
Code:
SAFEARRAYBOUND struct
cElements DWORD ? ; Number of Elements
lLbound DWORD ? ; Lower Boundary
SAFEARRAYBOUND ends
OLE_SAFEARRAY struct
cDims WORD ? ; Number of dimensions
fFeatures WORD ? ; Bitfield indicating attributes
cbElements DWORD ? ; size of an element of the array
cLocks DWORD ? ; lock counter 0=Locked
pvData DWORD ? ; Pointer to data
rgsabound SAFEARRAYBOUND <> ; Contains info for dimensions
OLE_SAFEARRAY ends
small example showing how to reference the structure:
Code:
testfunction proc myArray:DWORD
mov eax, myArray
mov edx, [eax] ;EDX now has our safearray.
'Assign 1st value to eax.
mov eax, (OLE_SAFEARRAY ptr [edx]).pvData
ret
testfunction endp
Education is an admirable thing, but it is well to remember from time to time that nothing that is worth knowing can be taught. - Oscar Wilde
Re: Mixed Language Programming - Some assembly required =P
One other thing off the top of my head...
Visual basic does strings in unicode. Before a dll is called, visual basic creates a temp null terminated string and passes the pointer to that string to the dll. After the return of the dll, it converts it back into unicdoe. If you want to design a dll to work with unicode strings, you have to do a work around to avoid this conversion by visual basic. The only way I know of to do this is to assign a byte array to a string like this:
bytearray() = mystring
then pass the byte array to the dll. This will give you the unicode version of the string in the dll file. This is also a useful bit of information when working with a few unicode windows API's from visual basic.
Education is an admirable thing, but it is well to remember from time to time that nothing that is worth knowing can be taught. - Oscar Wilde