ASP 101 - Active Server Pages 101 - Web03
The Place ASP Developers Go!

Please visit our partners


Windows Technology Windows Technology
15 Seconds
4GuysFromRolla.com
ASP 101
ASP Wire
VB Forums
VB Wire
WinDrivers.com
internet.commerce internet.commerce
Partners & Affiliates














ASP 101 is an
internet.com site
ASP 101 is an internet.com site
IT
Developer
Internet News
Small Business
Personal Technology

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

ASP 101 News Flash ASP 101 News Flash



 Top ASP 101 Stories Top ASP 101 Stories
VBScript Classes: Part 1 of N
Migrating to ASP.NET
Getting Scripts to Run on a Schedule

QUICK TIP:
Don't Forget that Final /
Show All Tips >>
ASP 101 RSS Feed ASP 101 Updates


Handling Errors in Net API Components
By Peter Persits
Copyright (c) 1999 Persits Software, Inc.






INTRODUCTION

The goal of this article is to demonstrate how to handle and report system errors in an active server component written in Visual C++/ATL which uses Win32 Net API.
To demonstrate the error-handling technique, we will create ChgPass,  a simple Active Server component that allows users to change their NT passwords over the Web.

This component will have one COM object, PasswordManager. This object will only have one method, ChangePassword, which will internally call WIN32 Net API ::NetUserChangePassword. The PasswordManager object will also have a read-only property, UserName, which will return the name of the currently logged-in user by calling the ::GetUserName API.


CREATING THE CHGPASS COMPONENT

Open Visual C++ version 5 or 6, go to the File menu, select New, and on the Projects tab, select ATL COM AppWizard. Under "Project Name", type the name of our project, ChgPass. Under "Location" enter a valid directory for the project, then Click OK.


 

On the next screen, accept all the default options, and click Finish.

 

The screen after that will show you the files that AppWizard is about to create. Simply click OK to create the files for our project.

Let's add the PasswordManager object to our project. Go to the Insert menu and select "New ATL Object". The ATL Object Wizard screen comes up that lists all possible object types that can be created. Under the Objects category, there are two types of objects that we can choose: Simple Object and ActiveX Server Component. Since our simple component will not contain any ASP-specific code, we will select Simple Object and click Next.

In the ATL Object Wizard Properties dialog, enter "PasswordManager" (the name of our object) in the "Short Name" box. The other boxes will be filled in automatically by Object Wizard. We will leave them intact and go to the Attributes tab.

Generally, it is a good idea for an active server component to support the ISupportErrorInfo interface, so we will check the appropriate box. It will have no effect if the component is used in an ASP environment, but it does make a lot of difference if you want your product to work equally well in VB. When you create an instance of your component with the New statement rather than CreateObject in a Visual Basic app, the component will not be able to correctly return rich error information unless it supports ISupportErrornInfo.

We are now ready to click OK and have Object Wizard create code for our object.

Since the ChgPass component uses Win32 Net API, we must include the appropriate headers in the StdAfx.h file:
 
 

StdAfx.h
extern CComModule _Module;
#include <atlcom.h>

#include <lmaccess.h>
#include <lmerr.h>
#include <lmapibuf.h>

We must also include netapi32.lib in the list of project libraries. Go to Project/Settings, pick "All Configurations" in the "Setting For" drop-down box, select the Link tab and include netapi32.lib in the list "Object/library modules".

Let's add the method ChangePassword and the property UserName to the component. In ClassView, right-click on an IPasswordManager node and select "Add Method". There are two such nodes on the ClassView tree, you can use either one. In the "Add Method to Interface" dialog, type ChangePassword under Method Name, and

[in]BSTR Domain, [in]BSTR Name, [in]BSTR OldPassword, [in]BSTR NewPassword

under Parameters, then click OK.

Right-click on the IPasswordManager node in the ClassView tree again, and this time select "Add Property". In the Add Property dialog, select BSTR under Property Type, enter UserName under Property Name, and uncheck the "Put Function" checkbox since we are adding a read-only property and do not need a modifier part of it. When you are finished, click OK.

It is time to write some code. Open the file PasswordManager.cpp and insert the implementation code for our property and method:
 
 

PasswordManager.cpp
STDMETHODIMP CPasswordManager::ChangePassword(BSTR Domain, BSTR Name, BSTR OldPassword, BSTR NewPassword)
{
 NET_API_STATUS res = ::NetUserChangePassword( Domain, Name,
       OldPassword, NewPassword );
 if( res != NERR_Success )
 {
  return VerbalErrorEx( res, 1 );
 }

 return S_OK;
}

STDMETHODIMP CPasswordManager::get_UserName(BSTR *pVal)
{
 char szName[256];
 DWORD dwSize = 255;

 if( !::GetUserName( szName, &dwSize ) )
 {
  return VerbalError( 2 );
 }

 CComBSTR bstrName = szName;
 * pVal = bstrName.Copy();

 return S_OK;
}

Some Win32 API functions return a 0 if completed successfully and a non-zero error code otherwise. Other functions return TRUE to report success and FALSE otherwise. In the latter case the error code can be obtained by calling ::GetLastError(). ::NetUserChangePassword belongs to the first group, and ::GetUserName to the second. To accomodate both types of functions, we wrote two error-handling routines, VerbalErrorEx and VerbalError.

Add the following declarations to PasswordManager.h:
 
 

PasswordManager.h:
#include "resource.h"       // main symbols

#define DISP_ERROR(n) MAKE_HRESULT(1, FACILITY_WINDOWS | FACILITY_DISPATCH, (n) )
 

...

// ISupportsErrorInfo
 STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

public:
 HRESULT VerbalErrorEx( DWORD dwError, int nErrorIndex );
 HRESULT VerbalError( int nErrorIndex );
 

And in PasswordManager.cpp:
 

PasswordManager.cpp
HRESULT CPasswordManager::VerbalError( int nErrorIndex )
{
 return VerbalErrorEx( ::GetLastError(), nErrorIndex );
}

HRESULT CPasswordManager::VerbalErrorEx( DWORD dwLastError, int nErrorIndex )
{
 HMODULE hModule = NULL;

 DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM;

 if(dwLastError >= NERR_BASE && dwLastError <= MAX_NERR) 
 {
  hModule = LoadLibraryEx( TEXT("netmsg.dll"), NULL, LOAD_LIBRARY_AS_DATAFILE );

  if(hModule != NULL)
   dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
 }

 LPVOID lpMsgBuf;
 FormatMessage(  dwFormatFlags, hModule, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL );
 CComBSTR bstrError = (char*)lpMsgBuf;
 LocalFree( lpMsgBuf );

 if(hModule != NULL)
  FreeLibrary(hModule);

 return Error( bstrError, GetObjectCLSID(), DISP_ERROR( nErrorIndex ) );
}

We use the function ::FormatMessage to convert a numeric error to the corresponding verbal message. The second parameter of this function is normally set to NULL, but in case of Net API errors, this parameter must be set to the handle of the library netmsg.dll where the verbal messages for the Net API functions reside.

The nErrorIndex parameter is simply an application-specific error code which you can set to anything you want. In our example we pass codes 1 and 2 for our method and property, respectively.

The DISP_ERROR macro converts the application-specific error number to an OLE Automation-compatible error code.

Now we can go ahead and compile the component.

You can use the the following ASP file to test the component. This file must be placed in a virtual directory with Basic Authentication enabled. Note that the ChangePassword method will not accept a user name in the form DOMAIN\USERNAME, so you may need to add some code to strip UserName of the domain prefix before passing it to ChangePassword.
 
 

ChangePassword.asp
<HTML>
<HEAD>
<TITLE>ChgPass Demo</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<OBJECT RUNAT=SERVER PROGID="ChgPass.PasswordManager" ID=CP>
</OBJECT>

<% 
 UserName = Request.ServerVariables("LOGON_USER")
 If UserName = "" Then
  Response.Write "<h2>You must disable anonymous access for this sample to function.</h2>"
  Response.End
 End If

 If Request("ChangePassword") <> "" Then
  If Request("Pass1") <> Request("Pass2") Then
   Response.Write("The new password was not correctly confirmed.<HR>")
  Else
   CP.ChangePassword "<Domain Name>", UserName, Request("OldPass"), Request("Pass1")
   Response.Write("Password has been changed.<HR>")
  End If
 End If
%>
 

<H1>User: <% = UserName %></h1>

<TABLE>
 <FORM Action="ChangePassword.asp" METHOD=POST>
  <TD>Old Password:</TD>  <TD><INPUT SIZE=40 TYPE=PASSWORD NAME="OldPass"><TD><TR>
  <TD>New Password:</TD>  <TD><INPUT SIZE=40 TYPE=PASSWORD NAME="Pass1"><TD><TR>
  <TD>Confirm Password:</TD> <TD><INPUT SIZE=40 TYPE=PASSWORD NAME="Pass2"><TD><TR>
  <INPUT NAME="ChangePassword" TYPE=SUBMIT VALUE="Change Password">
 </FORM>
</TABLE>

<P>
UserName returns <B><% = CP.UserName %></B>

</BODY>
</HTML>


Home |  News |  Samples |  Articles |  Lessons |  Resources |  Forum |  Links |  Search |  Feedback


The Network for Technology Professionals

Search:

About Internet.com

Legal Notices, Licensing, Permissions, Privacy Policy.
Advertise | Newsletters | E-mail Offers