How to Work with DAPI_EVENT Structure
Perhaps you have noticed in the samples described previously that a few
DAPI functions such as DAPIStart and DAPIRead return pointer to a DAPI_EVENT
structure. If the function is successful, it returns NIL pointer, otherwise a
DAPI_EVENT structure is allocated and its pointer is returned to the caller.
This structure contains error information. This topic describes how to deal with
it.
First of all, a few words of caution. If the structure is allocated you must
eventually free it with DAPIFreeMemory call - that's what Microsoft recommends.
My two illustrative samples in previous subsections don't do that, but
production code perhaps should. I use the opportunity to emphasize here again,
that the samples in this work are "illustrative". They show how you
could accomplish certain tasks, but they are obviously not rock-solid.
Here is the definition of DAPI_EVENT structure:
typedef struct _DAPI_EVENT
{
DWORD dwDAPIError; //
Message ID for event log
LPSTR rgpszSubst[DAPI_MAX_SUBST];
// Event message substitution array
UINT unSubst; // Number
of substitution strings
LPSTR pszAttribute; //
Name of attribute specifically affected
LPSTR pszHoldLine; //
Pointer to buffer containing copy of current import line
HINSTANCE hinstDAPI; //
Instance of DAPI DLL
struct _DAPI_EVENT *pNextEvent; //
Pointer to next event
} DAPI_EVENT, * PDAPI_EVENT;
And here is DELPHI
definition of DAPI_EVENT
structure:
type
PDAPI_EVENTA = ^DAPI_EVENTA;
_DAPI_EVENTA = record
dwDAPIError : DWORD;
rgpszSubst : Array[0..DAPI_MAX_SUBST-1] of PChar;
unSubst : UInt;
pszAttribute : PChar;
pszHoldLine : PChar;
hinstDAPI : HINST;
pNextEvent : PDAPI_EVENTA;
end;
DAPI_EVENTA = _DAPI_EVENTA;
DAPI_EVENT = DAPI_EVENTA;
PDAPI_EVENT = PDAPI_EVENTA;
The most important component of this structure is its first element - message
identifier. This number identifies an error message string. The last element of
the structure is also very useful. It hosts a pointer to the next related event.
A function returning DAPI_EVENT may in fact report "many" errors.
Suppose you want to modify a directory object with the DAPIWrite function and
supply invalid arguments. For example, you may supply "read-only"
attributes, and omit a required one. In such case the DAPI_EVENT will
individually report each error (quite a nice feature!) in a list of related
DAPI_EVENTs. You may easily traverse this list and view it during debugging in
the Watch window.
Our major concern here is how to deal with errors. How can we obtain text
description of an error? There are a few ways of doing this.
Examining DAPIMSG.H file
DAPIMSG.pas in DELPHI
The simplest thing to do is to open the DAPIMSG.H file, find message ID there,
and look at text description of an error. For example, for dwDAPIError being
equal to 0xC0000081 you should be able to find the following fragment in this
file:
// MessageId: DAPI_E_BAD_HANDLE
//
// MessageText:
//
// A bad DAPI handle supplied to call.
//
#define DAPI_E_BAD_HANDLE
0xC0000081L
In DAPIMSG.pas
const DAPI_E_BAD_HANDLE = $C0000081;
Take a look at text description of the error preceding const statement.
Obviously, a bad handle was used with the call. This is exactly what happened
with me when I mistakenly supplied address of DAPI handle instead of real handle
to the DAPIRead function.
DAPIMSG.H file may be found in the Include directory of your Platform SDK.
DAPIMSG.pas may be found in the IMI
EDK for DELPHI5
Using the FormatMessage Function
Second way of dealing with DAPI_EVENT error codes is to use the FormatMessage
Win32 function. In fact, it is a very useful general-purpose function. For
example, here is the sample showing how you can display Win32 error codes,
obtained via the GetLastError():
procedure Some;
var
lpMsgBuf:PCHAR;
begin
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER or
FORMAT_MESSAGE_FROM_SYSTEM or
FORMAT_MESSAGE_IGNORE_INSERTS,
nil,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
@lpMsgBuf,
0,
nil
);
// Display the string.
MessageBox( 0, lpMsgBuf, "Error", MB_OK or MB_ICONINFORMATION );
// Free the buffer.
LocalFree( Cardinal(lpMsgBuf));
end;
The above sample may be found in my collection
of sample code.
In our particular case of DAPI errors, we need to use slightly different
parameters. First of all, we need to change FORMAT_MESSAGE_FROM_SYSTEM flag to
FORMAT_MESSAGE_FROM_HMODULE, pass DAPI.DLL handle in second parameter, and error
ID in the third. The following sample demonstrates how you can use it. Notice
that I call DAPIRead without first calling DAPIStart, this causes it to fail
with invalid handle error. Notice the use of DAPIFreeMemory for DAPI_EVENT
structure as well. The sample below is located in FormatMessageDAPI
subdirectory.
procedure TEUtils.RaiseDAPIError(DapiEvent: PDAPI_EVENT);stdcall;
var
msg:PCHAR;
strmsg:string;
NextEv:PDAPI_EVENT;
dwDAPIError:ULONG;
begin
NextEv:=DapiEvent;
While Assigned(NextEv) do
begin
FormatMessage (FORMAT_MESSAGE_FROM_HMODULE
or FORMAT_MESSAGE_ALLOCATE_BUFFER
or FORMAT_MESSAGE_ARGUMENT_ARRAY,
pointer (NextEv.hinstDAPI),
NextEv.dwDAPIError,
MAKELANGID(LANG_NEUTRAL,
SUBLANG_DEFAULT),
@msg,
0,
@NextEv.rgpszSubst);
msg[Lstrlen(msg)-2] := #0;
ShowMessage(msg);
LocalFree(Cardinal(msg));
NextEv:=NextEv.pNextEvent;
end;
end;
For many application working co-operatively with MS Exchange server it would
be more appropriate to log messages to Windows NT event log, rather than
displaying them on the desktop. Once you have collected the message with the
FormatMessage function as described above, you can log it into Event log by
either using standard Win32 event logging techniques or a collection of
functions in Exchange Development Kit (such as EventLogMsg).
The sample may be found in my collection
of sample code.
|