Understanding function pointers

by Brent Knigge

As you probably know, a pointer is a variable containing a memory address. Most programmers use pointers to store the address of simple data types (such as a char, int, or long), or to access class objects. What isn't well known is that functions reside in memory as well, and therefore it is possible to store the address of a function in a pointer.

This concept gives the programmer incredible flexibility with the way applications are constructed. Pointers to functions are used extensively throughout the Windows API and VCL. Each framework utilizes pointers to functions in different ways. This article will explain how each technology implements them.

 

Simple function pointers

To start with, Iíll explain basic function pointers. Basic function pointers have existed since the inception of the C language. Letís say that you had a simple function declared like this:

int func(float arg);

To declare a pointer to this function you would use this syntax:

int (*ptrfunc)(float arg);	 

Once you have declared a function there are two things you can do with it: invoke the function, or take its address. When a function name is used in code without parentheses, the address of the function is returned. For example:

int func(float arg) 
{
  // function body
}

ptrfunc = func;

The ptrfunc variable now points to the address of the func() function. func() can now be called in the following ways.

// call function normally
func(3.2);    
// call function via pointer
ptrfunc(3.2); 
// call function via pointer
(*ptrfunc)(3.2); 

The last two examples achieve the same result; calling the function through a pointer. Using the syntax provided from the last example helps to better document the code so that a programmer can see that the function is in fact being invoked through a pointer.

 

Function pointers in the VCL

Pointers to functions are used in the VCL to provide the flexibility of calling a function that will be determined at runtime. Imagine with the previous example there was another function declared, funcB(), and that this function had the same argument list and return type as the func() function. Then ptrfunc could point to either function, as shown here:

if (choice)  // some boolean variable
  ptrfunc = func;
else
  ptrfunc = funcB;

ptrfunc(3.2); 

Now when ptrfunc is invoked, it calls either func() or funcB() depending on the value of the choice variable. This method of programming was used in C before the advent of C++ and virtual functions. The VCL uses a similar methodology for events, allowing several pointers to point to the same function.

Programmatically this can be done in the following manner:

Button1->OnClick = Button1Click;
Button2->OnClick = Button1Click;

Notice that these two events will invoke the same function when either button is clicked. This is possible because the VCL invokes events through the use of a pointer.

Event variables are not just ordinary function pointers, though. Declaring a pointer to an event in a VCL class differs slightly from standard C++ function pointers. Events require the use of a typedef and the __closure keyword. The following example illustrates this:

// usually declared private
typedef int  
  (__closure *ptrdef)(float arg);
// local to a function or declared public
ptrdef ptrfunc;		

Now ptrfunc is a pointer to a function that can be utilized the same way as shown in the previous section.

The __closure keyword is used to declare a special kind of function pointer. A regular function pointer holds a 32-bit value. It simply points to a memory location. A closure, however, holds a 64-bit value. It contains the address of the function highest 32 bits, and the address of the class where the function resides in the lowest 32 bits. Closures are not part of standard C++. Borland added the __closure keyword to C++Builder specifically to handle the event architecture.

The most likely reason for a programmer to use a pointer to a function in C++Builder would be to create events for custom components. This is a fairly simple process but there are a few things that you should consider when declaring an event:

 

 

The following code shows how a programmer might declare an event in the header file of a custom component:

private:
// the event type
typedef void __fastcall
  (__closure *ptrEvent)(
  TObject *Sender, float arg);
// declare a variable to hold 
// the function pointer
ptrEvent FOnPtrfunc;

published:
// declare the event
__property FOnPtrfunc OnPtrfunc = 
  {read=FOnPtrfunc, write=FOnPtrfunc};

It is imperative to check that the event pointer actually contains a valid address before attempting to call the event handler. Events are typically fired with code like this:

// Check for valid address
if(FOnPtrfunc)		
  // if valid, invoke the function
  FOnPtrfunc(this, 3.2);

You wonít typically use closures outside of the VCL event model but you certainly may if you wish. You must understand that you can use regular function pointers in a C++Builder program. In other words, the fact that Borland created closures for C++Builder doesnít mean you canít use regular function pointers in your applications.

 

Function pointers and the Windows API

The Windows API uses pointers to functions for different purposes. For example, the API often uses function pointers as parameters to other function calls.

There are two ways to declare a function when one of the arguments is a pointer to a function. One way is to use the typedef keyword to declare a new type and then use that type in the function declaration. The other way is to simply redefine the function pointer in the function that uses the pointer. When declaring C++ functions, argument names do not need to be specified. For example:

void funcD(int, ptrdef);
void funcD(int, int(__closure *)(float));

The following code shows several ways in which the address of a function called func() can be passed to the function called funcD().

ptrdef ptr;
ptr = func;

funcD(2, ptr);
//address of funcB passed directly
funcD(2, func);	
//no difference if the ampersand is used
funcD(2, &func);  

In the last two examples, the actual name of the function is passed. Because the function name was passed without parentheses, the expression evaluates to the address of the function. Even though a function address is being passed as a parameter, the responsibility still resides with the programmer to call the function that the pointer points to. An example of how this might work is shown below.

void FuncD(int Size, ptrdef SomeFunc)
{
  if(Size > 1000)
    Size = SomeFunc(3.2); 
}

The Windows API contains many functions that make use of a callback function. When defining the callback function you must be sure that the function pointer you are passing exactly matches the declaration that Windows has defined for the callback function. Consider the API function EnumFontFamilies(). This function enumerates the fonts in a specified font family. One of the parameters of this function is the address of a callback function. The declaration for EnumFontFamilies() is as follows:

int EnumFontFamilies(
  HDC hdc, LPCTSTR lpszFamily, 
  FONTENUMPROC lpEnumFontFamProc, 
  LPARAM lParam);

As you can see, the third parameter is used to pass the address of a callback function. The declaration of the callback function must match the specifications defined for FONTENUMPROC. The FONTENUMPROC type is declared like this:

int CALLBACK EnumFontFamProc(
  ENUMLOGFONT FAR *lpelf, 
  NEWTEXTMETRIC FAR *lpntm, 
  int FontType, LPARAM lParam);

The CALLBACK macro gives the programmer a clue that this function is used as an argument for another API function. CALLBACK is simply a macro that expands to __stdcall (Windows callback functions are always declared with this calling convention). The actual callback function could be defined as follows.

int CALLBACK MyEnumFont(
  ENUMLOGFONTEX *lpelf, 
  NEWTEXTMETRICEX *lpntm, 
  int FontType, LPARAM lParam)
{
  //Extract font information if desired
  ShowMessage(
    lpelf->elfLogFont.lfFaceName);
  return 0;
}

Notice how this function is a near mirror image of the FONTENUMPROC type. Windows API callback functions must be stand-alone functions (they cannot be class member functions). If you attempt to pass a class member function where Windows expects a callback function you will receive compiler errors.

This particular callback function will be called every time the EnumFontFamilies() function comes across a font with the name specified in the LPCTSTR parameter. An example call to EnumFontFamilies() might look like this:

int result = EnumFontFamilies(
  Canvas->Handle, "Times New Roman",
  (FONTENUMPROC)MyEnumFont, NULL);

As with all Windows API callback functions, the address of the callback function is cast to the specific type Windows is expecting. In this case the function pointer is cast to the FONTENUMPROC type.

 

Conclusion

Pointers to functions are often not documented in C++ books. As such, programmers often find themselves using function pointers in their code without fully understanding the concepts. This is particularly true of VCL events as they use a function pointer type not found in standard C++. The material presented in this article should remove any misconceptions that you may have had and prepare you for effectively using pointers to functions in your code.