Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

Controlling the Event Log - Part 1
Roberto C. Ianni, May 1, 2004
The "EventLog" or "Event viewer" is something we use every day, to obtain information about what happened to our computer at a certain moment. For example, when an application generates an error, most of us instinctively go to the EventLog to see what information it left for us.
Summary
The "EventLog" or "Event viewer" is something we use every day, to obtain information about what happened to our computer at a certain moment. For example, when an application generates an error, most of us instinctively go to the EventLog to see what information it left for us.
Description
The "EventLog" or "Event viewer" is something we use every day, to obtain information about what happened to our computer at a certain moment. For example, when an application generates an error, most of us instinctively go to the EventLog to see what information it left for us.

Everything we will explain about the EventLog will work on Windows NT/2000/XP terminals, from Windows NT 3.1 and later, but not on Windows 95/98/Me terminals, since they don't have this feature.

A while ago, our colleague Cleber Ferrari wrote an article about the EventLog in the Universal Thread Magazine, but from a somewhat different point of view. In his article, he used components of the Windows Script Host, while we will now see what these COM components really do to register an event in the "Event viewer".

Objetives

We will define, as our main objective, to obtain a clear understanding of how the operating system works for registering, consulting or deleting an event from its event registry.

For these purposes, we will only use the API function provided by the operating system, and Visual FoxPro, without any other third-party services.

In order to do this, we will explain the following points:

  1. How we can generate a new event in the EventLog

  2. How we can query the events in the EventLog

  3. How we can delete events from the EventLog

  4. How we can do a backup of the EventLog

Generating a new event

To generate a new event in the Eventlog, what we have to consider is how we would do to generate any kind of event. As a first step, we should write down such an event. As a second step, we should report information about the event we registered. For the operating system, it is exactly the same, but we have some more complexity in Visual FoxPro, since these function, which the operating system provides us, require pointers, which is something common in Visual C++, but not for us Visual FoxPro developers. But don't despair, this isn't so bad.

API Functions

We will now list the API functions that we use to generate a new event in the EventLog.

API FunctionDescription
RegisterEventSource Return a Handle registered to the EventLog.
DeregisterEventSource Un-register the Handle passed as a parameter.
ReportEvent Generate a new entry at the end of the EventLog, in the specified container.
GetLastError Obtain the last error set by the system.
RtlMoveMemory Copy a block of memory from one sector to another sector.
GlobalAlloc Allocate a specified number of bytes in the memory "heap".
GlobalFree Free the memory allocated on the "heap".

Let's see the function declarations in Visual FoxPro:

DECLARE LONG RegisterEventSource IN "advapi32.dll" STRING lpUNCServerName, STRING lpSourceName

DECLARE LONG DeregisterEventSource IN "advapi32.dll" LONG hEventLog
	
DECLARE INTEGER ReportEvent IN "advapi32.dll" ;
   LONG hEventLog, INTEGER wType, INTEGER wCategory, ;
   INTEGER dwEventID, INTEGER lpUserSid, INTEGER wNumStrings, ;
   INTEGER dwDataSize, LONG lpStrings, LONG lpRawData

DECLARE LONG GetLastError IN "kernel32"

DECLARE RtlMoveMemory IN "Win32API" AS CopyMemory LONG hpvDest, STRING @hpvSource, LONG cbCopy

DECLARE LONG GlobalAlloc IN "kernel32" LONG wFlags, LONG dwBytes

DECLARE LONG GlobalFree IN "kernel32" LONG HMEM
The idea here is to explain some of the variants that these API functions give us, but not to explain them in great detail, since they are already quite well explained in the MSDN which Microsoft offers us.

Figure 1: Logical Diagram

Logical diagram

We will here make a graphic of the logic we mentioned previously, to generate a new event in the EventLog.

In this diagram we see that there are four API functions, "GlobalAlloc"; "CopyMemory"; "GlobalFree" and "DeregisterEventSource", which we didn't name in the brief analysis we did previously, but if we watch carefully, the functions "GlobalAlloc"; "CopyMemory" and "GlobalFree" are functions for allocating, copying and freeing memory, and these are the functions that, in the previous analysis, we resumed with the word "pointer". On the other hand, there is the function "DeregisterEventSource" which we have to use to free the event.

Some variants

  1. The function RegisterEventSource is the first function which we will analyze as to possible variants. First, we will see its real declaration for VC++, and how we declare it in Visual FoxPro, and we will see what we can do with each parameter which the function needs.

    Visual C++:

    HANDLE RegisterEventSource(
       LPCTSTR lpUNCServerName,  // server name
       LPCTSTR lpSourceName      // source name
       );
    
    Visual FoxPro:
    DECLARE LONG RegisterEventSource IN "advapi32.dll" ;
       STRING lpUNCServerName, STRING lpSourceName
    
    lpUNCServerName

    This parameter is the one that informs the function the name of the server to which you want to connect, in the UNC (Universal Naming Convention) format. If this parameter is null, the registration will be done on the local machine.

    lpSourceName

    This parameter is the name of the origin which the Handle returned by the function references. This parameter is commonly used with the application name, since, when we want to see the events in the "EventLog", we can find it easily, sorting or filtering on the origin.

  2. The other function of which we will analyze possible variations is "ReportEvent". Here, too, we will show the real declaration in VC++, and its declaration in Visual FoxPro.

    Visual C++:

    BOOL ReportEvent(
       HANDLE hEventLog,    // handle to event log
       WORD wType,          // event type
       WORD wCategory,      // event category
       DWORD dwEventID,     // event identifier
       PSID lpUserSid,      // user security identifier
       WORD wNumStrings,    // number of strings to merge
       DWORD dwDataSize,    // size of binary data
       LPCTSTR *lpStrings,  // array of strings to merge
       LPVOID lpRawData     // binary data buffer
    );
    
    Visual FoxPro:
    DECLARE INTEGER ReportEvent IN "advapi32.dll" ;
       INTEGER hEventLog, INTEGER wType, INTEGER wCategory, ;
       INTEGER dwEventID, INTEGER lpUserSid, INTEGER wNumStrings, ;
       INTEGER dwDataSize, INTEGER lpStrings, INTEGER lpRawData
    
    hEventLog

    It is this parameter which receives the Handle returned by function RegisterEventSource.

    wType

    This parameter specifies the type of event that will be generated. It can be one of the following:

    Value Description
    EVENTLOG_ERROR_TYPE Frequently used to indicate that a significant error occured, and to let the user know the reason.
    EVENTLOG_WARNING_TYPE Frequently used to leave information about an error which is not immediately significant, but to leave a precedent for future errors.
    EVENTLOG_INFORMATION_TYPE Frequently used to indicate that an operation finished successfully. For example, when (r) SQL Server (tm) started correctly.
    EVENTLOG_AUDIT_SUCCESS Frequently used to indicate security events, for example, when a user logged in correctly.
    EVENTLOG_AUDIT_FAILURE Frequently used to indicate security events, for example, when a user could NOT login correctly.

    wCategory

    This parameter specifies the message category. It is used to organize events, and to be able to sort or filter them in the EventViewer in its column "Category".

    dwEventID

    This parameter indicates the event's ID, associated to the source of the event, also used to sort or filter them in the Event Viewer in its column "Event".

    lpUserSid

    This parameter identifies a pointer with a user's security identification. This can also be null, in case security is not required. We will do the latter in our examples.

    wNumStrings

    This parameter indicates the number of strings which are passed in the array passed to parameter lpStrings. If this is zero, it means that no string is used for the event. dwDataSize

    This parameter indicates the size, in bytes, of the array which contains the strings to be saved. If it is zero, it means that no strings are used for the event.

    lpStrings

    This parameter is a pointer to the buffer that contains the string array with the information to be saved with the event. This parameter can be null, in case there is no string for the event. The limit that this array can have is 32K characters.

    lpRawData

    This parameter is a pointer to the information in binary format.

  3. And finally, we will analyze some variations of the function "GlobalAlloc", specifying also its real declaration in VC++, and its declaration in Visual FoxPro.

    Visual C++:

    HGLOBAL GlobalAlloc(
      UINT uFlags,     // allocation attributes
      SIZE_T dwBytes   // number of bytes to allocate
    	);
    
    Visual FoxPro:
    DECLARE LONG GlobalAlloc IN "kernel32" LONG wFlags, LONG dwBytes
    
    uFlags

    This parameter specifies the mode in which the function allocates the requested memory. This can be one of the values from the list below, or a combination of them, except for some we will mention.

    Value Meaning
    GHND Combines GMEM_MOVEABLE and GMEM_ZEROINIT.
    GMEM_FIXED Allocates unmovable memory, returning a pointer to the memory.
    GMEM_MOVEABLE Allocates movable memory.
    The memory blocks are never moved in physical memory, but they can move within the heap.
    The heap is a special memory area used to store important resources.
    This option can not be combined with GMEM_FIXED.
    GMEM_ZEROINIT Initializes the requested memory with zeroes.
    GPTR Combines GMEM_FIXED and GMEM_ZEROINIT.

    dwBytes

    This parameter specifies the number of bytes that should be reserved.

Developing a class

Up until now, we have already talked in detail about what we can and should do to save a new log to the EventLog. Now, we can start to program.

The first thing we'll see is how to save information to the log, if this information is in binary mode, and not in text mode.

DEFINE CLASS CEventLog AS CUSTOM

   m_Machine = NULL
   
   PROCEDURE LogBinaryEvent( sString, iLogType, iCategoty, iEventID, sAplication )

      LOCAL hEventLog
      LOCAL hMsgs
      LOCAL cbStringSize
      LOCAL nError
      nError = 0

      *|-- Registers the event.-
      hEventLog    = RegisterEventSource( This.m_Machine, sAplication )
      IF( hEventLog == NULL )
         RETURN GetLastError()
      ENDIF

      *|-- Get the memory.-   
      cbStringSize = LEN(sString) + 1
      hMsgs        = GlobalAlloc( GMEM_ZEROINIT, cbStringSize )

      IF( hMsgs > 0 )
         *|-- Copy the message to the pointer obtained.-
         CopyMemory( hMsgs, sString, cbStringSize )


         IF( ReportEvent(hEventLog, ;
               iLogType, iCategoty, ;
               iEventID, 0x0, ;
               0x0, cbStringSize, ;
               hMsgs, hMsgs ) = 0 )
            nError = GetLastError()
         ENDIF
      ELSE
         nError = GetLastError()
      ENDIF

      *|-- Release the given memory.-
      IF( GlobalFree( hMsgs ) != 0 AND nError == 0 )
         nError = GetLastError()
      ENDIF

      *|-- Deregisters event.-
      IF( DeregisterEventSource( hEventLog ) == 0 AND nError == 0 )
         nError = GetLastError()
      ENDIF
      
      RETURN nError
   ENDPROC

We can see that this code reflects the logical diagram which we saw previously, but we will analyze it in more detail.

As a first step, what we do is register the event, passing, as parameters, the name of the terminal on which it will act, and the name of the application that generates the log. Then, we request sufficient memory to be able to store the information which we wish to save, passing as parameters to this function GMEM_ZEROINIT and the size we need; thus, we have the entire memory initialized to zero, but let's stop here for now.

First, why it is necessary to request memory, to then copy something which we already have in memory? It doesn't sound logical, right? Well, the answer is easy, and difficult to accept, at least for me. FOX doesn't manage pointers, therefore, we need to request memory from a function, and to have it give us the address of the memory which this pointer uses physically, so that we can next copy what we have in memory to another memory address which is thus marked as our pointer returned by the function. But once again, we encounter the word "pointer"; I will try to explain what, exactly, is a pointer.

A pointer is a variable that contains the address of another variable. A computer has a sort of array of memory cells, consecutively numbered. Thus, a byte can be a char; two bytes can be an integer, and four consecutive bytes can be a long integer. Pointers are used a lot in C/C++, and use 4 bytes in memory as an "unsigned long", mainly because:

  • They allow the functions to modify the contents of the parameters they receive.
  • Their use noticeably improves the efficiency of certain routines.
  • They are used to implement data structures such as linked lists and trees.

Example:

Figure 2: A variable pointing to another variable

A second important point is to understand the reason of the parameter GMEM_ZEROINIT. Why do we need to initialize the memory to zero? This is because the memory which the operating system gave us could have been used by a previous process, which used it and later freed it, but did it leave it clean? In most cases, the memory which they give us has garbage; at least, for us it is garbage, because we can't interpret what is stored in this memory positions; only the process that wrote to that memory knows how to do that.

Continuing with our method LogBinaryEvent, after it asks the operating system for memory, it copies the memory contained in the string which we passed as a parameter to the method, to the memory address that we allocate. Once this is done, we only need to report the event with all the properties that we defined as a parameter, for example, the log type, the category, and the event number.

Once we reported the event, the only thing left to do is to free the memory requested, and de-register the event. Freeing memory is very important, since otherwise, the memory we requested will continue being reserved, and can't be assigned to any other process that asks it, since we are using it.

Let's see an example of how the method is executed, and how it would affect the event viewer.

SET PROCEDURE TO EventLog.prg
oo = CREATEOBJECT("CEventLog")
?oo.LogBinaryEvent( 
   "Error---binary dump",;       && Information to save
   EVENTLOG_ERROR_TYPE,;         && Error type
   0,;                           && Error category
   0,;                           && Number of event
   "RobertApp" )                 && Name of the application
RELEASE oo

Figure 3: Registered event

Figure 4: Detail of the event registered

And if we open the event (to see its contents), we will see something like this:

Here we can see that the error description doesn't contain the information we saved; this is further down, where it says "Data:", since this part is reserved for writing binary data of the event.

With this method LogBinaryEvent, we already have the possibility of saving information into the event's binary data, but how should we save information in the the event's description? To do this, we have to understand that the way of doing this is similar to saving binary data, the difference being that we have to change the parameters we pass to the API function ReportEvent. The required changes are relatively simple with respect to parameters; we only need to change the parameters wNumStrings and lpStrings; that would be enough. The problem, however, is to generate the parameter lpStrings.

Let's see why.

Let's remember that the parameter lpStrings for the API function ReportEvent is a pointer to an array, and this is the problem for us. We already saw what is a pointer, but what is a pointer to an array? It is basically the same as a pointer, but the variable pointed to, by the pointer type variable, is different; it is an array. Let's try to explain how an array is distributed in physical memory.

A matrix is similar to a vector, and in some cases they are closely related, with the difference that a vector is one-dimensional, and a matrix is two-dimensional. Thus, for our example, it is simpler to start explaining what a vector is, and to later continue with a matrix, and see its similarities and relationships.

Arrays are structures that consist of a number of homogenuous elements, grouped under a single name; each element is distinguished from others by its position.

A vector has certain characteristics or properties:

  • It has a single variable name that represents all the elements; the elements are distinguished by their index or subscript.
  • All elements must be of the same type.
  • All elements of the vector are stored in contiguous memory positions.

Let's analyze the last two of these characteristics. The first says that all elements of a vector have to be of the same type. Although this is true, many of you might be asking: "But in VFP, I can declare a vector, and assign any type I want to each element; then, why do you say this?" The answer is simple: although we don't notice it, all the elements are of the same data type; the data type is VARIANT, therefore we have this possibility.

Now, let's see how a vector is defined in VFP, and let's contrast this with C++; thus we can notice the difference, what we can do with VFP vectors, thanks to the VARIANT data type, and what we can do in C++.

Visual FoxPro:

DIMENSION a[2]
a[0] = 0
a[1] = "aa"
?a[1]
?a[2]
Visual C++:
int a[2];
a[0] = 0;
a[1] = 1;
printf( "%i %i", a[0], [1] );
The second characteristic of vectors tells us that all elements are stored in contiguous positions, this allows us to access the elements either directly or indirectly. Let's see this.

Figure 5: Pointer to a vector - The variable "pa" points to vector "a"

In this graphic we see three important things we have been talking about. The first one is how a pointer variable points to a vector; the second, that all elements of a vector are stored in contiguous positions in physical memory; and third, that the elements of a vector can be accessed either directly or indirectly. The first two points, I believe, can be understood easily with the graphic, but the third one I will explain in a little more detail.

Directly accessing elements of a vector is the most natural thing for us VFP developers; this would be accessing elements by their index or subscript, for instance:

a[0] = 0
a[1] = "aa"
But the indirect way of accessing is more natural for C++ developers, since pinters and vectors are closely related. Any operation that can be obtained through indexing a vector can also be done with pointers. The version with pointer is usually faster, but more difficult to understand.
int *pa;    // Variable of type pointer.
int a[2];   // Vector with two positions.

// Direct access
a[0] = 0;
a[1] = 1;

// Indirect access
pa = &a[0]; // The address of the first element of vector "a"
            // is assigned to "pa"
printf( "%i %i", *( pa ), *(  pa + 1 ) );

The indirect access is called "pointer arithmethic", and for our example, of saving into the EventLog, we will use it in a very simple way to create the array that needs the parameter lpStrings of the API function ReportEvent, to be able to save the information into the event detail.

With this, we should have the concept of what is a vector quite clear; how it works and how it is stored in physical memory. But now, what is an array? Surely many of you have a good idea of whan an array is, but let's remember that up until now we saw what a vector of numbers is, and we need an array of strings.

The differences between an array of characters and a numeric array are big, but similar, since a vector of numbers only stores one value per index, whereas a character array stores several values, one for each character, for each index.

Once we understand this, we have to create an array of characters; for this purpose, we have to know how to have a vector of only one position contain (n) values, but actually we won't be inventing anything. If we think about it, C++ has been doing this for a long time, we only have to see how it is done, and let me anticipate that it isn't anything complicated, and if you have understood the concept of a vector well, it shouldn't be difficult to understand what is an array of characters.

If we remember the graphic of a pointer to a vector, we will realice that if, instead of saving numbers, we save characters in the vector, we already have what we require, how to save, in one single value, (n) values, having thus a pointer (a single value) that points to a vector that contains (n) values, as shown in the graphic of the pointer to a vector.

We have thus almost solved our problem, but we still need to know how to make our array contain (n) positions with (n) values, but surely you have already realized that this can be solved easily with two vectors as a minimum, one containing the addresses (pointers), and another vector, or vectors, containing the values.

Let's see, then, how in C++ we can create an array in memory.

Figure 6: The array "a" points to different parts of vector "v"

Thus, in position zero, vector "a" points to a memory address where there is a vector "v" of characters, and in its position one, vector "a" points to another memory address of the same vector"v" where another set of characters is stored. Surely you will ask yourself, how the position zero of vector "a" knows when its character string ends, and how come it doesn't use characters from position one of vector "a". This is solved using a control character that identifies the end of the string; this is character zero, that many use in C++ as "\0".

This is how C++ does this job, but I must add that this isn't the only one, since the first vector "a" only contains the memory addresses where the character string, terminated with "\0", is saved, therefore, it isn't necessary to have a single vector "v" contain the entire information from all positions of the first vector "a", rather, there might be (n) vectors "v" that contain only their information, vector "a" being related to them through their memory address. This would be as follows:

Figure 7: Vector "a" points to different
vectors, which contain the characters

Perhaps for many the second method is easier to understand than the first, but I believe it isn't the best, since we have to request memory once for each of the pointers; thus, respecting the decision of Dennis Ritchie (creator of the C language), I will use the first option.

As you have seen after all this theory, a pointer isn't anything taboo, and an array described in memory isn't anything magical either. What happens here is that VFP, and most high-level languages, hide these details from us, to make our development easier, but here we want to use an API function that requires an array in the C++ style, for the simple reason that most of the operating system's APIs are developed in C++.

But first, perhaps you have wondered that the parameter lpStrings is an array, wherease we actually only want to save an error description; so why should it be an array? This parameter is an array, because this gives us the possibility of saving (n) descriptions of the event we generate, as long as the total size of the array doesn't surpass 32K characters.

After understanding all this, we will now undertake to develop a method that can save a string array into the event. Here it is:

PROCEDURE LogArrayEvent( sArrayMsg, iLogType, iCategoty, iEventID, sAplication )

   LOCAL nMsgCount
   LOCAL nMemAlloc
   LOCAL pArray
   LOCAL pMsgsAux
   LOCAL hEventLog
   LOCAL nError
   LOCAL cAddrLst

   nError    = 0
   nMemAlloc = 0
   nMsgCount = ALEN( sArrayMsg, 1 )
   
   *|-- Registers the event.-
   hEventLog = RegisterEventSource( This.m_Machine, sAplication )
   IF( hEventLog == NULL )
      RETURN GetLastError()
   ENDIF

   *|-- Calculates the amount of memory needed for the vector.-
   FOR i = 1 TO nMsgCount
      nMemAlloc = nMemAlloc + LEN( sArrayMsg[i] ) + 1
   ENDFOR

   *|-- Gets the memory for the array.
   *|-- Multiplies the array quantity by 4
   *|-- as every pointer needs 4 bytes of memory
   *|-- This is the size of a C++ unsingned long .-
   pArray = GlobalAlloc( GMEM_ZEROINIT, nMsgCount * 4 )

   *|-- Gets the memory for the messages .-
   pMsgs  = GlobalAlloc( GMEM_ZEROINIT, nMemAlloc )
   
   *|-- Initializes the variable containing the copy of the memory pointed
   *|-- to the messages vector.-
   cAddrLst = ""
   IF( pArray > 0 AND pMsgs > 0 )
      pMsgsAux = pMsgs

      *|-- Copies the vector to the returned memory address
      *|-- Access the vector trough indirect access.-
      FOR i = 1 TO nMsgCount
         CopyMemory( pMsgsAux, sArrayMsg[i] + CHR(0), LEN( sArrayMsg[i] ) + 1 )
         
         *|-- Stores the vector address.-
         cAddrLst = cAddrLst + This.NumToDWord( pMsgsAux )
         
         *|-- Moves to the next element.-
         pMsgsAux = pMsgsAux + LEN( sArrayMsg[i] ) + 1
      ENDFOR

      *|-- Fills the vector with the pointers to the vector containing the messages.-
      CopyMemory( pArray, cAddrLst , LEN(cAddrLst) )

      IF( ReportEvent( hEventLog, ;
            iLogType, iCategoty, ;   
            iEventID, 0x0, ;
            nMsgCount, 0x0, ;
            pArray, 0x0 ) = 0 )
         nError = GetLastError()
      ENDIF

   ELSE
      nError = GetLastError()
   ENDIF

   *|-- Releases the given memory.-
   IF( GlobalFree( pMsgs ) != 0 AND nError == 0 )
      nError = GetLastError()   ENDIF
   IF( GlobalFree( pArray ) != 0 AND nError == 0 )
      nError = GetLastError()
   ENDIF

   *|-- Deregisters the event.-
   IF( DeregisterEventSource( hEventLog ) == 0 AND nError == 0 )
      nError = GetLastError()
   ENDIF

   RETURN nError
ENDPROC

We can continue by explaining that this code reflects the logical diagram that we saw when we started to analyze how to save into the EventLog. Also, we can see that this is very similar to the method LogBinaryEvent. This is because the structure of how to save the event is exactly the same; the only difference is that the method LogArrayEvent generates an array dynamically in memory in the best C++ style, to pass it to the API function ReportEvent.

Let's then analyze the variations to see how to code, in VFP, the creation of an array according to all the theory that we have been seeing.

As a first difference, we see that we have calculated the size of each array position that is passed to the method, and accumulated it into a variable "nMemAlloc", but you see that in this line, the size of the string is increased by one. If we remember the theory, it says that to detect the size of the string we have to place a "\0" that identifies the end of the string; it is for this reason that we add one to the real size of the string. This is the amount of memory that we must then request from the operating system, to create our message vector.

The second difference that we see is in requesting memory from the operating system. Here, we request memory for two vectors, the first for managing the pointers (for us, the array), and the second is the one that will contain the information of each array index.

In order to do this, we manage two memory requests, one to request the total of characters for the array index that is passed to us, one by one, for each index, for the end of the string. On the other hand, we have the request for memory to generate the vector that will contain the pointers to each of the strings, and here we should remember that a pointer uses 4 bytes in memory. Therefore, a vector of two consecutive positions would use 8 bytes in memory, one of three positions, 12 bytes, etc.

The third difference that we see is in the generation of the vector that contains the messages. For this purpose, we loop through the array passed to the method, and copy it into memory, one element at a time, moving through the vector indirectly, thanks to "pointer arithmetic".

We also note that we have called the method Long2ToString. What this one does is convert the number passed by parameter into a string, and this string is the representation of the number stoed in the computer's physical memory. We have already used this method previously, for instance in the article "Sending emails from Visual FoxPro without additional components".

All these transformations done to the pointers are saved into an auxiliary variable, and then copied to the vector, which will thus be an array that contains the pointer addresses of each of the character strings, thus forming an array.

Once we have done this, the only thing left todo is to report the event, and that's it.

Let's see an example of how to execute the method and how it would look in the event viewer.

SET PROCEDURE TO EventLog.prg
oo = CREATEOBJECT("CEventLog")
DIMENSION a[2]
a[1] = "The first message - Roberto"
a[2] = "The second message - Ianni!!!"
?oo.LogArrayEvent( 
   @a,;                    && Information to save
   EVENTLOG_ERROR_TYPE,;   && Error type
   0,;                     && Error category
   0,;                     && Event number
   "RobertApp" )           && Application name
RELEASE oo
In the event viewer, it will look more or less like this:

Figure 8: Registered event

Figure 9: Detail of the registered event

And if we open the event to see its contents, it will look something like this:

We can see now that the message was saved in the part of the event description, and not in the binary part as is done by the method LogBinaryEvent; this latter part remains empty.

We should note that there is a "," (comma) between each message that we pass to the array; this identifies the items in the array we pass.

In case we want to pass one single message, all we have to do is pass a single item to the array, and that's it, but to finish this class, let's create a new method that does this. The method would look like this:

PROCEDURE LogStringEvent( sStringMsg, iLogType, iCategoty, iEventID, sAplication )

   DIMENSION arrMsg[1]
   arrMsg[1] = sStringMsg
   RETURN This.LogArrayEvent( @arrMsg, iLogType, iCategoty, iEventID, sAplication )
ENDPROC
We would call it with something like this, and the result would be similar to the previous one, but with a single message.
SET PROCEDURE TO EventLog.prg
oo = CREATEOBJECT("CEventLog")
?oo.LogStringEvent( 
   "Hy, I'm a string",;       // Information to save
   EVENTLOG_ERROR_TYPE,;      // Error type
   0,;                        // Error category
   0,;                        // Event number
   "RobertApp";               // Application name
   )
RELEASE oo
To complete the job of saving information into the event, we still need the possibility of saving information in the description and binary parts of the event; with this, we would be finished with our first item, which is to save information about the event.

This new method that we develop is a mixture of the two we already developed. For this case, we would be passing the complete information of the parameters wNumStrings; dwDataSize; lpStrings and lpRawData of the API function API ReportEvent; with this, we already have our new method.

The new method will be called LogEventEx, and I understand that it is not worthwhile to analyze it, since it is a mixture of the methods LogArrayEvent and LogBinaryEvent, which we already know well. What we can do, however, is to see the result of choosing this new method, to see the result of executing it.

SET PROCEDURE TO EventLog.prg
oo = CREATEOBJECT("CEventLog")

DIMENSION a[2]
a[1] = "The first message - Roberto"
a[2] = "The second message - Ianni!!!"

?oo.LogEventEx( 
   @a,                          // Array to save
   " Error---binary dump",;     // Binary information to save
   EVENTLOG_ERROR_TYPE,;        // Error type
   0,;                          // Error category
   0,;                          // Event number
   "RobertApp";                 // Application name
   )
RELEASE oo
As a result, we obtain this, where we can see that the two event descriptions have data, both the text-format description, and the information in binary format.

Figure 10: Event registered with both descriptions

If we desire, as before, to pass a single message, the only thing we have to do is to pass a single item to the array, and that's it, but to complete this class, we will create a new method that does this. This method will be something like this:

PROCEDURE LogEventExEx( sStringMsg, sStringBin, iLogType, iCategoty, iEventID, sAplication )

   DIMENSION arrMsg[1]
   arrMsg[1] = sStringMsg
   RETURN This.LogEventEx( @arrMsg, sStringBin, iLogType, iCategoty, iEventID, sAplication )
ENDPROC
Y lo llamaríamos así:
SET PROCEDURE TO EventLog.prg
oo = CREATEOBJECT("CEventLog")
?oo.LogEventExEx( 
   "Roberto Ianni",;            // Binary information to save
   " Error---binary dump",;     // Binary information to save
   EVENTLOG_ERROR_TYPE,;        // Error type
   0,;                          // Error category
   0,;                          // Number of event
   "RobertApp";                 // Application name
   )
RELEASE oo

Thus, we have finished our methods, which have the capability of saving to the EventLog, without requiring any external tool.

In the next issue

Up to this point, we have covered a series of resources to access functions of the Windows API, managing concepts which are not so familiar for Visual FoxPro developers, such as pointers, vectors and arrays.

It is important to remember that once we have finished these generic functions, in many cases we can reuse them further on, to call other API functions. The most important thing is to have the concepts clear, so as to know in which cases we need what. It is not important to have a complete dominion of low-level programming, to use it on a daily basis, as C++ programmers do.

In the next part of this article, we will continue this process, detailing all the techniques required to consult the events generated. On the way, we will learn more about how to interact with the Windows API functions.

Roberto C. Ianni, Banco Hipotecario
Roberto Ianni (Buenos Aires, Argentina) is a professional software developer for the last 4 years. He has programmed in C++, VFP, VC++, C#, and has lots of experience in VFP and VC++. He currently works in the Banco Hipotecario as an Analyst-Developer.
More articles from this author
Roberto C. Ianni, December 1, 2004
Today we are going to talk about data compression. This is something we do frequently, something which is useful for many different tasks.
Roberto C. Ianni, June 1, 2004
The "EventLog" or "Event viewer" is something we use on a daily basis, to obtain information about what happened to our computer at a certain moment; for example, when an application generates an error, most of us will intuitively look at the EventLog to see what information it left for us.
Roberto C. Ianni, July 1, 2004
One of the great limitations I find in VFP is the lack of low-level manipulation I can obtain from it. For many, this might not be an inconvenience, but several times I have encountered a problem which can't be solved with Fox, and it is then that I use a DLL or an FLL.
Roberto C. Ianni, August 1, 2004
As an objective for this issue, I want to propose the following points, to complete the first part of the development of an FLL. Advanced development with an FLL; Accessing VFP data; Executing VFP commands from a FLL; MultiThreading; FLL with VFP 9.0; Executing Assembler from VFP.
Roberto C. Ianni, October 1, 2004
This article is the continuation of "Sending email through Visual FoxPro without additional components", published last March. Back when I wrote this article, I didn't think it would get such a huge response, but it did, hence, due to the mails coming from everywhere I decided to write again on that...
Roberto C. Ianni, November 1, 2004
In this second part we will learn how to: Implement MIME, Attach binary files, the final implementation of the WSendMail class, and the use of the WSendMail class.
Roberto C. Ianni, May 1, 2005
Windows Control Panel holds the configuration from several of the operating system's applications, and from some other applications that provide an applet to appear there. Learn how to build this kind of applets to give your application a professional configuration mechanism.
Roberto C. Ianni, April 1, 2005
The intention of this article is to finish with the use of POP3 (Post Office Protocol 3), implementing everything we have seen so far.
Roberto C. Ianni, January 1, 2005
This article is the beginning of receiving email from VFP; this is complemented with the articles you have already seen about sending email from VFP. To be able to do this, we will have to use the POP3 protocol (Post Office Protocol 3).
Roberto C. Ianni, February 1, 2005
We will continue some of the points mentioned in the previous article, in order to be able to implement message reception. The points to be developed are: POP3 Authentication (both Flat and MD5 authentication methods) and EMail parsing.
Roberto C. Ianni, March 1, 2004
Many of us send email using third-party tools such as Outlook Express or Microsoft Outlook, among others, and most likely have encountered registration problems in the client, licencing problems, OLE errors that only occurr at the client's site but not in our developer environment. Also problems if ...
Roberto C. Ianni, August 1, 2006
The present article is one of those that Martín Salías calls "low level fox", the general idea is to develop a service for the operative system whose only goal will be to run a VFP application in charge of handling the business logic.