0% found this document useful (0 votes)
13 views

Visual Basic Secrets

Uploaded by

serleb44
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

Visual Basic Secrets

Uploaded by

serleb44
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

Visual Basic Secrets http://www.thevbzone.com/secrets.

htm

Visual Basic Secrets


Copyright© 2002 by Kevin Wilson

• Introduction
• Using Pointers In Visual Basic
• VarPtr, StrPtr, and ObjPtr
• ByRef / ByVal
• AddressOf and Callbacks
• Accessing "Hidden" API's

Introduction:

Visual Basic is called a "Rapid Application Development (RAD) Development Tool" because it was designed to
take care of the Windows "ground work" for you, thus allowing you to concentrate on the important stuff like the
program's functionality and documentation.

For example, when you open VB and add a standard "Form" to your project, there's A LOT that goes into putting
that form on the screen when you hit "F5" to execute the program and simply display the form. You have to call
the "CreateWindow" to actually create the Form and give it it's properties that make up it's interface. You then
have to modify it's text font, forecolor, backcolor, device context, etc. by calling various Win32 API's. Lastly, you
have to hook into the Windows messages that are being sent to the newly created form by subclassing it and then
catching and processing each Windows messages properly via a "WindowProc" callback function. More complex
interfaces require more complex object creation and handling functionality to be programmed into the form. C and
C++ programmers actually have to create all that object creation, message handling, and object destruction code
by hand (or have a template of it generated).

Visual Basic's ability to do the "basics" for you like this is a powerful thing to programmers who know how to
correctly use VB as a development tool, but also puts a lot of power into the hands of people that don't know much
about programming. Visual Basic is mocked by C/C++ programmers because of this. They say, "Anyone can
develop with VB, but it takes a real programmer to develop with C/C++." I say that the SMART programmer
chooses Visual Basic because VB eliminates potential bugs in your object creation, message handling, and object
destruction routines, VB offers easier and quicker handling of Windows events, VB gives you a more robust
interface capabilities, VB gives you easier access to COM objects and third party controls, VB is easier to read
because it is very close to reading English where C/C++ is VERY cryptic, VB allows you easy access to the Win32
API (which gives the programmer the ability to tap into the power of Windows), and on top of ALL THAT... Visual
Basic has the ability to hook into the power and speed of C/C++ via components, libraries, and other code written
in C/C++. Heh... where's the bragging rights now?

Here's the thing though... even VB programmers that have been in the industry for years don't realize the real
power of VB because they don't grasp (or realize) a few key concepts and functionalities that VB offers. These
concepts aren't taught, or at least are not emphasized the way they should, so I call them "VB SECRETS".

^ TOP ^

Using Pointers In Visual Basic:

I was once asked in a job interview a question that I now realize was a TRICK QUESTION. The question was,
"Does Visual Basic have or use 'pointers' ?" The obvious answer to anyone that uses Visual Basic is "NO". You
don't see pointer declarations and macros in VB like you do in C/C++... and that's what I think the interviewer was
getting at. She accepted my answer with that reasoning. However, the correct answer should have been "YES".

Visual Basic (like just about every other programming language) does use pointers... EXTENSIVELY. The
difference is, Visual Basic hides them from you whenever possible, or calls them something different so as to not
burden you with the formalities and protocols required when using them.

I will proceed to explain how you can use pointers to access information held in variables directly (VarPtr / StrPtr /
ObjPtr), pass information to functions by pointers (ByRef / ByVal), and retrieve and pass pointers to functions
(AddressOf).

^ TOP ^

1 sur 8 20/04/2014 10:02


Visual Basic Secrets http://www.thevbzone.com/secrets.htm

VarPtr, StrPtr, and ObjPtr:

The VB Functions "VarPtr" (Variable Pointer), "StrPtr" (String Pointer), and "ObjPtr" (Object Pointer) are
UNDOCUMENTED, UNSUPPORTED functions that Microsoft has made available in Visual Basic 5.0, and 6.0. These
functions (along with many others) are no longer available in VB.net. These functions allow you to get the address
in memory where VB variables (pointers) are, as well as the address in memory where the actual data that the
variables point to are. The reason these functions are so useful is because if you know the memory address of
data, you can manipulate it, copy it, or pass it around directly instead of relying on VB to do it for you. This is
MUCH faster and (in some cases) gives you the ability to do things that VB on it's own can't do.

This is what Microsoft's MSDN says about "VarPtr":

This function can be used to get the address of a variable or an array element. It takes the variable name or
the array element as the parameter and returns the address. However, you should be aware that unlocked
Dynamic Arrays may be reallocated by Visual Basic, so you must be very careful when you use VarPtr to get
the address of an array element.

The following example gets the address of a variable:


Dim lngVariableAddress As Long
Dim dblMyVariable As Double
lngVariableAddress = VarPtr(dblMyVariable)

This example gets the address of the fourth element of an array:


Dim lngElementAddress As Long
Dim lngArrayOfLongs(9) As Long
' The following will get the address of the 4th element in the array
lngElementAddress = VarPtr(lngArrayOfLongs(3))

Limitations: The VarPtr function cannot be used to get the address of an array...

This is what Microsoft's MSDN says about "StrPtr":

Strings in Visual Basic are stored as BSTR's. If you use the VarPtr on a variable of type String, you will get the
address of the BSTR, which is a pointer to a pointer of the string. To get the address of the string buffer itself,
you need to use the StrPtr function. This function returns the address of the first character of the string. Take
into account that Strings are stored as UNICODE in Visual Basic.

To get the address of the first character of a String, pass the String variable to the StrPtr function.

Example:
Dim lngCharAddress As Long
Dim strMyVariable As String
strMyVariable = "Some String"
lngCharAddress = StrPtr(strMyVariable)

You can use this function when you need to pass a pointer to a UNIOCODE string to an API call.

This is what Microsoft's MSDN says about "ObjPtr":

ObjPtr takes an object variable name as a parameter and obtains the address of the interface referenced by this
object variable.

One scenario of using this function is when you need to do a collection of objects. By indexing the object
using its address as the key, you can get faster access to the object than walking the collection and using the Is
operator. In many cases, the address of an object is the only reliable thing to use as a key.

Example:

2 sur 8 20/04/2014 10:02


Visual Basic Secrets http://www.thevbzone.com/secrets.htm

objCollection.Add MyObj1, CStr(ObjPtr(MyObj1))


...
objCollection.Remove CStr(ObjPtr(MyObj1))

Note that the "Limitations" at the end of the information about "VarPtr", it said that you can't use VarPtr to get the
address of an array. That's true... to a point. You can't pass the variable "MyArray" to it (because VB stores arrays
in an OLE object called a "SafeArray"), but if you get the address of the first element of the array "MyArray(0)", you
have the address of the whole array because arrays elements are stored in memory contiguously (in numeric order
from the first to the last). So if a Win32 API, or a C/C++ function asks for a pointer to a byte array, like this:

Option Explicit
Private Type POINTAPI
X As Long
Y As Long
End Type

'BOOL Polyline(
' HDC hDC, // handle of device context
' CONST POINT *lpPT, // address of array containing endpoints
' int cPoints // number of points in the array
');
Private Declare Function Polyline Lib "GDI32.DLL" (ByVal hDC As Long, _
ByRef lpPT As Any, ByVal cPoints As Long) As Long

You could call it like this:

Private Sub Form_Load()


Dim ThePoints() As POINTAPI
Me.AutoRedraw = True
Me.Visible = True
Me.Move 0, 0, Me.Width, Me.Height
ReDim ThePoints(1 To 5) As POINTAPI
ThePoints(1).X = 0: ThePoints(1).Y = 0
ThePoints(2).X = 100: ThePoints(2).Y = 0
ThePoints(3).X = 100: ThePoints(3).Y = 100
ThePoints(4).X = 0: ThePoints(4).Y = 100
ThePoints(5).X = 0: ThePoints(5).Y = 0
If Polyline(Me.hDC, ByVal VarPtr(ThePoints(1)), 5) = 0 Then Debug.Print "FAILED!"
Me.Refresh
Erase ThePoints
End Sub

NOTE: Be careful about storing pointers to dynamic arrays because when arrays are reallocated, resized, or
redim'ed... it is very possible that you'll have a completely new memory address for the actual data.

For more information on VarPtr, StrPtr, and ObjPtr, see the following:

http://support.microsoft.com/default.aspx?scid=kb;en-us;Q199824
http://msdn.microsoft.com/library/en-us/dnw32dev/html/ora_apiprog6_topic1.asp
http://msdn.microsoft.com/library/en-us/dnovba00/html/LightningStrings.asp
http://msdn.microsoft.com/library/en-us/dnovba01/html/Lightweight.asp
^ TOP ^

ByRef / ByVal:

By far the biggest problem VB programmers run into while working with Win32 API's (or any exported C/C++
function for that matter) is correctly passing the required parameters to the function. Inserting a "ByRef" where a
"ByVal" should've been (or visa versa), or passing a value or variable when the function was expecting a pointer
can be the one difference between the function being called working perfectly or causing Windows to crash and
burn. Understanding how to pass parameters correctly takes an understanding of how Windows programs work
with "calling stacks" and memory allocation between the calling program and the function being called.

First of all, lets discuss what a "call stack" is and how it relates to memory allocation when passing parameters to
a function. The "call stack" is simply a spot in memory where the variables and values being passed to and from a
function are stored. It's called a "stack" because parameter values follow one after the other in memory and are

3 sur 8 20/04/2014 10:02


Visual Basic Secrets http://www.thevbzone.com/secrets.htm

accessed with that assumption. Because of this, parameters are in a way "stacked on top of eachother" to make
up all the information being given to the function. When a parameter is added to a function's call stack, it is said
to be "pushed" onto the call stack. When a parameter is removed (or cleaned up) from a function's call stack, it is
said to be "popped" off the call stack. The terms "stack", "push", and "pop" are assembler terms (yes, we are that
low-level at this point) and if you were to decompile a program or DLL into assembly, you'd see lines with the
words "push", "pop", etc.

When Visual Basic calls a Win32 API (or any exported C/C++ function), it expects the function to use the "Standard
Calling Convention" ( __stdcall ) as apposed to the default C/C++ calling convention ( __cdecl ). This means that
when the function is called, parameters are passed into memory (or pushed onto the stack) from right to left, and
the function being called is responsible for cleaning up the memory passed to it (or popping the memory passed
off of the stack). If you try to call an exported function that is declared with any other calling convention but
__stdcall, Visual Basic will not know how to handle the stack and parameters being passed back and forth so you
will get a message from VB saying "Bad DLL Calling Convention".

For a more in-depth and advanced explanation of how memory is allocated and deallocated when calling
parameters, and what call stacks are and how they work in Windows, I strongly recommend reading an
EXCELLENT book by Dan Appleman (Desaware) called "Dan Appleman's Win32 API Puzzle Book and Tutorial for
Visual Basic Programmers".

Now lets back up a little and get out of the inner workings of Windows memory and get back to working with VB.
When you call a function, you can pass parameters to it in one of two ways. You can pass it an explicit value that
you want the function to take and work with, or you can pass it a pointer to information already present in
memory. When you're passing simple information like numbers, sizes, flags, etc. you want to pass the information
in ByVal (meaning By Value) because you want it to take the value of what you are passing, not the memory
address of where that value is currently being held. Now when you want to pass more complex data to a function
like a data type, an array of values, or an object reference, you need to pass a reference (or pointer) to the function
telling it where in memory the data is. This is done by specifying the ByRef (meaning By Reference) keyword. The
function will then go to that point in memory and read the data that applies. The exception to this is when you
pass strings to Win32 API calls (or any C/C++ exported function). Always pass strings ByVal to API's (unless
you're passing a string array... in which case you'd use ByRef, or just pass the first element of the array ByVal).

So at this point you're saying, "I already know about passing parameters ByRef/ByVal". Yes, but did you realize
that what you're doing when you pass "ByRef" is passing a pointer? If you take that concept a step further, you
can do things like make function interfaces more generic (thus opening them up for more broad application) by
altering the "ByRef" to "ByVal" and passing an explicit pointer to the data you want to pass. So instead of
declaring your function like this:

Option Explicit

Private Type RECT


Left As Long
Top As Long
Right As Long
Bottom As Long
End Type

'int FillRect(
' HDC hDC, // handle to device context
' CONST RECT *lpRC, // pointer to structure with rectangle
' HBRUSH hBR // handle to brush
');
Private Declare Function FillRect Lib "USER32.DLL" (ByVal hDC As Long, _
ByVal lpRC As Long, ByVal hBR As Long) As Long
Private Declare Function CreateSolidBrush Lib "GDI32.DLL" (ByVal crColor As Long) As Long
Private Declare Function DeleteObject Lib "GDI32.DLL" (ByVal hObject As Long) As Long

Private Sub Form_Load()


Dim hBrush As Long
Dim MyRect As RECT

Me.AutoRedraw = True
Me.Visible = True
Me.Move 0, 0, Me.Width, Me.Height
With MyRect
.Top = 0: .Left = 0: .Right = 100: .Bottom = 100
End With

4 sur 8 20/04/2014 10:02


Visual Basic Secrets http://www.thevbzone.com/secrets.htm

hBrush = CreateSolidBrush(vbRed)
If hBrush = 0 Then Exit Sub
If FillRect(Me.hDC, VarPtr(MyRect), hBrush) = 0 Then Debug.Print "FAILED!"
Me.Refresh
DeleteObject hBrush
End Sub

If you think about it, this gives you all kinds of options when declaring functions and parameters. You're not
restricted anymore to the exact variable type. You could make EVERYTHING "Long" variable types and pass
pointers around to everything (as long as you were careful about how you did it). So you're having trouble
passing that monster custom type around, FORGET ABOUT IT... pass it with a pointer. So you're having problems
passing that object around, FORGET ABOUT IT... pass it with a pointer. VB 5.0 won't let you return variable arrays
(or funky types and objects) as the return type for a function, FORGET ABOUT IT... pass back a long value
representing where the array is in memory and use the CopyMemory API to copy it down into a local array. See
where I'm going with this?

If you just said, "NO"... using VarPtr, StrPtr, and ObjPtr in conjunction with ByRef and Byval allows you to pass
around data in ANY format if you know what you're doing.

^ TOP ^

AddressOf and Callbacks:

The "AddressOf" operator is all about callbacks. "But what is a call back?" you ask. A callback is the Windows
equivalent of a VB "Event". In fact, VB events (on a very low level) are just about always triggered by callback
functions that catch the original event in the form of a Windows Message. Callbacks are most often seen within
the Win32 API (and other C/C++ code) where notification of user and/or Windows activity is required or desired
within your application. You don't see callbacks within VB much because VB handles messaging and notification
via "Events", which are much easier and safer to deal with compared to callbacks and all that goes into them.

Lets say that you wanted to receive notification of EVERY message that Windows is sending to a Form within your
project (even ones that you'd never use), along with a few custom messages that may be sent to your Form via
some other API call(s). What you would do is setup a callback function that is recognized by Windows
("WindowProc") and then tell Windows (via the "SetWindowLong" API) to send all of it's messages meant for your
Form to your callback function instead, so you can inspect them and/or react to them... and then send them on
their way (via the "CallWindowProc" API). Doing this is called "Sub-Classing" and is a very powerful (but at the
same time very dangerous) technique that you can use to redraw your Form, it's menus, and/or it's contents in a
custom manner (or whatever you want to do with your Form).

There two draw-backs to using "AddressOf":

1) You can only retrieve the address to a function or sub (public or private) contained within a Standard VB
Module. There's no way around this.

2) It can only be called as part of a parameter list to a function or sub. The way to get around it is like this:

Option Explicit

Public Sub Main()


Dim lngProcAddress As Long
lngProcAddress = GetProcAddress(AddressOf WindowProc)
End Sub

Public Function GetProcAddress(ByVal lngAddressOf As Long) As Long


GetProcAddress = lngAddressOf
End Function

Public Function WindowProc(ByVal hWnd As Long, ByVal uMsg As Long, _


ByVal wParam As Long, ByVal lParam As Long) As Long
' < YOUR CODE GOES HERE >
End Function

You'll notice that we pass the "AddressOf" with the name of the function we want to get the address (memory
pointer) of to the function "GetProcAddress" which simply returns back that value. Simple concept and is very
effective. The addresses of functions and subs doesn't change so you could store the address of the functions
and subs you want to reference this way so you don't have to repeatedly call AddressOf, etc.

5 sur 8 20/04/2014 10:02


Visual Basic Secrets http://www.thevbzone.com/secrets.htm

"So lets see it in action!" you say... OK!

Here's an example of "sub-classing" as mentioned above:

Option Explicit

Private Const GWL_WNDPROC = (-4)


Private lngPrevProc As Long
Private lngHWND As Long

Private Declare Function SetWindowLong Lib "USER32.DLL" Alias "SetWindowLongA" _


(ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function CallWindowProc Lib "USER32.DLL" Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal uMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

' This is the CALLBACK function that receives the messages for the specified hWnd
Private Function WindowProc(ByVal hWnd As Long, _
ByVal uMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
' Display the messages and their information in the IMEDIATE window
' NOTE: You can find out what messages are being passed by comparing the value of
' "uMsg" to Windows Messages (WM_*) constant values defined in the WINUSER.H file
Debug.Print _
"hWnd=" & hWnd & ", uMsg=" & uMsg & ", wParam=" & wParam & ", lParam=" & lParam

' Forward on the messages to where they were originally supposed to go. This MUST
' here or the window will become unresponsive because it will stop recieving messages
WindowProc = CallWindowProc(lngPrevProc, hWnd, uMsg, wParam, lParam)
End Function

' This function starts a new sub-classing


Public Function Subclass_Start(ByVal hWnd As Long) As Boolean
' Stop any previous sub-class
If Subclass_Stop = False Then Exit Function
' Attempt to start a new sub-class
lngPrevProc = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WindowProc)
If lngPrevProc <> 0 Then
lngHWND = hWnd
Subclass_Start = True
End If
End Function

' This function stops any existing sub-classing


Public Function Subclass_Stop() As Boolean
' If no previous sub-class was started, just exit
If lngPrevProc = 0 Or lngHWND = 0 Then
Subclass_Stop = True
Else
' Set the message handling procedure back to what it originally was
If SetWindowLong(lngHWND, GWL_WNDPROC, lngPrevProc) <> 0 Then
Subclass_Stop = True
End If
End If
' Clear the variables used
lngPrevProc = 0
lngHWND = 0
End Function

Option Explicit

Private Sub Form_Load()


Subclass_Start Me.hWnd
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)


Subclass_Stop

6 sur 8 20/04/2014 10:02


Visual Basic Secrets http://www.thevbzone.com/secrets.htm

End Sub

Here's an example of "enumeration", which is a VERY popular way for Windows to give you back information
about "information lists" (like a list of all windows, a list of all the objects on a window, a list of all the installed
fonts, etc):

Option Explicit

Private lngWinHandle() As Long


Private lngWinHandleCount As Long

Private Declare Function EnumWindows Lib "USER32.DLL" (ByVal lpEnumFunc As Long, _


ByVal lParam As Long) As Long

' This is the CALLBACK that enumerates through all windows in the current desktop
Private Function EnumWindowsProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
' Incrament the array of window handles
lngWinHandleCount = lngWinHandleCount + 1
ReDim Preserve lngWinHandle(1 To lngWinHandleCount) As Long
' Add the information to the array
lngWinHandle(lngWinHandleCount) = hWnd
' Tell the function to keep going
EnumWindowsProc = 1
End Function

Public Function GetAllWindows(ByRef Return_Handles() As Long, _


Optional ByRef Return_WinCount As Long) As Boolean
' Clear any previous information
Erase lngWinHandle
lngWinHandleCount = 0
' Start enumerating through the windows
If EnumWindows(AddressOf EnumWindowsProc, 0) <> 0 Then
Return_Handles = lngWinHandle
Return_WinCount = lngWinHandleCount
GetAllWindows = True
End If
Erase lngWinHandle
lngWinHandleCount = 0
End Function

Option Explicit
Private Sub Form_Load()
Dim lngWindows() As Long
Dim lngWindowsCount As Long
Dim lngCounter As Long
Dim strWindows As String
If GetAllWindows(lngWindows, lngWindowsCount) = True Then
If lngWindowsCount > 0 Then
For lngCounter = 1 To lngWindowsCount
strWindows = strWindows & " " & lngWindows(lngCounter) & Chr(13)
Next
End If
Me.AutoRedraw = True
Me.Print strWindows
End If
Erase lngWindows
End Sub

^ TOP ^

Accessing "Hidden" API's:

This part is definately the most "secret" of all the secrets decribed on this page. There are indeed MANY hidden
Win32 API calls in Windows... the trick is to find them and find out how to call them because Microsoft sure won't
tell you.

"But why hide them?" you ask? Because Microsoft adds extra functionality to the API that only they can gain
access to. This gives their products an advantage when (running under Windows) over everyone else's because

7 sur 8 20/04/2014 10:02


Visual Basic Secrets http://www.thevbzone.com/secrets.htm

only they have access to more powerfull functionality, faster functionality, or extra functionality via these hidden
API calls when everyone else has to make do with the regular, documented functionality exposed by Windows and
the documented in the MSDN. "Unfair advantage" you say? DEFINATELY! But who ever said that Microsoft does
business fairly... or ethically for that matter! These kinds of business practices are what are constantly keeping
Microsoft in court and on newspaper headlines.

"What kind of hidden API's are out there, and how do I find out what they are and how to use them?" you ask?
EXCELLENT question, and that's why I've included this here. There are many web pages out on the internet
dedicated to finding these hidden API's and exposing their functionality to "level the playing field" and give the
more cool functionality to developers like you and me! Here's a few good web pages on this:

http://www.geocities.com/SiliconValley/4942/index.html
http://www.users.qwest.net/~eballen1/nt.sekrits.html
http://www.mvps.org/vbnet/code/shell/undocshelldlgs.htm
http://www.mvps.org/vbnet/code/shell/undocformatdlg.htm
http://www.mvps.org/vbnet/code/shell/undocchangeicondlg.htm
http://www.mvps.org/vbnet/code/shell/undocshpaths.htm
http://www.ercb.com/ddj/1992/ddj.9211.html

You can find a few of these "hidden API's" in the modCOMDLG32.bas module under the "VB Standard Modules".
They look like this:

Public Declare Function DLG_FindFile Lib "shell32.dll" Alias "#90" _


(ByVal pidlRoot As Long, ByVal pidlSavedSearch As Long) As Long
Public Declare Function DLG_FindComputer Lib "shell32.dll" Alias "#91" _
(ByVal pidlRoot As Long, ByVal pidlSavedSearch As Long) As Long

You'll notice that they are aliased by a number "#90", "#91", etc. These are called "Ordinal Numbers" and they are
a way for you to expose API's through a DLL without exposing it's name. So if you wrote a function, and you
wanted to use it but didn't want anyone else to, you could expose it by a number. This doesn't give anything away
as to what it does or why it's there, but at the same time gives you access to it.

Sneaky, huh?!

WELL! That's all folks. If I think of any other "secrets" or neat "hidden" or "obscure" functionality within VB (or
remember any that I meant to put here), I'll add them. Happy coding! =)

MAIN | DOWNLOADS | SAMPLE CODE | STEP BY STEP | DISCUSSION BOARD | LINKS | AUTHOR
E-MAIL

8 sur 8 20/04/2014 10:02

You might also like