Part two discusses the following important elements you need to know about COM when developing ADO applications:
Reading the Type Library
Because ADO is a language-neutral Automation server, the more you know about COM, the more you can accomplish with ADO. Even if you are programming in Visual Basic for Applications (VBA), which hides many COM details, it helps to know COM. Whatever language you use, the ability to read and understand the ADO type library (contained within msado15.dll for ADO 1.5 and later) greatly enhances your ability to fully utilize features of ADO.
Reading a type library is not hard, and there are excellent tools for doing so. VBA features an Object Browser in both Visual Basic and Access. You can use #import, which returns the contents of the type library in a .tlh file. (This method is useful for non-#import Visual C++ developers so they can examine enumerated type definitions that MFC OLE might not otherwise provide.) Or you can use a language-neutral utility for viewing language-neutral object models: the OLE COM Object Viewer, which ships with Visual Studio.
To view a type library with the OLE-COM Object Viewer
Figure 3. Viewing the ADO type library
In this case, use the OLE-COM Object Viewer to examine the Command.Execute() method. You can observe from the type library that each of the arguments of Command.Execute() are optional, and that one has a default value. This is significant because not all COM implementation mechanisms take advantage of either optional arguments or default values; that is, you have to provide some value anyway. If you want to know what the default is, you may have to use the type library. For each of the languages and implementations discussed in Part One, a discussion of the use of default values and optional arguments will follow.
Hidden Type Library Elements, Default Values, and Optional Arguments
The type library viewer won't actually indicate which members of the ADO type library, be it values of enumerated type or actual interfaces, are hidden, and which ones are public. Command-line completion within Visual Basic for Applications (VBA) will not expose hidden elements of the type library, but you can view hidden members with the Show Hidden Members command in the Object Browser in Visual Basic. COM implementations for Visual C++ and Java will typically expose all members, hidden or not, in any class wrappers that are generated. Most of the time, though, you will not need to use or know about hidden members of the type library.
Viewing the type library is useful for determining default values of a given property or argument of a method. It also lets you determine which arguments are optional. The COM implementation you use may not support either defaults or optional arguments; that is, you may have to specify every argument when calling a method call yourself. Viewing the type library is useful for understanding what ADO expects, or rather, what more robust COM implementations provide for free.
Many of the ADO enumerated types typically correspond either to arguments within the methods of ADO objects, or to properties of ADO objects. Not all arguments and properties have corresponding enumerated types, but most do. For any given enumerated type, the valid values are listed, and optionally two special values, Unknown and Unspecified.
Unknown is usually for a property in a read-only state, and is indicative of an object that hasn't been initialized or that is in an unknown state. Unspecified tells ADO to either use the default value for the property or argument, or to use the value previously assigned to that property. Unspecified is actually a hidden member of a given enumerated type.
There is one special case for Unknown used with the CommandTypeEnum property, but this is an exception and not fully in line with the rest of the ADO specification for the use of Unknown values within an enumerated type.
Occasionally, the argument of a method will have a corresponding property with the same object that the method belongs to. This scenario is the exception, not the rule, but it is important to understand how Unspecified may affect the behavior you receive from ADO. As a rule, ADO will use the value in the method and ignore the value you set in the property, unless you used one of the Unspecified enumerated values.
The Command.Execute method demonstrates this, whose third argument also corresponds to the Command.CommandType property. Specifying adCmdUnspecified in the third argument of Command.Execute tells ADO to use the value in the CommandType property. Otherwise, the value in the Execute method will be used and the CommandType property will be ignored. This particular example is further compounded by the fact that it is one of the rare cases where ADO also allows you to specify an Unknown value for CommandType. In this case, giving ADO an adCmdUnknown in the CommandType property (assuming it's not overridden by the Execute method's third argument) or in the Execute method's third argument tells ADO that the CommandText to execute could be a query statement, a table name, or a stored procedure name. ADO will try all three until one works.
Listing 16 gives some examples of how Unspecified and Unknown work with the Command object. This provides a definitive, if somewhat complex sample of how default values, optional arguments, Unknown, and Unspecified work together.
Listing 16: Executing a command
' Technique #1
Set Cmd1.ActiveConnection = conn1
Cmd1.CommandText = "SELECT * FROM Authors"
' CommandType by default is unknown
Cmd1.Execute
' Technique #2
Set Cmd2.ActiveConnection = conn1
Cmd2.CommandText = "SELECT * FROM Authors"
Cmd2.Execute v1, , adCmdUnknown
' Technique #3
Set Cmd3.ActiveConnection = conn1
Cmd3.CommandText = "SELECT * FROM Authors"
Cmd3.Execute v1, , adCmdText
All three techniques accomplish the same goal, although using Unknown slows performance because ADO takes time to test the actual type of the CommandText provided. With Visual C++ or Java, you would have to specify values for each argument because COM implementations in these languages do not take advantage of either optional arguments or default values for method arguments.
Default Properties and Collections
At times, an element in the type library may be the "default" property of a particular object or collection. In this case, you wouldn't need to see or use the element unless the implementation you are using doesn't recognize it as a default. An example of this is the Item property and its usage in Collections.
Typically, to reference an item within a collection (such as Fields, Properties, and Parameters) you provide an index to the Item property of the collection. However the Item property is the default property of a collection, and in languages such as Visual Basic for Applications (VBA), you can easily skip it. With command-line completion, you won't even see that it is there. Visual C++ and Java developers do not have this support, and therefore need to know the Item property of a collection exists and reference it.
Here is equivalent code given in VBA, Visual C++, and Java for retrieving the value of the first column in an open recordset. Note that because of VBA's support for recognizing the default member of a given object or property, it has the greatest number of ways, in one line of code, to retrieve the first column's value.
Listing 17: Comparison of the Item property in VBA, Visual C++, and Java
' VBA Syntax
Dim v as variant
v = Rs1.Fields.Item("Au_ID").Value
v = Rs1.Fields.Item(0).Value
v = Rs1.Fields("Au_ID").Value
v = Rs1.Fields(0).Value
v = Rs1("Au_ID").Value
v = Rs1(0).Value
v = Rs1!Au_ID
// C++ Syntax (using #import)
_variant_t v;
v = Rs1->Fields->GetItem( _variant_t( 0L ) )->Value;
v = Rs1->Fields->GetItem( _variant_t( "au_id" ) )->Value;
// Java Syntax (using Java Type Library Wizard)
Variant v = new Variant();
Variant v1 = new Variant();
v1.putInt( 0 );
v = Rs1.getFields().getItem( v1 ).getValue()
v1.putString( "Au_ID" );
v = Rs1.getFields().getItem( v1 ).getValue()
The ADO Type Library and COM Implementations
Microsoft Visual Basic for Applications
Visual Basic for Applications (VBA) typically exposes required elements of the type library using command-line completion. Most of the time this provides you with all the information you need to use ADO. There are occasional exceptions, and the Object Browser in both Visual Basic 5.0 and Access 97 provide the equivalent of the OLE-COM Object Viewer for any type libraries you have referenced in your current application. This is useful because the IDE does not expose hidden members of the ADO type library with command-line completion. You can enable the Show Hidden Members option of the Object Browser to view these otherwise unseen members of the type library. This is illustrated in Figure 4.
Figure 4. Viewing hidden members of the ADO type library
An example of a hidden member, as mentioned earlier, is the Unspecified value for many of the ADO enumerated types.
Microsoft Visual C++
For Visual C++ the need to view the type library is dependent upon the mechanism you choose to implement ADO. You must specify valid values for each argument of a given method. None of the three mechanisms in Visual C++ take full advantage of default values or optional arguments:
Java
The Java Type Library Wizard and Microsoft SDK for Java both create class wrappers. However, you cannot step into the wrappers generated by the Java Type Library Wizard within the debugger. The Java Type Library Wizard generates classes encapsulating the type library, and a file, summary.txt, for each type library you reference.
Scripting
There is no inherent support for reading the type library or even taking advantage of command-line completion with scripting.
COM Data Types
ADO uses three COM data types, Bstr, Variant, and SafeArray, that you need to handle. Depending upon the native COM support of the language you are using, this might have an impact on your code. Of the three, SafeArray is probably the most challenging to learn, and COM provides excellent support for the SafeArray.
VBA, VBScript, and JScript are not strongly typed, and each of these implementations features a Variant data type. With VBA, you can use other types besides Variant; in VBScript, every variable is of type Variant. Visual C++ and Java are strongly typed, which places more responsibility on you to convert between data types.
Empty Variant and Bstr Values
Within Visual C++ or Java, you must create empty Bstr and Variants for arguments with default values that VBA would allow you to safely ignore, or use Unknown or Unspecified enumerated type values.
Listing 18: The need for empty Bstr and Variant values
' VBA Syntax
Conn1.ConnectionString = "DSN=AdoDemo;UID=admin;PWD=;"
Conn1.Open
Set Cmd1.ActiveConnection = Conn1
Set Rs1 = Conn1.Execute()
// C++ Syntax (using #import)
_variant_t vtEmpty (DISP_E_PARAMNOTFOUND, VT_ERROR);
_bstr_t bstrEmpty(L"");
_bstr_t Source(L"SELECT * FROM Authors");
ADODB::_ConnectionPtr Conn1 = NULL;
ADODB::_RecordsetPtr Rs1 = NULL;
HRESULT hr = Conn1.CreateInstance(__uuidof( ADODB::Connection ));
Conn1->ConnectionString = "DSN=AdoDemo;UID=admin;PWD=;";
Conn1->Open( bstrEmpty, bstrEmpty, bstrEmpty, -1 );
Rs1 = Conn1->Execute(
Source,
&vtEmpty,
ADODB::adCmdText );
Rs1->Close();
Conn1->Close();
::MessageBox( NULL, "Success!", "", MB_OK );
// Java Syntax (using the Java Type Library Wizard)
Variant vtEmpty;
Variant vtEmpty2;
String bstrEmpty = new String("");
msado15._Connection Conn1 = new msado15.Connection();
msado15._Recordset Rs1 = new msado15.Recordset();
Variant vtEmpty = new Variant("");
String bstrEmpty = new String("");
String Source = new String("SELECT * FROM Authors");
vtEmpty.VariantClear();
Conn1.putConnectionString( "DSN=AdoDemo;UID=admin;PWD=;" );
Conn1.Open( bstrEmpty, bstrEmpty, bstrEmpty, -1 );
Rs1 = Conn1.Execute( Source,
vtEmpty,
msado15.CommandTypeEnum.adCmdText);
Note that even though Connection.Open() requires four arguments and Command.Execute() requires three arguments, the Visual Basic code does not include them. With Visual C++ or Java, you may not have the luxury of default arguments being exposed by the classes that wrap ADO objects. In particular, you must define the empty Variants using the parameters DISP_E_PARAMNOTFOUND and VT_ERROR;
otherwise, you may get errors when you attempt to use them with ADO.
The code listing above explicitly defines vtEmpty, however #import automatically creates a Variant of the exact same type called vtMissing. You can easily use vtMissing in place of the definition for vtEmpty. vtMissing was defined to be used for optional arguments of any method in a COM type library.
Converting Variants to Native Data Types
With Visual Basic, a loosely typed language, it is simple to use Variants. In Java you can assign and retrieve other data types with a Variant fairly easily. With Visual C++, however, you must be more rigorous in your coding. With the Rosetta Stone samples, a function called CrackStrVariant converts a Variant into native data types. This function was originally included in the Visual C++ sample DAOVIEW and offers some code for converting Variants to native data types. It was written for MFC classes CString and COleVariant, but it can easily be adapted for non-MFC classes. In fact, COleVariant is just a class derived from the Variant data type. This code has also been included in the Rosetta Stone samples as a stand-alone code segment, crackvar.cpp.
ANSI to Unicode Conversions
With VBA and Java, you don't need to know if your strings are ANSI, Unicode, or if they need to be converted. Visual C++, operating at a lower level, does require you to be aware of this. For #import developers, you can use _bstr_t and _variant_t to handle conversions for you implicitly. For MFC, CString::SysAllocString() converts an ANSI string to a Unicode string. But if you are using COM directly without any helper classes such as _variant_t or COleVariant, you need to use the Microsoft Win32® API to perform these conversions. You can borrow some MFC or #import code to help. In Listing 19, the CString::SysAllocString() method has been modified to be a stand-alone, MFC-independent function.
Listing 19: ANSI to UNICODE conversion helper
BSTR AnsiToBSTR( LPTSTR pchData )
{
// Helper function derived from source of CString::AllocSysString()
BSTR bstr = NULL;
#if defined(_UNICODE) || defined(OLE2ANSI)
bstr = ::SysAllocStringLen(pchData, wcslen( pchData ) );
#else
int nLen = MultiByteToWideChar(CP_ACP, 0, pchData,
strlen( pchData ), NULL, NULL);
bstr = ::SysAllocStringLen(NULL, nLen);
if (bstr == NULL)
return NULL;
MultiByteToWideChar(CP_ACP, 0, pchData,
strlen( pchData ), bstr, nLen);
#endif
return bstr;
}
This is but one technique you can use for creating a Bstr, and is useful for strings assigned or created dynamically during execution. Consider the COM code in Listing 9 that assigned a Bstr value to a Variant named Source. There are actually several ways you can accomplish this, as shown in Listing 20.
Listing 20: Ways to create a Bstr from an ANSI string
VARIANT Source;
Source.vt = VT_BSTR;
Source.bstrVal = ::SysAllocString(L"SELECT * FROM Authors");
Source.bstrVal = AnsiToBSTR( "SELECT * FROM Authors" );
Using SafeArrays
SafeArray is an Automation data type used to pass data of varying types safely between client and server processes. Use them in ADO as optional ways to add records: with the AddNew and OpenSchema methods, and with the Parameters argument of the Command.Execute method.
SafeArrays are not complex, but can be more intimidating than other data types. Visual Basic for Applications (VBA) handles them invisibly with its Variant data type. Visual C++ handles them with the COM API and supports class wrappers such as COleSafeArray (MFC OLE). Visual J++ offers similar support in Java with its SafeArray type. The following listings demonstrate how to use OpenSchema to retrieve column information in VBA, Visual C++, and Java.
Listing 21: Using SafeArrays with the OpenSchema method in VBA
Dim conn1 As ADODB.Connection
Dim rs1 As ADODB.Recordset
Dim vsa
Set conn1 = New ADODB.Connection
conn1.Open "DSN=AdoDemo;UID=admin;PWD=;"
vsa = Array(Empty, Empty, "Authors", Empty)
Set rs1 = conn1.OpenSchema(adSchemaTables, vsa)
Do Until rs1.EOF
Debug.Print "Table name: " & _
rs1!TABLE_NAME & vbCr & _
"Table type: " & rs1!TABLE_TYPE & vbCr
rs1.MoveNext
Loop
rs1.Close
conn1.Close
MsgBox "Success!"
In this case, it is trivial to create a SafeArray with VBA. You define a Variant and then use the VBA Array function to build the array. With Visual C++, you have to do a bit more work, as shown in Listing 22.
Listing 22: Using SafeArrays with the OpenSchema method in Visual C++
ADODB::_ConnectionPtr Conn1 = NULL;
ADODB::_RecordsetPtr Rs1 = NULL;
HRESULT hr = S_OK;
_variant_t vtEmpty (DISP_E_PARAMNOTFOUND, VT_ERROR);
hr = Conn1.CreateInstance( __uuidof( ADODB::Connection ) );
Conn1->Open( L"DSN=Pubs;UID=sa;PWD=;", L"", L"", -1 );
// Create elements used in the array
_variant_t varCriteria[4];
varCriteria[0] = vtEmpty;
varCriteria[1] = vtEmpty;
varCriteria[2] = L"Authors";
varCriteria[3] = vtEmpty;
const int nCrit = sizeof varCriteria /
sizeof varCriteria[0];
// Create SafeArray Bounds and initialize the array
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = nCrit;
SAFEARRAY *psa = SafeArrayCreate( VT_VARIANT, 1, rgsabound );
// Set the values for each element of the array
for( long i = 0 ; i < nCrit && SUCCEEDED( hr );i++)
{
hr = SafeArrayPutElement(psa, &i,&varCriteria[i]);
}
// Initialize and fill the SafeArray
VARIANT vsa;
vsa.vt = VT_VARIANT | VT_ARRAY;
V_ARRAY(&vsa) = psa;
// call OpenSchema
Rs1 = Conn1->OpenSchema(ADODB::adSchemaTables,vsa);
Rs1->Close();
Conn1->Close();
Creating the SafeArray in Visual C++ takes several steps. First, create an array of Variants and set them to the values required by the particular schema you are opening. Then create the boundary structure for the SafeArray and use it to initialize the size and contents of the SafeArray. Assign each element in the SafeArray from your Variant array. Finally, assign the completed SafeArray as a member of a Variant type to pass as the second argument to the OpenSchema method. (While this example used the #import _variant_t class, you could just as easily use the Variant type.)
The following listing demonstrates how to create a SafeArray in Java.
Listing 23: Using SafeArrays with the OpenSchema method in Java
msado15._Connection Conn1 = new msado15.Connection();
msado15._Recordset Rs1 = new msado15.Recordset();
Conn1.Open( "DSN=AdoDemo;UID=admin;PWD=;", "", "", -1 );
SafeArray sa = new SafeArray(Variant.VariantVariant ,4 );
sa.setString( 0, "" );
sa.setString( 1, "" );
sa.setString( 2, "authors" );
sa.setString( 3, "" );
Variant Vsa = new Variant( sa, false );
Variant vtEmpty = new Variant(); // OpenSchema 3rd param
vtEmpty.noParam();
Rs1 = Conn1.OpenSchema(SchemaEnum.adSchemaTables, Vsa,vtEmpty);
System.out.println( "Success!\n" );
Java supports a native SafeArray type, which you can use to encapsulate some of the steps required for Visual C++. Declare a new instance of the Java SafeArray and declare it to be an array of type Variant. Initialize each element in the array and pass the completed SafeArray into a new Variant argument to use with the OpenSchema method.
Regardless of the programming language you use, error handling with ADO deserves careful attention. Any error condition you encounter will be raised through COM to the native error-handling mechanism you are utilizing. As a convenience, the Errors collection lists additional warnings exposed by the underlying provider.
The ADO Errors collection is available if you have instantiated an ADO Connection object. This collection is reset and catches errors any time an unexpected condition is raised by an underlying provider (data or service) used by ADO. In addition, components underlying the provider may raise errors back to the provider, which it passes to ADO. For example, errors encountered by ODBC are raised to the OLE DB Provider for ODBC, and from there to the ADO Errors collection. A common misconception, though, is that errors ADO itself encounters are not placed in this collection but must instead be caught by whatever native error-handling mechanism the language or implementation you are using provides. Therefore your error handling must check both the ADO Errors collection as well as whatever native mechanism for raising and catching errors exists.
When an error condition is encountered, whether that error is raised to the ADO Errors collection or to your language's native error-handling mechanism, you may see an error message in one of three formats, as shown in the following table. For example, the ADO error "No Current Record" is defined as having a numeric value of 3021. But it could also appear as an 8-digit hexadecimal value or as a negative long value. The table below presents all three valid results for 3021.
Table 1. Error Code Conversions
Error code | Error message |
3021 | No Current Record |
0x80040BCD | No Current Record |
-2147218483 | No Current Record |
0x0BCD is simply the hexadecimal conversion of the value 3021. The value shows up as 0x80040BCD because all ADO errors have 8004 prepended to them. The long negative value, 2147218483, is simply a LONG conversion of 80040BCD.
The remainder of this section discusses what you need to implement robust error handling in your application and includes code excerpts from the ADO Rosetta Stone samples.
Microsoft Visual Basic for Applications
With Visual Basic for Applications (VBA) you need to verify both the ADO Errors collection as well as information raised to the Err object. Listing 24 demonstrates how to implement ADO error handling with VBA.
Listing 24: ADO error handling with VBA
msado15._Connection Conn1 = new msado15.Connection();
msado15._Recordset Rs1 = new msado15.Recordset();
Conn1.Open( "DSN=AdoDemo;UID=admin;PWD=;", "", "", -1 );
SafeArray sa = new SafeArray(Variant.VariantVariant ,4 );
sa.setString( 0, "" );
sa.setString( 1, "" );
sa.setString( 2, "authors" );
sa.setString( 3, "" );
Variant Vsa = new Variant( sa, false );
Variant vtEmpty = new Variant(); // OpenSchema 3rd param
vtEmpty.noParam();
Rs1 = Conn1.OpenSchema(SchemaEnum.adSchemaTables, Vsa,vtEmpty);
System.out.println( "Success!\n" );
Microsoft Visual C++
When calling an ADO method, regardless of language used, an error condition is typically returned as an HRESULT. This HRESULT is translated into an exception for #import (_com_error exception class) and MFC OLE (COleDispatchException class). With COM programming, you must review the returned HRESULT of each method to ensure that you have not encountered an error condition.
Exception handling within Visual C++ is recommended for all three implementations, although exceptions are far less likely to occur with native COM programming. Your application could raise two types of exceptions: Win32 and C++ exceptions. The ADO Rosetta Stone samples use the EXCEPTEX sample (available in the Visual C++ Knowledge Base) to convert Win32 structured exceptions into C++ exceptions and catch them with try/catch syntax. Whether you use #import, MFC OLE, or native COM programming, your application should have robust and comprehensive exception handling.
The modified version of the EXCEPTEX sample has been included in the log.h and log.cpp files. For help catching C++ and Win32 structured exceptions, see the EXCEPTEX Knowledge Base article available from http://support.microsoft.com/support. Listing 25 gives an example of what it takes to ensure you have robust exception handling. This code is a small excerpt of both the EXCEPTEX sample and the LOG files provided with the ADO Rosetta Stone samples for Visual C++, and provides working exception handling code you can add to your application.
Listing 25: Catching C++ and Win32 exceptions
#include <eh.h> // Required for Win32 Exception Handling
#include <stdio.h> // Optional, specific to this sample
#include <afxwin.h> // Required for MFC Exception Handling
#include <afxdisp.h> // Required for MFC Exception Handling
#import <msado15.dll> rename("EOF", "adoEOF")
// Class for Win32 Structured Exception Handling
class SEH_Exception {
private:
unsigned int m_uSECode;
public:
SEH_Exception(unsigned int uSECode) : m_uSECode(uSECode) {}
SEH_Exception() {}
~SEH_Exception() {}
unsigned int getSeHNumber() { return m_uSECode; }
};
// Raise Win32 Exception within a C++ Class SEH_Exception
static void MappingSEHtoCPPExceptions
(
unsigned int uExceptionCode,
_EXCEPTION_POINTERS*
)
{
throw SEH_Exception( uExceptionCode );
}
// Sets up mapping between Win32 SEH & C++ Exception Handling
void LogEnable()
{
_set_se_translator(MappingSEHtoCPPExceptions);
}
void main( void )
{
try
{
ADODB::_ConnectionPtr Conn1 = NULL;
Conn1.CreateInstance( __uuidof( ADODB::Connection ) );
LogEnable();
// To see each of the catch blocks work, comment out all but
// one of the following 4 lines, each of which throws an
// exception.
// Bogus DataSource Name, raises _com_error
Conn1->Open( L"", L"", L"", -1 );
// Explicitly raise MFC Exception
AfxThrowMemoryException();
// Explicitly raise SEH Exception
RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, NULL);
// Explicitly raise exception of a type not caught below
// i.e. to the catch handlers, an "Unknown exception"
throw 0L; // Raises exception of type long
}
// Catch Blocks -- All of which are a subset of the
// Exception Handling demonstrated in LOG.CPP
// in the C++ Rosetta Stone Samples
catch( CException *e )
{
CString strName;
CRuntimeClass *pClass = e->GetRuntimeClass();
printf( "MFC Exception(%s) thrown\n",
(LPCTSTR) pClass->m_lpszClassName );
}
catch( _com_error &e )
{
printf( "#import encountered failed HRESULT\n" );
printf( "\tCode = %08lx\n", e.Error());
}
catch( SEH_Exception &e )
{
printf( "Win32 Structured Exception Raised\n" );
printf( "\t Error Code %08lx\n", e.getSeHNumber() );
}
catch(...)
{
printf( "Caught an exception of unknown type\n" );
}
::MessageBox( NULL, "Success!", "", MB_OK );
}
COleDispatchDriver::m_lpDispatch and Unrecoverable Exceptions
One weakness of the MFC OLE implementation for an Automation server is that it is possible to generate an exception from which there is no recovery. That is, if you do not instantiate an actual ADO object, you can still invoke the methods of the helper class on that object. As long as the method is not returning another object, you will receive TRACE messages telling you, in a debug build, that you have not instantiated the object. Consider, though, calling Connection.Execute, which returns a Recordset object. Listing 26 generates an exception that triggers exceptions within exceptions, and there is no way to catch these. Before running this code sample, though, you should save your work in all other open applications (especially if running it in debug mode on Windows 95).
Listing 26: The Unrecoverable exception via MFC OLE
#include <eh.h> // Required for Win32 Exception Handling
#include <stdio.h> // Optional, specific to this sample
#include <afxwin.h> // Required for MFC Exception Handling
#include <afxdisp.h> // Required for MFC Exception Handling
#import <msado15.dll> rename("EOF", "adoEOF")
...
// Class for Win32 Structured Exception Handling
class SEH_Exception {
private:
unsigned int m_uSECode;
public:
SEH_Exception(unsigned int uSECode) : m_uSECode(uSECode) {}
SEH_Exception() {}
~SEH_Exception() {}
unsigned int getSeHNumber() { return m_uSECode; }
};
// Raise Win32 Exception within a C++ Class SEH_Exception
static void MappingSEHtoCPPExceptions
(
unsigned int uExceptionCode,
_EXCEPTION_POINTERS*
)
{
throw SEH_Exception( uExceptionCode );
}
// Sets up mapping between Win32 SEH & C++ Exception Handling
void LogEnable()
{
_set_se_translator(MappingSEHtoCPPExceptions);
}
void main( void )
{
try
{
ADODB::_ConnectionPtr Conn1 = NULL;
Conn1.CreateInstance( __uuidof( ADODB::Connection ) );
LogEnable();
// To see each of the catch blocks work, comment out all but
// one of the following 4 lines, each of which throws an
// exception.
// Bogus DataSource Name, raises _com_error
Conn1->Open( L"", L"", L"", -1 );
// Explicitly raise MFC Exception
AfxThrowMemoryException();
// Explicitly raise SEH Excetption
RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, NULL);
// Explicitly raise exception of a type not caught below
// i.e. to the catch handlers, an "Unknown exception"
throw 0L; // Raises exception of type long
}
// Catch Blocks -- All of which are a subset of the
// ext:] [.156 [End Scenario] [Start Scen] [Name]
sform Copy Test [Test Locati C:\sqltools\Transform\Dmp\river.mst (155)
Time] 98 13:09:04 etail 0] Failed at:
C:\sqltnsform\DTSPump\DTSPumpDriver.mst (161)
Trapped at: C:\sqltools\Transform\Dmp\umpDriver.mst (161) sul
FAIL [Reason] [Error:] [Microsoft][OServer Driver][SQL
Server]Cannot opabase requesin n 'dtssource'. Login fails.ourrosoft OLE
DB Provider for ODBC Drivers [HelpFile:pContext:] [Elapsed Time]
0.156 [End Scenar
[Start Scena e]
Simple DDQ Test using Peal Script, DDQ_IID_IT [Test
Location] C:\sqltools\Transform\DTSPumwŁ_p\DTSPumpDriver.mst (155)
[Start Time] 06/30/1913:09:05 etail 0] Failed at:
C:\sqltools\Transform\DTSPump\DTSPumpDriver.mst (161) [Detail 0]
Trapped at: C:\sqltools\Transform\DTSPump\DTSPumriver.mst (161)Result]
FAIL [ [Error:] [Microsoft][ODBC SQL Server Driver][SQL
Server]Cannot open database requested in login 'dtssource'. Login fails.
[Source:] Microsoft OLE DB Provider for ODBC Drivers [HelpFile:]
[HelpContext:] [Elapsed Time] 0.156 [End Scenario] [Start
Scenario] [Name] Simple Transform Copy Test [Test
Location] C:\sqltools\Transform\DTSPump\DTSPumpDriver.mst (155)
[Start Tme] 06/30/1998 13:09:05 [Detail 0] Failed at:
C:\sqltools\Transform\DTSPump\DTSPumpDriver.mst
ltools\Transform\DTSPump\DTSPumpDriver.mst (161) [Result] FAIL
[Reason] [Error:] [Microsoft][ODBC SQL Server iver][SQL Server]Cannot
open database requested in login 'dtssource'. Login fails. [Source:]
Microsoft OLE DB Provider for ODBC Drivers [HelpFile:] [HlpContext:]
[Elapsed Time] 0.156 [End Scenario] [Start Scenario] [Name]
imple DDQ Test using VB Script, DDQ_INVALID_UPDATE [st
Location]C:\sqltools\Transform\DTSPump\DTSPumpDriver.mst (155) [Start
Time] 06/30/1998 13:09:05 [Detail 0]
Faileump\DTSPumpDriver.mst (161) [Detail 0] Trapped at:
C:\sqltools\TransforDTSPump\DTSPumpDriver.mst (161) [Result]
FAIL [Reason] [Error:] [Microsoft][ODBC SQL Server Driver][SQL
Server]Cannot open database requested in login 'dtssource'. Login fails.
[Source:] Microsoft OLE DB Provider for ODBC Drivers [HelpFile:]
[HelpContext:] [Elapsed Time] 0.203 [End Scenario]
[StarSenario] [Name] Simple Transform Copy Test [Test
Location] C:\sqltools\Transform\DTSPump\DTSPumpDriver.mst 55) [Start
Time] 06/30/1998 13:09:0 [Detail 0] Failed at:
C:\sqltools\Transform\DTSPump\DTSPumpDriver.mst (161) [Detail 0]
Trapped at: C:\sqltools\TransfDTSPumpDriver.mst (161) [Result]
[Reason] [Error:] [Microsoft][ODBC SQL Serveerver]Cannot open
database requested in login 'dtssource'. Login fails. [Source:]
Microsoft OLE D Drivers [HelpFile:] [HelpContext:] [Elapsed Time]
0.156 [End Scenario] [Start Scenario] [Name] Simple
DDQ Test using Java Script, DDQ_INVALID_UPDATE [Test Location]
C:\sqltools\Transform\DTSPump\DTSPumpDriver.mst (155)
[Star31p\DTSPumpDriver.mst (161) [Detail 0] Trapped at:
C:\sqltools\Transform\DTSPump\DTSPumpDriver.mst (161)
[Result] FAIL [Reason] [Error:] [Microsoft][ODBC
SQL Server Driver][SQL Server]Cannot open database requestedin login
'dtssource'. Login fails. [Source:] Microsoft OLE DB Provider for ODBC
Drivers [Helptained for the ADOVJ sample.
Listing 28: Error handling with ADO with Java Type Library Wizard
public static void AdoErrorEx( msado15._Connection Conn1 )
{
// Local Error Objects
msado15.Errors Errs1 = null;
msado15.Error Err1 = null;
long nCount;
Variant v = new Variant();
if ( Conn1 == null )
return;
Errs1 = Conn1.getErrors();
nCount = Errs1.getCount();
for( long i = 0; i < nCount; i++ )
{
v.putInt( (int) i );
Err1 = Errs1.getItem( v );
System.out.println( " Number " + Err1.getNumber() );
System.out.println( " Description = " + Err1.getDescription() );
if ( Err1 != null ) Err1 = null;
}
if ( Errs1 != null ) Errs1 = null;
}
public static void main( String args[])
{
msado15._Connection Conn1 = new msado15.Connection();
try
{
Conn1.Open( "", "", "", -1 );
}
// Catch Blocks -- These catch blocks are a subset of the
// Exception Handling demonstrated in LogEx.Java &
// AdoUtils.java in the Java Rosetta Stone Samples
catch (com.ms.com.ComFailException e)
{
// Test if Connection.Errors Collection is non-empty
AdoErrorEx( Conn1 );
// Crack open ComFailure class that wraps COM Exceptions
System.out.println( "ComFailException Class Raised:" );
System.out.println( "Exception Class = " + e.getClass() );
System.out.println( "Error Message = " + e.getMessage() );
System.out.println( "Failed HRESULT = " + e.getHResult() );
}
catch(Exception e)
{
// Test if Connection.Errors Collection is non-empty
AdoErrorEx( Conn1 );
// Crack open Java Exception class
System.out.println( "Exception Class Raised:" );
System.out.println( "Exception Class = " + e.getClass() );
System.out.println( "Error Message = " + e.getMessage() );
}
}
Scripting
VBScript
VBScript does not offer any language support for trapping errors. You can check the Err object and use On Error Resume Next, but it is not possible to set up any kind of error handler such as is used with Visual Basic for Applications (VBA) in the example. Therefore, you must thoroughly test your VBScript application during development because problems will not be caught during deployment.
Listing 29: Error handling with ADO in VBScript
<HTML>
<HEAD>
<SCRIPT LANGUAGE="VBScript">
<!--
Sub Procedure1()
on error resume next
badcommand
if len(err.description) > 1 then
msgbox "error is: " & err.description
end if
end sub
-->
</SCRIPT>
<TITLE> Page1 </TITLE>
</HEAD>
<BODY>
<FORM NAME="form1">
<INPUT LANGUAGE="VBScript" TYPE=button VALUE="button1" ONCLICK="call Procedure1()">
</FORM>
</BODY>
</HTML>
JScript
Error handling is not straightforward because JScript does not support try/catch syntax. In fact, there is no JScript-specific support for server-side error handling. You can use HTML tags to set up a client-side error handler, as shown in Listing 30.
Listing 30: Error handling with ADO in JScript
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JScript"
FOR="window"
EVENT="onerror(msg,url,lineno)">
<!--
alert("window.onerror\n\n" +
"Error: " + msg + "\n" +
"URL: " + url + "\n" +
"Line: " + lineno);
return true; // Tell the browser not to report the error itself
//-->
</SCRIPT>
<SCRIPT LANGUAGE="JScript"
FOR="button1"
EVENT="onclick()">
<!--
badcommand();
//-->
</SCRIPT>
</HEAD>
<BODY>
<INPUT TYPE=button VALUE="Click for error" NAME=button1>
</BODY>
</HTML>