Escolar Documentos
Profissional Documentos
Cultura Documentos
• Introduction
• Using Pointers In Visual Basic
• VarPtr, StrPtr, and ObjPtr
• ByRef / ByVal
• AddressOf and Callbacks
• Accessing "Hidden" API's
Introduction:
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 ^
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 ^
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.
Limitations: The VarPtr function cannot be used to get the address of an array...
Strings in Visual Basic are stored as BSTR's. If you use the VarPtr on a variable of type String, you will get t
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:
You can use this function when you need to pass a pointer to a UNIOCODE string to an API call.
ObjPtr takes an object variable name as a parameter and obtains the address of the interface referenced by thi
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 I
operator. In many cases, the address of an object is the only reliable thing to use as a key.
Example:
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
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 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".
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
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
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 ^
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).
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.
Option Explicit
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.
Option Explicit
' 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
Option Explicit
Option Explicit
' 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
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 ^
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 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'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! =)
Visual Basic 6.0 Tips
Capitalizing the First Letter of Each Word in a String
If App.PrevInstance Then
Msgbox "Application already running"
End
End If
lFile = FreeFile
sUrl = "URL=http://www.TheScarms.com"
'
' See my shell link program to determine the desktop path.
'
sFile = "C:\Windows\desktop\TheScarms.url"
ErrHandler:
Err.Raise Err.Number, "fCreateObject", Err.Description & ": '" & sID & "'"
End Function
Create a VB Add-In to Close all Open Windows in the VB IDE
You can create a VB Add-In to close all the open windows in the VB development
environment with a single click. Open a new VB project of type Add-In. Enter this
code in the load event of frmAddIn. Press F2 to open the Object Browser, highlight
the Connect class, right click it, and edit the Description field to change the name
and description of your add-in. Also, search the entire project and replace all
occurrences of "My Add-In" with whatever you decide to call it. Change the project's
properties as desired. Make the DLL then you can add your add-in from the Add-In
Manager.
Dim w As Window
w.Close
End If
Next
A Better DoEvents
Putting DoEvents in loops to make your app responsive to user input is a common
but expensive practice. Use GetInputState instead. GetInputState returns 1 when a
mouse is clicked or key pressed. It has much less overhead and can be called every
so often as need be. When an input event occurs, then call DoEvents.
With Me
lLeft = (Screen.TwipsPerPixelX * _
(GetSystemMetrics(SM_CXFULLSCREEN) / 2)) - (.Width / 2)
lTop = (Screen.TwipsPerPixelY * _
(GetSystemMetrics(SM_CYFULLSCREEN) / 2)) - (.Height / 2)
.Move lLeft, lTop
End With
End Sub
Add a multi line textbox to your form. In form_load call this code:
s = String(9000, "X")
Me.Show
lRet = SendMessage(txtlarge.hwnd, WM_SETTEXT, 0&, ByVal s)
Debug.Print "WM_SETTEXT: " & lRet
TypeVar1 = EmptyVar
TypeVar2 = EmptyVar
End Sub
If blnResult Then
strRelativepath = Left(strRelativepath,
InStr(strRelativepath, vbNullChar) - 1)
Else
strRelativepath = ""
End If
GetRelativePath = blnResult
End Function