Search the Catalog
VB & VBA in a Nutshell: The Language

VB & VBA in a Nutshell: The Language

By Paul Lomax
1st Edition October 1998
1-56592-358-8, Order Number: 3588
656 pages, $24.95

VB & VBA Booklet

Booklet TOC

Implementing Custom Events     p66-68      Chapter 4
Automation Examples            p85-89      Chapter 5
Silent Reporting               p106-107    Chapter 6
#Const Directive               p113-117    Chapter 7
AddressOf Operator             p121-123    Chapter 7
CallByName Function            p142-146    Chapter 7
Declare Statement              p214-218    Chapter 7
DoEvents Function              p241-242    Chapter 7
Err.LastDLLError Property      p259-261    Chapter 7
Filter Function                p308-310    Chapter 7
GetObject                      p358-363    Chapter 7
WithEvents Keyword             p576-577    Chapter 7

Implementing Custom Events

In the early versions of VB, programmers were limited to working with the built-in events. In VB5, however, three simple keywords--Event, RaiseEvent, and WithEvents--were added to the language to allow the programmer to define custom events or to trap events in external objects that would otherwise be inaccessible.

Custom events applications

Custom events can be used for any of the following:

Custom event rules

The following are some of the rules and "gotchas" for defining custom events:

Creating a custom event

To raise an event from within an object module, you first of all must declare the event in the declarations section of the object module that will raise the event. You do this with the Event statement using the following syntax:

[Public] Event eventname [(arglist)]

For example:

Public Event DetailsChanged(sField As String)

In the appropriate place in your code, you need to fire the event using the RaiseEvent statement. For example:

RaiseEvent DetailsChanged("Employee Name")

That is all you need to do within the object module. Simply declare an event using Event, and fire it using RaiseEvent.

The client code is just as simple. You declare an object variable using the WithEvents keyword to alert VB that you wish to be informed when an event is fired in the object. For example:

Private WithEvents oEmployee As Employee

This declaration should be placed in the Declarations section of the module. VB automatically places an entry for the object variable name in the Object drop-down list at the top left of your code window. When you select this, note that the events declared in the object are available to you in the Procedure drop-down list at the top right of your code window. You can then select the relevant event and its event handler. For example:

Private Sub oEmployee_DetailsChanged(sField As String)
   MsgBox sField & " has been changed"
End Sub

In the earlier section "The Property Let procedure," we mentioned using a custom event to fire a warning to the client as part of a data-validation procedure. Unfortunately, though, events don't return a value. However, if you define one of the parameters of your event to be ByRef, you can examine the value of the variable once the event has been handled to determine the outcome of the event handling within the client application. Here's a simple example:

Server code:

Public Event Warning(sMsg As String, ByRef Cancel As Boolean)
Public Property Let ClaimValue(dVal As Double)
   Dim blnCancel As Boolean
   If dVal > 10000 Then
      RaiseEvent Warning("The Claim Value appears high", _
      If blnCancel Then
         Exit Property
      End If
   End If
   mdClaimValue = dVal
End Property

Client code:

Private WithEvents oServer As clsServer
Private Sub oServer_Warning(sMsg As String, _
                            Cancel As Boolean)
    Dim iResponse As Integer
    iResponse = MsgBox(sMsg & " is this OK?", _
                       vbQuestion + vbYesNo, _
    If iResponse = vbNo Then
        Cancel = True
        Cancel = False
    End If
End Sub

As you can see, this is a powerful technology. However, it also demonstrates another aspect of custom events that may not be desirable in certain circumstances: RaiseEvent is not asynchronous. In other words, when you call the RaiseEvent statement in your class code, your class code won't continue executing until the event has been either handled by the client or ignored. (If the client has not created an object reference using the WithEvents keyword, then it isn't handling the events raised by the class, and any events raised will be ignored by that client.) This can have undesirable side effects, and you should bear it mind when planning your application.

For more information on the custom event statements, see the entries for the Event, Friend, Private, Public, RaiseEvent, and WithEvents statements in Chapter 7.

Automation Examples

So let's bring together all you've seen in this chapter with a few sample implementations of OLE automation servers.

Using Word as a Report Writer from VB

This first application demonstrates how you can seamlessly use Microsoft Word to print output from your VB program without the user knowing that you have actually used Microsoft Word:

Private Sub cmdWordDoc_Click()
   'create an error handler
   On Error GoTo cmdWordDoc_Err
      'create the local Early Bound object variables
   Dim oWord           As Word.Application
   Dim oWordActiveDoc  As Word.Document
   Dim oWordSel        As Word.Selection
   'Create a new instance of Word
   Set oWord = New Word.Application
   'Create a new document object
   Set oWordActiveDoc = oWord.Documents.Add
   Set oWordSel = oWord.Selection
      'Do some work with the Selection object
      oWordSel.TypeText "This is some text from the VB app."
      oWordSel.Font.Name = "Arial"
      oWordSel.Font.Size = 12
      oWordSel.Font.Bold = wdToggle
      'Now print out the doc
   'always tidy up before you leave
   Set oWordSel = Nothing
   Set oWordActiveDoc = Nothing
   Set oWord = Nothing
   Exit Sub
   MsgBox Err.Number & vbCrLf & Err.Description & vbCrLf _
          & Err.Source
End Sub

Because this example uses early binding, you'll have to use the References dialog to add a project reference to the Word 8 Object Model.

TIP: Note that this application appears seamless because the application's Visible property is False by default. If you wanted to show the Word application window in operation (which may be required while debugging), simply set the property to True.

Using Email Within VB

This application demonstrates how you can work with a late bound object. The OLE server in this instance is Windows MAPI. Using MAPI in this way uses Outlook sort of through the back door; you don't actually create an instance of Outlook, but this sample demonstrates how closely tied MAPI and Outlook are. In fact, the mail side of Outlook isn't much more than a nice GUI to the Windows MAPI. If you are connected to an Exchange server when this simple application runs, the mail is sent automatically; otherwise, the mail is placed in Outlook's outbox, ready for you to send. You may also have to change the profile name to match that on your own system.

The sample function shown below is called from a form containing a text box (txtDomain) that holds the domain name of the recipients, and a list box (lstEmails) that holds the individual addresses of the recipients. This example is in fact part of a working application used several times a day to send test messages to new email accounts:

Private Function SendReturnEMail() As Boolean
' create an error handler
On Error GoTo SendReturnEMail_Err
   'set the default return value
   SendReturnEMail = False
   'we're using late binding for this app
   Dim objSession   As Object
   Dim objMessage   As Object
   Dim objRecipient As Object
   'declare some other utility variables
   Dim i            As Integer
   Dim sSubject     As String
   Dim sText        As String
   Dim sName        As String
   'set up the email message text
   sText = "This is an automatic test message, " & _
           vbCrLf & _
           "Please reply to the sender confirming receipt."
   'and the subject
   sSubject = "Test Message"
   'start with the top of the mapi hierarchy --
   'the session object
   Set objSession = CreateObject("mapi.session")
      'use the local Outlook default profile
      objSession.LogOn profilename:="Microsoft Outlook"
      'this application will send a number of test messages
      'to the members of a particular domain
      For i = 0 To lstEmails.ListCount - 1
         'build the addresses from the names in the list
         'and the given domain name
         sName = Trim(lstEmails.List(i)) & "@" & _
         'now create a new message object
         Set objMessage = objSession.outbox.messages.Add
            'feed in the required property values for the
            objMessage.subject = sSubject
            objMessage.Text = sText
            'create a new recipient for this message
            Set objRecipient = objMessage.Recipients.Add
               'and set it's properties
               objRecipient.Name = sName
               objRecipient.Type = 1
               'make sure the email address is resolved
               'now send the message
               objMessage.Send showdialog:=False
               'tidy up this message
            Set objRecipient = Nothing
         Set objMessage = Nothing
      'and go round again for the next one
      Next i
      'all done so off we go
   'tidying up as always
   Set objSession = Nothing
   'set the success return value
   SendReturnEMail = True
   Exit Function
   MsgBox Err.Number & vbCrLf & Err.Description & vbCrLf _
          & Err.Source
End Function

Output from VB to Excel

To finish with, here's an easy little application that places values from a VB application into an Excel spreadsheet. There are project-level (early bound) references created to both Excel and the ADODB 2.0 Reference Library. An ADO recordset has already been created and is passed as a parameter to the OutputToExcel function. The function creates an instance of a new Excel workbook and worksheet, then copies the values from the ADO recordset into the worksheet. Excel's functionality is used to perform a simple calculation on the data, the worksheet is saved, Excel is closed down, and all references are tidied up.

This example illustrates the power of a glue language such as Visual Basic. Here VB is acting as the glue between ADO, which is an ActiveX server, and Excel--controlling both to produce a simple yet patently powerful and seamless application:

Private Function OutputToExcel(oADORec As ADODB.Recordset) _
                 As Boolean
On Error GoTo cmdExcel_Err
   'set up the default return value
   OutputToExcel = False
   ' Declare the Excel object variables
   Dim oXLApp  As Excel.Application
   Dim oXLWBook As Excel.Workbook
   Dim oXLWSheet As Excel.Worksheet
   'start at the top of the model
   Set oXLApp = New Excel.Application
      'and work your way down
      Set oXLWBook = oXLApp.Workbooks.Add
         'until you get to the worksheet
         Set oXLWSheet = oXLWBook.Worksheets.Add
            oXLWSheet.Cells(1, 1).Value = oADORec!FirstValue
            oXLWSheet.Cells(2, 1).Value = oADORec!SecondValue
            ' do some stuff in Excel with the values
            oXLWSheet.Cells(3, 1).Formula = "=R1C1 + R2C1"
            ' save your work
            oXLWSheet.SaveAs "vb2XL.xls"
            'quit Excel
            ' always remember to tidy up before you leave
         Set oXLWSheet = Nothing
      Set oXLWBook = Nothing
   Set oXLApp = Nothing
   OutputToExcel = True
   Exit Function
   MsgBox Err.Description & vbCrLf & Err.Number & _
          vbCrLf & Err.Source
End Function

Silent Reporting: Logging the Error Event

Your efforts to resolve issues within an application are often frustrated by users not reporting errors. The user simply clicks past the message box reporting the error and continues. Either they forget or can't be bothered to contact the MIS department or the software developer to report the issue. There is a way you can store information about the error on the user's machine without having to go to the trouble of coding a file open/write/close routine that itself could cause a fatal error within the error handler.

The App object includes a method called LogEvent whose operation depends on the operating system being used. On NT the LogEvent method writes to the machine's event log, whereas in Windows 9x a log file is created or an existing log file appended to. Logging only takes place in compiled VB applications.

You can specify an event log file using the StartLogging method, which takes two parameters, the log filename and the log mode. (The App object's LogPath and LogMode properties, which you would expect to set before beginning logging, are read-only and can only be set by calling the StartLogging method.)

WARNING: Note that the log mode constants were missing from Version 5 of VB, so you either have to enter their literal values, or you have to define your own constants.

In Windows NT, if you call the StartLogging method but don't specify a log file, or in Windows 95, if you don't call the StartLogging method at all, VB creates a file called vbevents.log, which is placed in the Windows directory. To use event logging, you don't necessarily need to use the StartLogging method.

The LogEvent method itself takes two parameters. The first is a string containing all the detail you wish to store about the error or event. The second is an EventType constant, which denotes an error, information, or a warning. In NT, this event type value displays the correct icon in the event log, whereas in Windows 95, the word "Error," "Information," or "Warning" appears at the start of the item in the event log file.

TIP: In a previous section, "Error Handling in ActiveX Servers," you saw how to force MsgBox prompts to be automatically written to an event log by selecting the Unattended Application option. But which event log? The MsgBox function doesn't take a parameter to specify an optional event log, so VB will write the string contained within the Prompt parameter to the default vbevents.log in Windows 9x or to the application event log in Windows NT. However, you can place a call to the app object's StartLogging method in the class's Initialize event, thereby specifying a log file for all Msgbox and LogEvent calls.

Once you have an event log for your application, you can look back through the history of the application any time you choose. If you are networked to the user's machine, you can open the user's event log from your machine and detect problems without even leaving your desk.


#Const Directive

Named Arguments



#Const constantname = expression
Use: Required
Data Type: Variant (String)
Name of the constant.
Use: Required
Data Type: Literal
Any combination of literal values, other conditional compilation constants defined with the #Const directive, and arithmetic or logical operators except Is.


Defines a conditional compiler constant. By using compiler constants to create code blocks that are included in the compiled application only when a particular condition is met, you can create more than one version of the application using the same source code. This is a two-step process:

A conditional compiler constant can be assigned any string, numeric, or logical value returned by an expression. However, the expression itself can consist only of literals, operators other than Is, and another conditional compiler constant.

When the constant is evaluated, the code within the conditional compiler #If...Then block is compiled as part of the application only when the conditional compiler constant evaluates to True.

You may wonder why you should bother having code that is compiled only when a certain condition is met, when a simple If...Then statement could do the same job. The reasons are:

Rules at a Glance


#Const ccDebug = 1 'evaluates to true
Function testValue(sValue as String)
sValue = UCase(sValue)
testValue = sValue
#If ccDebug Then
   'this code only executes if ccDebug evaluates to true
   Debug.Print sValue
#End If
End Function

Programming Tips & Gotchas

Constants Defined Through the VB/VBA Interface

The rules for defining constants in the Conditional Compilation Arguments text box are somewhat different than for constants defined in code using the #Const statement. The value assigned through the VB/VBA interface must be an integer literal; it can't be an expression formed by using multiple literals or conditional constants, along with one or more operators, nor can it be a Boolean value (i.e., True or False). If multiple conditional constants are assigned through the user interface, they are separated from one another by a colon. For instance, the following fragment defines three constants, ccFlag1, ccFlag2, and ccFlag3:

ccFlag1 = 1 : ccFlag2 = 0 : ccFlag3 = 1
If waveOutGetNumDevs > 0 Then
   #Const ccSoundEnabled = True
#If ccSoundEnabled Then
   ' Include code for sound-enabled systems
   ' Include code for systems without a sound card
#End If

However, the code doesn't work as expected, since it includes or excludes the code supporting a sound card based on the state of the development machine, rather than the machine on which the application is running.

See Also

#If...Then...#Else Directive

AddressOf Operator

Named Arguments



AddressOf procedurename
Use: Required
The name of an API procedure.


Passes the address of a procedure to an API function. There are some API functions that require the address of a callback function as a parameter. (A callback function is a routine in your code that is invoked by the routine that your program is calling: it calls back into your code.) These callback functions are passed to the API function as pointers to a memory address. In the past, calling functions that required callbacks posed a unique problem to VB, since, unlike C or C++, it lacks a concept of pointers. However, the AddressOf operator allows you to pass such a pointer in the form of a long integer to the API function, thereby allowing the API function to call back to the procedure.

Rules at a Glance


The following example uses the EnumWindows and GetWindowText API calls to return a list of currently open windows. EnumWindows requires the address of a callback function as its first parameter. A custom function, EnumCallBackProc, is the callback function that populates the lstWindowTitles list box.

When the cmdListWindows command button is clicked, the list box is cleared, and a call to the EnumWindows API function is made, passing the AddressOf the EnumCallBackProc function and a reference to the list box control. EnumWindows then calls back to EnumCallBackProc, passing it the window handle of an open window and the reference to the list box. EnumCallBackProc then uses the GetWindowText API function to return the text in the titlebar of the window, passing it the window handle, a string buffer, and the length of that buffer. EnumCallBackProc is called by the API function as many times as is required, depending upon the number of open windows. The first portion of the example code must be stored in a code module, while the cmdListWindows_Click event handler can be stored in the form module containing the cmdListWindows button.

Option Explicit
Public Declare Function EnumWindows Lib "User32" _
                             (ByVal lpEnumFunc As Any, _
                             ByVal lParam As Any) As Long
Public Declare Function GetWindowText Lib "User32" _
                                Alias "GetWindowTextA" _
                                (ByVal hWnd As Long, _
                                 ByVal lpString As String, _
                                 ByVal cch As Long) As Long
Function EnumCallBackProc(ByVal hWnd As Long, _
                          ByVal lParam As ListBox) As Long
    On Error Resume Next
    Dim sWindowTitle As String
    Dim lReturn As Long
    sWindowTitle = String(512, 0)
    lReturn = GetWindowText(hWnd, sWindowTitle, 512)
    If lReturn > 0 Then
        lParam.AddItem sWindowTitle
    End If
    EnumCallBackProc = 1
End Function
Private Sub cmdListWindows_Click()
   Dim lReturn As Long
   lReturn = EnumWindows(AddressOf EnumCallBackProc, _
End Sub

Programming Tips & Gotchas

Private Sub cmdListWindows_Click()
Dim lReturn As Long
lReturn = DoWindowTitles(AddressOf EnumCallBackProc, _
End Sub
Private Function DoWindowTitles(CallBackAddr As Long, _
                          lstBox As ListBox) As Long
    'other stuff here
    DoWindowTitles = EnumWindows(CallBackAddr, lstBox)
End Function

See Also

Declare Statement

CallByName Function (VB6)

Named Arguments



CallByName(object, procedurename, calltype, _
[argument1,..., argumentn])
Use: Required
Data Type: Object
A reference to the object containing the procedure being called.
Use: Required
Data Type: String
The name of the procedure to call.
Use: Required
Data Type: vbCallType constant
A constant that indicates the type of procedure being called. vbCallType constants are listed in the next table.
Use: Optional
Data Type: Variant
Any number of variant arguments, depending on the argument list of the procedure to call.






The called procedure is a Property Get



The called procedure is a Property Let



The called procedure is a method; this can be a Sub or a Function within object



The called procedure is a Property Set

Return Value

Depends on the return value (if any) of the called procedure.


Provides a flexible method for calling a public procedure in a VB object module. Since procedurename is a string expression, rather than the hard-coded name of a routine, it's possible to call routines dynamically at runtime with a minimum of coding.

Rules at a Glance

Programming Tips & Gotchas


The following example takes CallByName and the amendments to CreateObject to their logical conclusion: a variable procedure call to a variable ActiveX server in a variable location. In this example, the SQL Server pubs database is used as the source of the data. Two ActiveX objects on two separate machines are used to create two different recordsets: one from the Authors table, the other from the Titles table. However, nowhere in the program are the names of the ActiveX DLLs, the procedures, or the remote servers mentioned.

The middle tier of this application uses the registry to store these names, allowing fast alteration of the application without touching a single line of code or creating incompatibilities between components. The repercussions of this approach to enterprise-wide programming are wide-reaching, and the prospects very exciting.

Only when dealing with the user interface of the client component are the names of the required datasets and fields specified. The Form_Load event calls a standard function to populate combo box controls with the required data:

Private Sub Form_Load()
    PopulateCombo cboAuthors, "Authors", "au_lname"
    PopulateCombo cboTitles, "Titles", "title"
End Sub

The PopulateCombo function calls a GetRecordset function in the first middle tier of the model, passing in the recordset name required (either Authors or Titles in this case) and a search criteria string that is concatenated into the embedded SQL script to refine the recordset. GetRecordset returns an ADO recordset that populates the desired combo box:

Private Function PopulateCombo(oCombo As ComboBox, _
                               sRecords As String, _
                               sField As String) As Boolean
    Dim adorRecords As ADODB.Recordset
    Dim sSearch As String
    If sRecords = "Authors" Then
        sSearch = "contract = 1 AND state = 'CA'"
        sSearch = ""
    End If
    Set adorRecords = oAdmin.GetRecordset(sRecords, sSearch)
    Do While Not adorRecords.EOF
        oCombo.AddItem adorRecords(sField)
    Set adorRecords = Nothing
End Function

The GetRecordset method that sits on a central machine interrogates the registry (using the GetSetting function) to determine the names of the ActiveX server, the machine, and the procedure to call. I've also coded an alternative method of obtaining these names using a Select Case statement (which is commented out in the code sample). Finally, the CreateObject function obtains a reference to the appropriate ActiveX server on the appropriate machine and a call is made to the function in that server to obtain the correct recordset:

Public Function GetRecordset(sRecords As String, _
                             sCriteria As String _
                             ) As ADODB.Recordset
    Dim sServer     As String
    Dim sLocation   As String
    Dim sMethod     As String
    Dim oServer     As Object
    sServer = GetSetting(App.Title, sRecords, "Server")
    sLocation = GetSetting(App.Title, sRecords, "Location")
    sMethod = GetSetting(App.Title, sRecords, "GetMethod")
' An alternative method of obtaining the names of the 
' elements of the remote procedure call is to hard-code
' them into the application as follows:
'    Select Case sRecords
'        Case Is = "Titles"
'            sServer = "TestDLL.Titles"
'            sLocation = "NTSERV1"
'            sMethod = "GetTitles"
'        Case Is = "Authors"
'            sServer = "Test2DLL.Authors"
'            sLocation = "NTWS2"
'            sMethod = "getAuthors"
'        Case Else
'            Set GetRecordset = Nothing
'            Exit Function
'    End Select
    Set oServer = CreateObject(sServer, sLocation)
    Set GetRecordset = CallByName(oServer, _
                                  sMethod, _
                                  VbMethod, _
End Function

The code to create the recordsets in TestDLL.Titles and Test2DLL.Authors isn't shown here, as it's straightforward database access code.

Now, imagine for a moment that the organization using this application wanted a minor alteration in the way the Authors recordset was presented to the client (a different sort order, for example). You can now make a change to the procedure, calling it getAuthorsRev ; compile a completely new ActiveX server; and place it on the remote server. Then with two quick edits of the registry, all the clients in the organization would instantly access the new procedure with a minimum of fuss, no loss of component compatibility, zero downtime, and an almost seamless transition.

See Also

Call Statement

Declare Statement

Named Arguments



Syntax for subroutines
[Public | Private] Declare Sub name Lib "libname" _
[Alias "aliasname"] [([arglist])]
Syntax for functions
[Public | Private] Declare Function name Lib "libname"
[Alias "aliasname"] [([arglist])] [As type]

Use: Optional
Keyword used to declare a procedure that has scope in all procedures in all modules in the application.
Use: Optional
Keyword used to declare a procedure that has scope only within the module in which it's declared.
Use: Optional
Keyword indicating that the procedure doesn't return a value. Mutually exclusive with Function.
Use: Optional
Indicates that the procedure returns a value. Mutually exclusive with Sub.
Use: Required
Data Type: String
Any valid procedure name within the DLL or code library. If the aliasname argument is used, name represents the name the function or procedure is called in your code, while aliasname represents the name of the routine as found in the external library.
Use: Required
Keyword indicating that the procedure is contained within a DLL or other code library.
Use: Required
Data Type: String
The name of the DLL or other code library that contains the declared procedure.

Use: Optional
Keyword whose presence indicates that name is different from the procedure's real name within the DLL or other code library.
Use: Optional
Data Type: String
The real name of the procedure within the DLL or code library.
Use: Optional
Data Type: Any
A list of variables representing the arguments that are passed to the procedure when it's called. (For details of the arglist syntax and elements, see the entries for the Sub statement or Function statement.)
Use: Optional

Data type of the value returned by a function. (For further details see the Function statement entry.)


Used at module level to declare references to external procedures in a dynamic-link library (DLL).

Rules at a Glance


Option Explicit
Declare Function GetVersion Lib "kernel32"() As Long
Public Function WhereAmI() As Boolean
   Dim lWinVersion As Long
   Dim lWinMajVer As Long
   Dim lWinMinVer As Long
   Dim sSys As String
   lWinVersion = GetVersion()
   lWinMajVer = lWinVersion And 255
   lWinMinVer = (lWinVersion And 65280) / 256
   If lWinVersion And &H80000000 Then 
      sSys = "Windows 95"
      sSys = "Windows NT"
   End If
   Msgbox "Platform: " & sSys & vbCrLf & _
          "Version: " & lWinMajVer & "." & lWinMinVer

Programming Tips & Gotchas

See Also

Sub Statement, Function Statement, StrConv Function

DoEvents Function

Named Arguments




Return Value

In VBA, DoEvents returns 0; in the retail version of VB, it returns the number of open forms.


Allows the operating system to process events and messages waiting in the message queue. For example, you can allow a user to click a Cancel button while a processor-intensive operation is executing. In this scenario, without DoEvents, the click event wouldn't be processed until after the operation had completed; with DoEvents, the Cancel button's Click event can be fired and its event handler executed even though the processor-intensive operation is still executing.

Rules at a Glance

Control is returned automatically to your program or the procedure that called DoEvents once the operating system has processed the message queue.


The following example uses a UserForm with two command buttons to illustrate how DoEvents interrupts a running process:

Option Explicit
Private lngCtr As Long
Private blnFlag As Boolean
Private Sub CommandButton1_Click()
   blnFlag = True
   Do While blnFlag
      lngCtr = lngCtr + 1
   MsgBox "Loop interrupted after " & lngCtr & _
          " iterations."
End Sub
Private Sub CommandButton2_Click()
   blnFlag = False
End Sub

Programming Tips & Gotchas