Matthew Beck
Matthew Beck

Reputation: 359

Having issues with C++ Function pointers in COM

There is one thing that bothers me, C++ function pointers. I'm asking this question because I'm trying to implement callback functions used in my game engine. The issue is, for example:

// Callback function
HRESULT RenderScene(float fps){ HRESULT hr; return S_OK; }

// Set the message
msk->SetMessage(0, SM_RENDERSCENE, (void*) RenderScene);

The problem is COM doesn't allow function pointers in their member functions. Also, doesn't allow for polymorphism. As you can see, I made it a void*. Fine with me because I know what the callback function is. The main issue is that I want something that is type-safe. Now say if the user doesn't know the callback function declaration. For example,

// Callback function
HRESULT CALLBACK RenderScene() or RenderScene(int fps) or RenderScene(int a, int b)

Is implemented in WndProc as:

...
SM_RENDERSCENE:
((void (_stdcall*)(float fps)) pfn)(1.0f);
break;
...

The first, doesn't have an argument, therefore, he doesn't see fps. The second, loses precision. The third, loses precision and has an unused argument. Do you see where I'm going at? Tried using a union but COM doesn't allow function pointers in the member function.

I tried, and tried again. Nothing works, even the MFC message maps are ugly if they didnt have those macros.

To clarify, I rather have it return an error like E_FAIL or E_INVALIDFUNCTION if the function doesn't match the specification of SM_RENDERSCENE.

Does anyone have a solution to this problem.

Note: I like the COM specification and I'm not going to change, so focus on the issue not about why I'm using COM. Thank you, any help will do.

Upvotes: 1

Views: 784

Answers (3)

Hans Passant
Hans Passant

Reputation: 941327

HRESULT RenderScene(float fps){ ...}
HRESULT CALLBACK RenderScene() or ...
Is implemented ... ((void (_stdcall*)(float fps)) pfn)(1.0f);

No, COM definitely supports function pointers. It is only when you use the subset of COM Automation or need to marshal function calls that you could get in trouble. Which is not the case here, you don't marshal between processes or threads and don't need automation since you work with only one language.

The simple problem is that you have the issue that you are trying to protect against, the function pointer definition doesn't match the implementation. Yes, a (void*) cast will stop the compiler from complaining about it, nothing good happens at runtime.

Your first declaration has the wrong calling convention. Using the STDMETHODIMP macro is wise.

Your second set of declarations have the wrong arguments.

The third snippet is applying an invalid function pointer cast, a cast to a function that returns void instead of HRESULT. And assumes __stdcall even though your RenderScene() function didn't use STDMETHODIMP or CALLBACK. Which is why you don't see a proper value for the argument.

Solve function pointer problems by declaring an alias for the pointer type:

 typedef HRESULT (__stdcall * RenderSceneCallback)(float fps);

And consistenly use RenderSceneCallback in all your declarations. Never cast.

Upvotes: 1

Sergey Podobry
Sergey Podobry

Reputation: 7189

Pass interface pointers instead of function pointers. Also there is no function overloading in COM, so use different function names. Here is a pseudocode:

struct IRenderScene : public IUnknown
{
  HRESULT RenderScene(); // Callback function
  HRESULT RenderSceneFps(float fps); // Callback function
  HRESULT RenderSceneAB(int a, int b); // Callback function
}

IRenderScene *renderScene = ...;
msk->SetRenderScene(renderScene); // Set the callback

You don't need to implement COM Event Handling (Connection Points) if it is superfluous to you.

Upvotes: 1

yms
yms

Reputation: 10418

COM is a technology that is supposed to support multiple programming languages, and not all programming languages have support for all kind of features, hence some features like function pointers and exceptions are not (in general) supported in COM.

In the case of callbacks, usually the way to go is to define a COM interface that includes the method you need to call. You can implement this function in a COM class of your choice, and you can specify this interface as a parameter to any other COM function.

If you are using ATL for your COM implementation, here is a link on how to add an new interface to an ATL project. And you could also take a look on this article at codeproject about COM Event Handling

Upvotes: 1

Related Questions