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