views:

394

answers:

3

I know this has been asked before but none of the cases I've seen here are like this one. I am importing some API functions at runtime, the general declaration on those functions would be like:

// Masks for UnmapViewOfFile and MapViewOfFile
typedef BOOL (WINAPI *MyUnmapViewOfFile)(LPCVOID);
typedef LPVOID (WINAPI *MyMapViewOfFile)(HANDLE, DWORD, DWORD, DWORD, SIZE_T);

// Declarations
MyUnmapViewOfFile LoadedUnmapViewOfFile;
MyMapViewOfFile LoadedMapViewOfFile;

I then call a generic "load" function where it calles GetProcAddress to get the address of the exported function from the proper DLL. That address is returned on a void**. This void** is one of the parameters in the generic load, something like:

int GenericLoad(char* lib, void** Address, char* TheFunctionToLoad)

and I would call this function:

void *Address;
GenericLoad("kernel32.dll", &Address, "UnmapViewOfFile");
LoadedUnmapViewOfFile = (MyUnmapViewOfFile) Address;

Or something similar to this. Now, of course the compiler complains about trying to cast a data void* to a function pointer. How do I do this then?

I've read countless sites and all kinds of nasty casts, so I'd appreciate it if you add code to the explanation.

Thanks Jess

+4  A: 

The correct code would be this one line:

GenericLoad("kernel32.dll", (void**)&LoadedUnmapViewOfFile, "UnmapViewOfFile");

What is done here basically is this: the address of the pointer variable (the one in which you want the address of the function to be put) is passed to GenericLoad - which is basically what it expects. void** was to denote "give me the address of your pointer". All the type casting is a magic around it. C would not allow specifying "a pointer to any function pointer", so the API author preferred void**.

Pavel Radzivilovsky
Thank you! I'll try this and see if nothing is broken
Jessica
+2  A: 

Addresses to data and addresses to functions are incompatible things. I believe that your Address variable must be a function pointer as in your BOOL definition: (WINAPI *MyUnmapViewOfFile)(LPCVOID). This type of declaration is required because in order to have a pointer to a function, the return type, and the type and number of arguments must be known. This is because when you call your function, the correct amount of space must be allocated on the stack to contain these return value and args.

Given that, I believe that the correction in Pavel's answer is correct (FYI, his (void**) cast is a type safety measure).

Dana the Sane
thanks for the explanation.
Jessica
+2  A: 

Now, of course the compiler complains about trying to cast a data void* to a function pointer

My compilers don't complain at all with your code (then again, I don't have warnings cranked up - I'm using more-or-less default options). This is with a variety of compilers from MS, GCC,and others. Can you give more details about the compiler and compiler options you're using and the exact warning you're seeing?

That said, C doesn't guarantee that a function pointer can be cast to/from a void pointer without problems, but in practice this will work fine on Windows.

If you want something that's standards compliant, you'll need to use a 'generic' function pointer instead of a void pointer - C guarantees that a function pointer can be converted to any other function pointer and back without loss, so this will work regardless of your platform. That's probably why the return value of the Win32 GetProcAddress() API returns a FARPROC, which is just a typedef for a function pointer to a function that takes no parameters (or at least unspecified parameters) and returns a pointer-sized int. Something like:

typedef INT_PTR (FAR WINAPI *FARPROC)();

FARPROC would be Win32's idea of a 'generic' function pointer. So all you should need to do is have a similar typedef (if you don't want to use FARPROC for some reason):

typedef intptr_t (*generic_funcptr_t)();    // intptr_t is typedef'ed appropriately elsewhere, 
                                            //    like in <stdint.h> or something

int GenericLoad(char* lib, generic_funcptr_t* Address, char* TheFunctionToLoad)

generic_funcptr_t Address;
GenericLoad("kernel32.dll", &Address, "UnmapViewOfFile");
LoadedUnmapViewOfFile = (MyUnmapViewOfFile) Address;

Or you can dispense with the middle-man and pass the pointer you really want to get the value into:

GenericLoad2("kernel32.dll", (generic_funcptr_t *) &LoadedUnmapViewOfFile, "UnmapViewOfFile");

Though that's more dangerous than the method using the intermediate variable - for example the compiler will not give a diagnostic if you leave off the ampersand in this last example, however in the previous example, it would generally give at least a warning if you left off the ampersand from the Address argument. Similar to Bug #1 here: http://blogs.msdn.com/sdl/archive/2009/07/28/atl-ms09-035-and-the-sdl.aspx

Now you should be set. However, any way you look at it you'll need to perform some dangerous casting. Even in C++ with templates you'd have to perform a cast at some level (though you might be able to hide it in the template function) because the GetProcAddress() API doesn't know the actual type of the function pointer you're retrieving.

Also note that your interface for GenericLoad() has a possibly serious design problem - it provides no way to manage the lifetime of the library. That may not be a problem if your intent is to not allow unloading a library, but it's something that users may want so you should consider the issue.

Michael Burr