December 13, 2002
Got a problem or a stumper question on anything to do with Internet or Windows-based development? Ask the good doctor (drgui@microsoft.com); he's here for you twice a month, online at MSDN. Though his busy surgery schedule precludes answering every question, he'll answer as many as he can—and if yours is picked, he'll be glad to send you a t-shirt!
Summary: Dr. GUI shows how to call API's from Microsoft Visual Basic .NET, and how to create assembly code that can be called from the CallWindowProc API. (12 printed pages)
Dear Dr. GUI,
I have recently stumbled across a sample project in Visual Basic 6.0, which performed CRC16 and CRC32 checks. The description of the code boasted that it could beat any C/C++ application at this task. I was amazed to discover that this was in fact correct. The following outlines how it is done:
- Precompile a tiny bit of assembly code, and convert it to hex.
- Hard-code the hex value into a string variable in Visual Basic 6.0.
- Load the data to be run through the CRC routine into a byte array.
- Execute the ASM string from memory through CallWindowProc by passing the memory address of the string in place of the window handle, and a pointer to the first element of the array, and the literal length of the array as the LParam and WParam respectivly.
Well I've ported this code to Microsoft .NET, but since Visual Basic .NET is now missing the VarPtr and xxxPtr functions, the conversion process has turned into a bit of a hack, and I can't get CallWindowProc to execute the code. I have attached my port and the original if you are interested.
Good luck,
Jeremy
Dr. GUI replies:
Excellent question, Jeremy. Now, let me just get out my oximeter and sphygmomanometer to check for your missing vital functions... Hmmm... Yes, you're absolutely right, they're nowhere to be found. But don't panic. The good doctor may still have a cure for you—with just a little minor surgery. Let me start by trying to explain a little better what is taking place here, and then we can talk about how you would do this in Microsoft? Visual Basic? .NET.
Come to think of it, first the good doctor feels he'd better let some of his readers in on a little secret... Cyclic redundancy checking (CRC) is used to check data by creating a hash of the data and generating a checksum. Then you can pass the data around (for instance, over the Internet), and when the end user gets the data, he or she can run it through the CRC function to get the checksum and verify that it is the same as the original. If it is the same, the data has not changed, and all's well. If it is not the same, then something is wrong. CRC is used whenever you need to verify that you have the same data that was sent, from security to common file transfer. For more information see the discussion at http://www.microconsultants.com/tips/crc/crc.txt.
All right, so now let's get on to what you are really asking, Jeremy: How to execute assembly code with the CallWindowProc API in Visual Basic .NET? (To answer this, I'll use a simpler example than the original CRC sample.)
First, let's look at the assembly code. To do this, you need to compile the assembly and look at a listing of the code bytes. These code bytes are then concatenated to make a string that is essentially the machine-level binary value of the assembly code. To get at this information, just place the assembly code that you want to be precompiled in _ASM block in a simple Microsoft? Visual C++? application. Here is the code for this:
int main(int argc, char* argv[])
{
_asm
{
push ebp
mov ebp,esp
push edi
push esi
push eax
push ebx
push ecx
mov eax, [ebp+8]
mov eax, [eax]
mov esi, [ebp+12]
mov edi, [ebp+16]
mov ecx, [ebp+20]
xor ebx, ebx
loopy:
mov bl, [esi]
add eax, [edi+ebx*4]
inc esi
dec ecx
jnz loopy
mov ecx, [ebp+8]
mov [ecx], eax
pop ecx
pop ebx
pop eax
pop esi
pop edi
mov esp,ebp
pop ebp
ret 16
}
return 0;
}
Now notice that this assembly is simply taking these 4 things:
- Return value for the coded sum.
- Byte array of the characters in the string.
- Integer array containing the value that all ASCI characters map to in this coding.
- Count of the number of characters in the byte array.
You are then looping through all of the characters in the byte array and determining what value they map to. Then you take that value and add it to the total. Finally, you return from the procedure.
In Visual Basic .NET code, this operation would equate to something like this:
Dim x As Byte
For Each x In ByteArray
returnValue += ValueTable(x)
Next
Fairly simple, eh? Now that you have your assembly, you compile it. Take a look at the disassembly with code bytes:
12: push ebp
00401035 55 push ebp
13: mov ebp,esp
00401036 8B EC mov ebp,esp
14:
15: push edi
00401038 57 push edi
16: push esi
00401039 56 push esi
17: push eax
0040103A 50 push eax
18: push ebx
0040103B 53 push ebx
19: push ecx
0040103C 51 push ecx
20:
21: mov eax, [ebp+8]
0040103D 8B 45 08 mov eax,dword ptr [ebp+8]
22: mov eax, [eax]
00401040 8B 00 mov eax,dword ptr [eax]
23: mov esi, [ebp+12]
00401042 8B 75 0C mov esi,dword ptr [ebp+0Ch]
24: mov edi, [ebp+16]
00401045 8B 7D 10 mov edi,dword ptr [ebp+10h]
25: mov ecx, [ebp+20]
00401048 8B 4D 14 mov ecx,dword ptr [ebp+14h]
26: xor ebx, ebx
0040104B 33 DB xor ebx,ebx
27:
28: loopy:
29: mov bl, [esi]
0040104D 8A 1E mov bl,byte ptr [esi]
30: add eax, [edi+ebx*4]
0040104F 03 04 9F add eax,dword ptr [edi+ebx*4]
31: inc esi
00401052 46 inc esi
32: dec ecx
00401053 49 dec ecx
33: jnz loopy
00401054 75 F7 jne loopy (0040104d)
34:
35: mov ecx, [ebp+8]
00401056 8B 4D 08 mov ecx,dword ptr [ebp+8]
36: mov [ecx], eax
00401059 89 01 mov dword ptr [ecx],eax
37:
38: pop ecx
0040105B 59 pop ecx
39: pop ebx
0040105C 5B pop ebx
40: pop eax
0040105D 58 pop eax
41: pop esi
0040105E 5E pop esi
42: pop edi
0040105F 5F pop edi
43:
44: mov esp,ebp
00401060 8B E5 mov esp,ebp
45: pop ebp
00401062 5D pop ebp
46:
47: ret 16
00401063 C2 10 00
Finally, you strip out the code bytes, and this leaves you with the following string:
"558BEC57565053518B45088B008B750C8B7D108B4D1433DB8A1E03049F464975F78B4D088901595B585E5F8BE55DC21000"
(Notice that the code bytes are the 2-character strings to the right of the 8-character memory address.) Now that you have your precompiled assembly, you can head into Visual Basic .NET and see what can be done with it.
The precompiled assembly string is read into a byte array. You will pass this byte array to the CallWindowProc API. Essentially, you are passing the precompiled assembly in place of the window function handle. The API, however, blindly executes whatever is passed to it. This is lucky in this case (the good doctor always appreciates a little luck when it comes his way), because it means that CallWindowProc will execute your binary instead of getting the binary from a function pointer that you would normally pass in.
For the other parameters of the CallWindowProc, you pass the values that your assembly will be operating on. These will be placed on the call stack, and your assembly will fetch them, execute on them, and then return.
Here is the MSDN definition for CallWindowProc. While there are many different types here, most are values that underneath are just longs:
LRESULT CallWindowProc(
WNDPROC Error! Hyperlink reference not valid., // pointer to previous procedure
HWND Error! Hyperlink reference not valid., // handle to window
UINT Error! Hyperlink reference not valid., // message
WPARAM Error! Hyperlink reference not valid., // first message parameter
LPARAM Error! Hyperlink reference not valid. // second message parameter
);
You have 2-byte arrays, an integer array, and two integers that you need to pass into this function. To get all of this working, you will need to start by looking at what it takes to call this or any API from Microsoft? .NET.
Calling the API from Microsoft .NET boils down to interoperating with unmanaged code. Dr. GUI likes to break this into 3 main steps:
- Identifying the function in the DLL. In this case the Library (DLL) is user32.dll and the function that you want to call is CallWindowProc.
- Creating a prototype in managed code. The prototype for this problem will look something like this:
Private Declare Function CallWindowProc Lib "User32.dll" _
Alias "CallWindowProcA" ( _
<MarshalAs(UnmanagedType.LPArray)> ByVal lpPrevWndFunc() As Byte, _
ByRef hWnd As Integer, _
<MarshalAs(UnmanagedType.LPArray)> ByVal Msg() As Byte, _
<MarshalAs(UnmanagedType.LPArray)> ByVal wParam() As Integer, _
ByVal lParam As Integer) As Integer
There are 2 things to take note of in this function. First, we have specified the managed types that we are going to expect, namely, ByVal lpPrevWndFunc() As Byte. From here, we are telling the runtime that we are going to marshal this managed array to a long pointer by adding the MarshalAs attribute, as in MarshalAs(UnmanagedType.LPArray). For further information on these topics, see these documents in the MSDN Library:
MarshalAsAttribute Constructor
UnmanagedType Enumeration
Consuming Unmanaged DLL Functions
- Call the function.
Dim returnVal As Integer
Dim ByteArray() As Byte
Dim AssemblyValue() As Byte
Dim ValueTable(255) As Integer
Dim ByteSize As Integer
CallWindowProc(AssemblyValue, returnVal, ByteArray, ValueTable, ByteSize)
Since you are using the MarshalAs attributes, you no longer need to specify a VarPtr or anything of that nature, as you did in Visual Basic 6.0. The other advantage is that you do not have to pass the first element of an array. Instead, you can just set up the declare of the API that you are going to call so as to expect these types and marshal them correctly.
Now that you have the assembly code and API defined, you need to look at how to put this all together to execute your code. Here are the variables you will be using:
Dim AssemblyValue() As Byte
Dim ValueTable(255) As Integer
Dim result As Integer
Dim StringByte() As Byte
Dim StringSize As Integer
First, you need to get the precompiled assembly code from the string into the AssemblyValue byte array:
Dim i As Integer
Dim assemblyCode As String
'Create a bytearray to hold the precompiled assembler code
assemblyCode = "558BEC57565053518B45088B008B750C8B7D108B4D1433DB8A1E03049 _
F464975F78B4D088901595B585E5F8BE55DC21000"
ReDim AssemblyValue(Len(assemblyCode) 2 - 1)
For i = 1 To Len(assemblyCode) Step 2
AssemblyValue(i 2) = Val("&H" & Mid(assemblyCode, i, 2))
Next
Basically, you are splitting the code into 2-character segments and then converting them into their byte value. Did you notice that the 2-character segments are the same as the segments of the code bytes when the assembly was first compiled? Excellent! You then load each code byte into an element of the AssemblyValue array.
Now you need to populate the array that contains the value that each of the 256 ASCII characters will map to. To do this, we are simply going to generate some numbers. They serve no specific purpose except to provide us with values to work with in this demonstration:
Dim i As Integer
For i = 0 To 255
ValueTable(i) = (i * 81) Mod (90 + i)
Next
Next you will create your final array—the byte array that contains the ASCII values of each character in the string. This is the array you will be generating your result from:
Dim StringToRun As String = "This is a really fun example"
Dim UEnc As New System.Text.ASCIIEncoding()
StringByte = UEnc.GetBytes(StringToRun)
Then you fill the value that will contain the size of the string's byte array:
StringSize = UBound(StringByte) - LBound(StringByte) + 1
Finally, you have to call the API that you declared with all of the values that you have prepared:
CallWindowProc(AssemblyValue, result, StringByte, ValueTable, StringSize)
After this executes, result will contain the value of the encoding of your string. Basically, you use the ASCII value of each character in the string's byte array as the index on the Value Table. The numeric values that you get from the Value Table are then summed up and returned in the result. The string size is just used as a counter, so that you know when you have reached the end of the string and can return.
For your convenience, Jeremy, the good doctor has taken all of this code and merged it together into a Visual Basic .NET console application. Here is the source for that:
Visual Basic .NET code
------------------------------------------------------------------
Imports System.IO
Imports System.Runtime.InteropServices
Module Module1
Private WithEvents x As Class2
Private Declare Function CallWindowProc Lib "User32.dll" _
Alias "CallWindowProcA" ( _
<MarshalAs(UnmanagedType.LPArray)> ByVal lpPrevWndFunc() As Byte, _
ByRef hWnd As Integer, _
<MarshalAs(UnmanagedType.LPArray)> ByVal Msg() As Byte, _
<MarshalAs(UnmanagedType.LPArray)> ByVal wParam() As Integer, _
ByVal lParam As Integer) As Integer
Private AssemblyValue() As Byte
Private ValueTable(255) As Integer
Private StringToRun As String = "This is a really fun example"
Sub Main()
InitializeVals()
Dim result As Integer
Dim StringByte() As Byte
' First convert the string to a Byte Array.
Dim UEnc As New System.Text.ASCIIEncoding()
StringByte = UEnc.GetBytes(StringToRun)
result = CalculateFromAssembly(StringByte)
Console.WriteLine("Assembly Coded Sum of '" + StringToRun + _
"' is " + Hex(result))
result = CalculateFromDonNetCode(StringByte)
Console.WriteLine(".NET Coded Sum of '" + StringToRun + _
"' is " + Hex(result))
End Sub
Public Function CalculateFromDonNetCode(ByVal ByteArray() As Byte) _
As Integer
Dim returnValue As Integer = 0
Dim x As Byte
For Each x In ByteArray
returnValue += ValueTable(x)
Next
Return returnValue
End Function
Public Function CalculateFromAssembly(ByVal ByteArray() As Byte) _
As Integer
Dim returnValue As Integer = 0
Dim ByteSize As Integer
ByteSize = UBound(ByteArray) - LBound(ByteArray) + 1
CallWindowProc(AssemblyValue, returnValue, ByteArray, ValueTable, _
ByteSize)
Return returnValue
End Function
Private Sub InitializeVals()
Dim i As Integer
Dim assemblyCode As String
For i = 0 To 255
ValueTable(i) = (i * 81) Mod (90 + i)
Next
'Create a bytearray to hold the precompiled assembler code
assemblyCode = "558BEC57565053518B45088B008B750C8B7D108B4D1433DB8A1E03049 _
F464975F78B4D088901595B585E5F8BE55DC21000"
ReDim AssemblyValue(Len(assemblyCode) 2 - 1)
For i = 1 To Len(assemblyCode) Step 2
AssemblyValue(i 2) = Val("&H" & Mid(assemblyCode, i, 2))
Next
End Sub
End Module
All of that said, there are a few other considerations when calling into an API that the good doctor feels he should point out. Like what happens when the garbage collector comes along during your call to CallWindwProc and compacts the heaps? Won't that change the memory address of the arrays? Well, when .NET passes these parameters as pointers into unmanaged code, it pins the memory so that the garbage collector cannot move it until the call returns and releases that pinned memory.
Another point is that this API that we have declared is very specific to our current purpose. This will usually be fine, because you will not be making many different types of calls to the same API in the same program. However, there may be an instance where you would like to pass different things into a specific API at different times. The easiest way to do this is to create a class that you place all of your API declarations into. This way you can declare them all with different names and function signatures, and you can then use these functions wherever you need.
Okay, I think this wraps it up, and I hope that this helps to clear up the problem for you.
Thanks!
Dr. GUI wants to thank his fabulous team of specialists, especially Zach Kramer, and his dedicated nurses Maura Baughman and Sharon Watts. If Dr. GUI did surgery without y'all, the prognosis of the patients would not be good.
Ask Dr. GUI
Can't get enough of Dr. GUI?
For additional shots from the good doctor, take a look at Dr. GUI .NET, published monthly.
Posted by Dual



