Você está na página 1de 18

Visual Basic Secrets

• 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 ^

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 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:

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 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:

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 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 Lo
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

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.

"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
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 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! =)
Visual Basic 6.0 Tips
Capitalizing the First Letter of Each Word in a String

Dim sNew as String


Dim sOld as String

sNew = StrConv$(sOld, vbProperCase)

Determining if Your App is Already Running

If App.PrevInstance Then
Msgbox "Application already running"
End
End If

Creating a Desktop Shortcut to a Web Site

Dim sUrl As String


Dim sFile As String
Dim lFile As Long

lFile = FreeFile
sUrl = "URL=http://www.TheScarms.com"
'
' See my shell link program to determine the desktop path.
'
sFile = "C:\Windows\desktop\TheScarms.url"

Open sFile For Output As lFile


Print #lFile, "[InternetShortcut]"
Print #lFile, sUrl

Give Users More Icons With Your App


Resource files expose any contained icons to Windows. By adding a resource file
containing icons to your application and compiling, the user can select any of those
icons to display in a shortcut to your application.

Can't Create What Object


Ever get this error (error 429) and wonder what object? Use this code to wrap your
calls to CreateObject. It will return the name of the object that could not be created.

Public Function fCreateObject(sID as String) as Object


On Error Goto ErrHhandler
Set fCreateObject = VBA.CreateObject(sID)
Exit Function

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

For Each w In VBInstance.Windows


If (w.Type = vbext_wt_CodeWindow Or _
w.Type = vbext_wt_Designer) And _
w.Visible Then

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.

Private Declare Function GetInputState Lib "user32" () As Long

Dim bUserCancel As Boolean

Private Sub cmdCancel_Click()


bUserCancel = True
End Sub

Private Sub cmdGo_Click()


Dim lCtr As Long
bUserCancel = False
For lCtr = 0 To 1000000
'
' A long loop that may need to be interupted.
'
If lCtr Mod 100 Then
If GetInputState() <> 0 Then
'
' A mouse or keyboard event occured.
'
DoEvents
If bUserCancel Then Exit For
End If
End If
Next
End Sub

Center a Form Accounting for the Taskbar and Other Appbars


Center your forms based on the actual portion of the screen that is exposed. This
method takes into account Window's taskbar and any other appbars such as toolbars
that are docked to the edge of the screen.

Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As


Long
Private Const SM_CXFULLSCREEN = 16
Private Const SM_CYFULLSCREEN = 17

Private Sub Form_Load()


Dim lLeft As Long
Dim lTop As Long

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

Use System Icons on your Forms


Extract the standard system icons to use on your forms to make them look like
typical Window's message boxes.

Private Enum StandardIconEnum


IDI_ASTERISK = 32516&
IDI_EXCLAMATION = 32515&
IDI_HAND = 32513&
IDI_QUESTION = 32514
End Enum

Private Declare Function LoadStandardIcon Lib "user32" _


Alias "LoadIconA" (ByVal hInstance As Long, _
ByVal lpIconNum As StandardIconEnum) As Long

Private Declare Function DrawIcon Lib "user32" _


(ByVal hdc As Long, ByVal x As Long, ByVal y As Long, _
ByVal hIcon As Long) As Long

Call this code:

Dim lIcon As Long


Me.Cls
lIcon = LoadStandardIcon(0&, lstIcon.ItemData(lstIcon.ListIndex))
Call DrawIcon(Me.hdc, 10&, 10&, lIcon)
Load Textbox With More Than 64K of Data
Get past the 64K limit imposed on the contents of a textbox with the SendMessage
API. Note that this will work only in NT and Win2K.

Private Const WM_SETTEXT = &HC


Private Const WM_GETTEXT = &HD
Private Const WM_GETTEXTLENGTH = &HE

Private Declare Function SendMessage Lib "user32" _


Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Any) As Long

Private Declare Function GetWindowTextLength Lib "user32" _


Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long

Add a multi line textbox to your form. In form_load call this code:

Dim lret As Long


Dim s As String

s = String(9000, "X")
Me.Show
lRet = SendMessage(txtlarge.hwnd, WM_SETTEXT, 0&, ByVal s)
Debug.Print "WM_SETTEXT: " & lRet

lRet = SendMessage(txtlarge.hwnd, WM_GETTEXTLENGTH, 0&, ByVal 0&)


Debug.Print "WM_GETTEXTLENGTH: " & lRet

In form_resize call this code:

txtlarge.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight

Clear Structures With One Assignment


You can quickly clear a user defined type without setting each subvariable.

Private Type udtType


SubVariable1 As Integer
SubVariable2 As String
SubVariable3 As Long
End Type
'
' Dim variables of this type.
'
Dim TypeVar1 As udtType
Dim TypeVar2 As udtType
'
' A method in a class which clears the structure variable.
'
Private Sub ClearData()
Dim EmptyVar As udtType

TypeVar1 = EmptyVar
TypeVar2 = EmptyVar
End Sub

Get the Relative Path Between 2 Folders

Private Function GetRelativePath(ByRef strRelativepath As String, _


ByVal strPathFrom As String, ByVal strPathTo As String) As

Boolean Dim blnResult As


Boolean Const MAX_PATH = 260
strRelativepath = Space$(MAX_PATH)
'
' Set dwAttr... to vbDirectory for directories, or 0 for files.
'
blnResult = PathRelativePathToW(StrPtr(strRelativepath), _
StrPtr(strPathFrom), vbDirectory, StrPtr(strPathTo), 0)

If blnResult Then
strRelativepath = Left(strRelativepath,
InStr(strRelativepath, vbNullChar) - 1)
Else
strRelativepath = ""
End If

GetRelativePath = blnResult
End Function

Private Sub Command1_Click()


Dim strRelativepath As String

If GetRelativePath(strRelativepath, "c:\temp", "c:\windows") Then


Debug.Print strRelativepath
Else
Debug.Print "Error"
End If
End Sub

Copy Large Arrays Faster


You can copy arrays much faster with a simple API call:

Private Declare Sub CopyMemory Lib "kernel32" _


Alias "RtlMoveMemory" (Dest As Any, _
Source As Any, ByVal Length As Long)

Private Sub CopyArray()


Dim lngbytes As Long
Dim lngSrc(1 To 600000) As Long
Dim lngDest(1 To 600000) As Long
'
' Number of bytes equals number of array
' elements times the element length.
'
lngbytes = (UBound(lngSrc) - LBound(lngSrc) + 1) * Len(lngSrc(1))
'
' Copy the array passing the address of the start to
' the destination and source arrays and the length
' of the arrays.
'
Call CopyMemory(lngDest(LBound(lngDest)), lngSrc(LBound(lngSrc)), lngbytes)
End Sub

Você também pode gostar