是否可以在C ++中为顶级函数访问对象的成员?

时间:2011-04-25 17:53:06

标签: c++ c class object encapsulation

所以我在Win32中为GUI编程编写了一些包装类。我从一个Window类开始,到目前为止它包含一个MainLoop方法,它基本上是标准Win32 WinMain函数的克隆。这样,人们可以这样做:

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow) {
  Window *win = new Window();

  // Do all your widget creation and add it to the window object...

  return win->MainLoop(hInst, hPrev, szCmdLine, nCmdShow);
}

在window对象的MainLoop方法中,它必须通过设置其lpfnWndProc成员来创建新的Win32窗口。正如任何Win32程序员所知,该成员是指向特定定义的WndProc函数的函数指针。问题是,如果我要创建一个WndProc函数,我需要访问该窗口对象的成员(以便它知道在窗口上绘制什么,等等)。这给我留下了两个选择(我知道):

  1. 我可以在顶层定义WndProc,但会切断对对象成员的访问权。

  2. 我可以将它定义为类方法,但是它不是lpfnWndProc要求的确切函数类型,所以我无法设置它!

  3. 任何人都可以帮我解开这个捕获22吗?

5 个答案:

答案 0 :(得分:4)

您还可以将其设为静态成员函数。 :)
无论如何,解决方案取决于您是否只需要一个窗口或是否需要多个窗口。 首先是单个窗口的解决方案:

// in .h
class Window{
public:
  static LRESULT WINAPI MessageProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
  LRESULT InternalMessageProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
  // ...
};

// in .cpp
#include "Window.h"

Window* global_window = 0;

Window::Window(/*...*/){
  if(!global_window)
    global_window = this;
  else
    // error or exception... or something else
}

LRESULT WINAPI Window::MessageProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
  return global_window->InternalMessageProc(hWnd, msg, wParam, lParam);
}

现在,如果您想允许多个窗口,请使用std::map(或者如果您的编译器支持std::unordered_map)。
修改:此解决方案带有一些微妙的问题。正如@Ben Voigt在他的评论中指出的那样,由于在MessageProc内调用了CreateWindow(Ex),你会遇到鸡和蛋的问题,但只有在CreateWindow(Ex)调用之后才会有窗口句柄。这是基于Ben的下一条评论的解决方案(谢谢!):

// Window.h stays the same

// in .cpp
#include "Window.h"
#include <map>

std::map<HWND, Window*> window_map;
Window* currently_created_window = 0;

Window::Window(){
  currently_created_window = this;
  window_handle = CreateWindow(/*...*/);
}

LRESULT WINAPI Window::MessageProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
  // if the key 'hWnd' doesn't exist yet in the map
  // a new key-value pair gets created and the value gets value-initialized
  // which, in case of a pointer, is 0
  if(window_map[hWnd] == 0){
    // window doesn't exist yet in the map, add it
    window_map[hWnd] = currently_created_window;
  }
  window_map[hWnd]->InternalMessageProc(hWnd, msg, wParam, lParam);
}

但要小心,因为上面的示例不是线程安全的。你需要互斥锁定窗口的创建:

Window::Window(/*...*/){
  Lock lock_it(your_mutex);
  currently_created_window = this;
  window_handle = CreateWindow(/*...*/);
  lock_it.release();
  // rest of the initialization
}

上面应该为线程安全做(我希望)。

答案 1 :(得分:3)

您需要创建窗口地图,并在创建新窗口时将其添加到此全局地图中。您可以使用简单的链接列表,而不是当然。

map<HWND, Window *> wndmap;

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
{
    Window *pWnd = wndmap [hwnd];

    ....
}

答案 2 :(得分:1)

WndProc不能是实例成员函数,因为Windows不会传递任何隐藏的this参数。它可以是命名空间范围或静态成员。

一个简单的解决方案是使用map<HWND, Window*>查找对象,然后将参数转发给对象上的方法。

请注意,WndProc可以维护地图本身,因为CreateWindow提供了一个不透明的用户参数,该参数显示在WM_CREATE中,这对于携带Window *很有用,然后您删除了WM_DESTROY中的条目。

答案 3 :(得分:0)

将WndProc定义为静态类成员 - 这将与非成员函数指针兼容(对于我所知的所有编译器),例如Win32编程中使用的那些。

但我不得不说这有点浪费时间 - 那里有很多Windows类库,而且我认为这个世界真的不需要另外一个。

答案 4 :(得分:-1)

听起来你需要声明函数而不定义它。这就是原型的用途。

class Object;

void f(Object* o);

class Object {
    public:
        ...
        void some_method() {
           ... &f ...
        }

        void another_method() {
           ...
        }

        ...
};

void f(Object* o) {
    ...
    o->another_method();
    ...
}

反过来也可能。

class Object {
    public:
        ...
        void some_method();
        void another_method();
        ...
};

void f(Object* o) {
    ...
    o->another_method();
    ...
}

void Object::some_method() {
    ... &f ...
}

void Object::another_method() {
    ...
}