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.