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