Windows 消息循环集成:在 `WndProc` 中处理 Flutter Engine 的事件分发

Windows 消息循环集成:在 WndProc 中处理 Flutter Engine 的事件分发

大家好,今天我们来深入探讨 Flutter Engine 在 Windows 平台上的集成,特别是如何在 WndProc 函数中处理 Flutter Engine 的事件分发。这部分是 Flutter 在 Windows 上运行的关键,理解它有助于我们更好地调试、优化和定制 Flutter 应用。

1. Windows 消息循环与 WndProc

首先,我们需要理解 Windows 消息循环的基本概念。在 Windows 操作系统中,应用程序通过消息循环来响应用户的操作和系统事件。消息循环的核心是 GetMessage 函数,它从消息队列中获取消息,然后将消息传递给窗口过程(Window Procedure),也就是 WndProc 函数。

WndProc 是一个回调函数,负责处理特定窗口的消息。它接收窗口句柄 (HWND)、消息类型 (UINT)、WPARAMLPARAM 这四个参数,并根据消息类型执行相应的操作。

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  switch (message) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    // 其他消息处理
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
}

在这个简单的例子中,WndProc 处理了 WM_DESTROY 消息,当窗口被销毁时,它会调用 PostQuitMessage 函数,将一个退出消息放入消息队列中,从而结束应用程序的消息循环。对于其他未处理的消息,它会调用 DefWindowProc 函数,将消息传递给默认的窗口过程进行处理。

2. Flutter Engine 的集成挑战

Flutter Engine 本身不是一个原生的 Windows 窗口。它渲染的内容绘制在它管理的 Surface 上,这个 Surface 通常是 DirectX 或 OpenGL 的渲染目标。因此,我们需要将 Flutter Engine 的渲染结果集成到 Windows 窗口中,并处理用户的输入事件,例如鼠标点击、键盘输入等,并将这些事件传递给 Flutter Engine。

这带来了一些挑战:

  • 渲染集成: 如何将 Flutter Engine 渲染的图像正确地显示在 Windows 窗口中。
  • 事件分发: 如何将 Windows 消息转换为 Flutter Engine 可以理解的事件,并将其传递给 Flutter Engine 进行处理。
  • 线程同步: Flutter Engine 通常运行在独立的线程中,因此需要确保线程安全地访问和修改共享资源。

3. 在 WndProc 中集成 Flutter Engine

为了解决上述挑战,我们需要在 WndProc 函数中集成 Flutter Engine 的事件分发逻辑。以下是一种常见的集成方法:

  • 创建 Flutter Engine 实例: 在应用程序初始化时,创建 Flutter Engine 的实例,并将其与 Windows 窗口关联起来。
  • 处理 Windows 消息:WndProc 函数中,拦截与输入事件相关的 Windows 消息,例如 WM_MOUSEMOVEWM_LBUTTONDOWNWM_KEYDOWN 等。
  • 转换事件: 将 Windows 消息转换为 Flutter Engine 可以理解的事件格式。
  • 分发事件: 将转换后的事件传递给 Flutter Engine 进行处理。

4. 事件分发的具体实现

以下是一个示例代码片段,展示了如何在 WndProc 函数中处理鼠标点击事件,并将其分发给 Flutter Engine:

// 包含必要的头文件
#include "flutter_window.h" // 假设 FlutterWindow 类封装了 Flutter Engine 的操作

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  FlutterWindow* window = reinterpret_cast<FlutterWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));

  switch (message) {
    case WM_LBUTTONDOWN: {
      if (window) {
        // 获取鼠标坐标
        int xPos = LOWORD(lParam);
        int yPos = HIWORD(lParam);

        // 将鼠标点击事件传递给 Flutter Engine
        window->OnMouseButtonEvent(MouseButton::kPrimary, MouseButtonState::kDown, xPos, yPos);
      }
      return 0;
    }
    case WM_MOUSEMOVE: {
        if (window) {
          // 获取鼠标坐标
          int xPos = LOWORD(lParam);
          int yPos = HIWORD(lParam);

          // 将鼠标移动事件传递给 Flutter Engine
          window->OnMouseMoveEvent(xPos, yPos);
        }
        return 0;
    }
    case WM_KEYDOWN: {
        if (window) {
          // 获取按键代码
          int keyCode = wParam;

          // 将键盘事件传递给 Flutter Engine
          window->OnKeyEvent(keyCode, KeyEventType::kDown);
        }
        return 0;
    }
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
}

在这个例子中,我们首先通过 GetWindowLongPtr 函数获取与窗口关联的 FlutterWindow 实例。FlutterWindow 类封装了 Flutter Engine 的相关操作。然后,我们根据消息类型,获取鼠标坐标或键盘代码,并将这些信息传递给 FlutterWindow 实例的相应方法,例如 OnMouseButtonEventOnKeyEvent

5. FlutterWindow 类的实现

FlutterWindow 类负责封装 Flutter Engine 的操作,包括初始化、渲染、事件分发等。以下是一个 FlutterWindow 类的示例代码:

#include <iostream>
#include <flutter_windows.h>

enum class MouseButton {
  kNone,
  kPrimary,
  kSecondary,
  kMiddle,
};

enum class MouseButtonState {
  kNone,
  kDown,
  kUp
};

enum class KeyEventType {
  kDown,
  kUp
};

class FlutterWindow {
 public:
  FlutterWindow(int width, int height) : width_(width), height_(height) {
    // 初始化 Flutter Engine
    FlutterRendererConfig config = {};
    config.type = kOpenGL;
    config.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig);
    config.open_gl.make_current = [](void* userdata) {
        return true;
    };
    config.open_gl.clear_current = [](void* userdata) {
        return true;
    };
    config.open_gl.present = [](void* userdata) {
        return true;
    };
    config.open_gl.fbo_callback = [](void* userdata) {
        return 0; // 返回默认帧缓冲对象 ID
    };

    FlutterProjectArgs args = {};
    args.struct_size = sizeof(FlutterProjectArgs);
    args.assets_path = "flutter_assets"; // 替换为你的 assets 路径
    args.icu_data_path = "icudtl.dat";   // 替换为你的 icudtl.dat 路径
    args.platform_plugins.struct_size = sizeof(FlutterPlatformPlugin);

    FlutterEngineResult result = FlutterEngineInitialize(&engine_, &config, &args);
    if (result != kSuccess) {
      std::cerr << "Failed to initialize Flutter Engine: " << result << std::endl;
      return;
    }

    FlutterEngineRun(engine_);
  }

  ~FlutterWindow() {
    if (engine_) {
      FlutterEngineShutdown(engine_);
    }
  }

  void OnMouseButtonEvent(MouseButton button, MouseButtonState state, int x, int y) {
    FlutterPointerPhase phase;
    if(state == MouseButtonState::kDown){
        phase = kDown;
    } else {
        phase = kUp;
    }

    FlutterPointerEvent event = {};
    event.struct_size = sizeof(FlutterPointerEvent);
    event.phase = phase;
    event.x = static_cast<double>(x);
    event.y = static_cast<double>(y);
    event.timestamp = GetTickCount(); // 使用 GetTickCount 获取时间戳
    event.device = kFlutterPointerDeviceKindMouse; //鼠标

    FlutterEngineResult result = FlutterEngineSendPointerEvent(engine_, &event, 1);
    if (result != kSuccess) {
        std::cerr << "Failed to send pointer event: " << result << std::endl;
    }
  }

  void OnMouseMoveEvent(int x, int y) {
    FlutterPointerEvent event = {};
    event.struct_size = sizeof(FlutterPointerEvent);
    event.phase = kMove;
    event.x = static_cast<double>(x);
    event.y = static_cast<double>(y);
    event.timestamp = GetTickCount(); // 使用 GetTickCount 获取时间戳
    event.device = kFlutterPointerDeviceKindMouse; //鼠标

    FlutterEngineResult result = FlutterEngineSendPointerEvent(engine_, &event, 1);
    if (result != kSuccess) {
        std::cerr << "Failed to send pointer event: " << result << std::endl;
    }
  }

  void OnKeyEvent(int keyCode, KeyEventType type) {
    // 将 Windows 键盘事件转换为 Flutter KeyEvent
    FlutterKeyEvent event = {};
    event.struct_size = sizeof(FlutterKeyEvent);

    if(type == KeyEventType::kDown){
        event.type = kFlutterKeyEventTypeDown;
    } else {
        event.type = kFlutterKeyEventTypeUp;
    }

    //TODO: 这里需要将 keyCode 转换为 Flutter 可以理解的 key 符号和扫描码
    // event.physical = ...; // 物理键码
    // event.logical = ...; // 逻辑键码
    event.timestamp = GetTickCount(); // 使用 GetTickCount 获取时间戳
    event.synthesized = false;

    FlutterEngineResult result = FlutterEngineSendKeyEvent(engine_, &event);
    if (result != kSuccess) {
      std::cerr << "Failed to send key event: " << result << std::endl;
    }
  }

 private:
  int width_;
  int height_;
  FlutterEngine engine_;
};

在这个例子中,FlutterWindow 类负责初始化 Flutter Engine,并在接收到鼠标点击事件时,将其转换为 FlutterPointerEvent 结构体,然后调用 FlutterEngineSendPointerEvent 函数将事件传递给 Flutter Engine。同样,键盘事件也被转换成 FlutterKeyEvent 结构体并通过 FlutterEngineSendKeyEvent发送。注意,实际应用中 keyCodephysicallogical 的转换需要完善。

6. 数据结构对应关系

为了更好地理解事件分发的过程,以下表格展示了 Windows 消息与 Flutter 事件的数据结构对应关系:

Windows 消息 Flutter 事件类型 数据结构 备注
WM_LBUTTONDOWN PointerEvent FlutterPointerEvent phase 设置为 kDownxy 设置为鼠标坐标
WM_LBUTTONUP PointerEvent FlutterPointerEvent phase 设置为 kUpxy 设置为鼠标坐标
WM_MOUSEMOVE PointerEvent FlutterPointerEvent phase 设置为 kMovexy 设置为鼠标坐标
WM_KEYDOWN KeyEvent FlutterKeyEvent type 设置为 kFlutterKeyEventTypeDown,需要将 wParam 转换为 Flutter 可以理解的 key 符号和扫描码
WM_KEYUP KeyEvent FlutterKeyEvent type 设置为 kFlutterKeyEventTypeUp,需要将 wParam 转换为 Flutter 可以理解的 key 符号和扫描码

7. 线程安全问题

Flutter Engine 通常运行在独立的线程中,而 WndProc 函数运行在主线程中。因此,我们需要确保线程安全地访问和修改共享资源,例如 FlutterWindow 实例和 Flutter Engine 实例。

常见的线程安全方法包括:

  • 互斥锁: 使用互斥锁来保护共享资源,防止多个线程同时访问。
  • 原子操作: 使用原子操作来修改简单的共享变量,例如计数器。
  • 消息队列: 使用消息队列将消息从一个线程传递到另一个线程。

在上述例子中,我们可以使用互斥锁来保护 FlutterWindow 实例,防止多个线程同时访问。

8. 渲染集成

除了事件分发之外,还需要将 Flutter Engine 渲染的图像集成到 Windows 窗口中。这通常涉及以下步骤:

  • 创建渲染目标: 创建一个与 Windows 窗口兼容的渲染目标,例如 DirectX 或 OpenGL 的渲染目标。
  • 绑定渲染目标: 将 Flutter Engine 的渲染输出绑定到渲染目标。
  • 绘制渲染目标:WndProc 函数中,处理 WM_PAINT 消息,将渲染目标的内容绘制到 Windows 窗口中。

具体的渲染集成方法取决于你选择的渲染技术。例如,如果你使用 DirectX,你需要创建 IDirect3DDevice9IDirect3DSurface9 对象,并将 Flutter Engine 的渲染输出绑定到 IDirect3DSurface9 对象。然后,在 WM_PAINT 消息处理函数中,使用 IDirect3DDevice9::Present 方法将 IDirect3DSurface9 对象的内容绘制到 Windows 窗口中。

9. 性能优化

在集成 Flutter Engine 时,需要注意性能优化。以下是一些常见的性能优化技巧:

  • 减少消息处理: 尽量减少 WndProc 函数中的消息处理逻辑,避免阻塞主线程。
  • 异步处理: 将耗时的操作放在后台线程中进行处理,避免阻塞主线程。
  • 缓存渲染结果: 缓存 Flutter Engine 的渲染结果,避免重复渲染。
  • 优化渲染设置: 调整 Flutter Engine 的渲染设置,例如分辨率和帧率,以提高性能。

10. 调试技巧

在集成 Flutter Engine 时,可能会遇到各种问题。以下是一些常见的调试技巧:

  • 日志输出: 在代码中添加日志输出,以便跟踪程序的执行流程。
  • 断点调试: 使用调试器设置断点,以便逐步执行代码并检查变量的值。
  • 性能分析: 使用性能分析工具来识别性能瓶颈。

代码示例:完整的 FlutterWindow 类及初始化

#include <iostream>
#include <flutter_windows.h>
#include <Windows.h>

enum class MouseButton {
  kNone,
  kPrimary,
  kSecondary,
  kMiddle,
};

enum class MouseButtonState {
  kNone,
  kDown,
  kUp
};

enum class KeyEventType {
  kDown,
  kUp
};

class FlutterWindow {
 public:
  FlutterWindow(int width, int height) : width_(width), height_(height), engine_(nullptr) {
  }

  bool Initialize() {
    // 初始化 Flutter Engine
    FlutterRendererConfig config = {};
    config.type = kOpenGL;
    config.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig);
    config.open_gl.make_current = [](void* userdata) {
        // 在这里实现 OpenGL 上下文的 MakeCurrent 操作
        // 这通常依赖于你的 OpenGL 实现(例如 GLFW, SDL, 或原生 Windows OpenGL)
        // 示例 (需要根据你的 OpenGL 实现进行调整):
        // HDC hDC = GetDC(static_cast<HWND>(userdata));
        // return wglMakeCurrent(hDC, reinterpret_cast<HGLRC>(userdata));
        return true;
    };
    config.open_gl.clear_current = [](void* userdata) {
        // 在这里实现 OpenGL 上下文的 ClearCurrent 操作
        // 示例 (需要根据你的 OpenGL 实现进行调整):
        // return wglMakeCurrent(NULL, NULL);
        return true;
    };
    config.open_gl.present = [](void* userdata) {
        // 在这里实现 OpenGL 上下文的 Present 操作,用于交换缓冲区
        // 示例 (需要根据你的 OpenGL 实现进行调整):
        // HDC hDC = GetDC(static_cast<HWND>(userdata));
        // return SwapBuffers(hDC);
        return true;
    };
    config.open_gl.fbo_callback = [](void* userdata) {
        // 返回默认帧缓冲对象 ID (通常是 0)
        return 0;
    };

    FlutterProjectArgs args = {};
    args.struct_size = sizeof(FlutterProjectArgs);
    args.assets_path = "flutter_assets"; // 替换为你的 assets 路径
    args.icu_data_path = "icudtl.dat";   // 替换为你的 icudtl.dat 路径
    args.platform_plugins.struct_size = sizeof(FlutterPlatformPlugin);

    FlutterEngineResult result = FlutterEngineInitialize(&engine_, &config, &args);
    if (result != kSuccess) {
      std::cerr << "Failed to initialize Flutter Engine: " << result << std::endl;
      return false;
    }

    return true;
  }

  void Run() {
    if (engine_) {
        FlutterEngineResult result = FlutterEngineRun(engine_);
        if (result != kSuccess) {
            std::cerr << "Failed to run Flutter Engine: " << result << std::endl;
        }
    }
  }

  ~FlutterWindow() {
    if (engine_) {
      FlutterEngineShutdown(engine_);
    }
  }

  void OnMouseButtonEvent(MouseButton button, MouseButtonState state, int x, int y) {
    FlutterPointerPhase phase;
    if(state == MouseButtonState::kDown){
        phase = kDown;
    } else {
        phase = kUp;
    }

    FlutterPointerEvent event = {};
    event.struct_size = sizeof(FlutterPointerEvent);
    event.phase = phase;
    event.x = static_cast<double>(x);
    event.y = static_cast<double>(y);
    event.timestamp = GetTickCount(); // 使用 GetTickCount 获取时间戳
    event.device = kFlutterPointerDeviceKindMouse; //鼠标

    FlutterEngineResult result = FlutterEngineSendPointerEvent(engine_, &event, 1);
    if (result != kSuccess) {
        std::cerr << "Failed to send pointer event: " << result << std::endl;
    }
  }

  void OnMouseMoveEvent(int x, int y) {
    FlutterPointerEvent event = {};
    event.struct_size = sizeof(FlutterPointerEvent);
    event.phase = kMove;
    event.x = static_cast<double>(x);
    event.y = static_cast<double>(y);
    event.timestamp = GetTickCount(); // 使用 GetTickCount 获取时间戳
    event.device = kFlutterPointerDeviceKindMouse; //鼠标

    FlutterEngineResult result = FlutterEngineSendPointerEvent(engine_, &event, 1);
    if (result != kSuccess) {
        std::cerr << "Failed to send pointer event: " << result << std::endl;
    }
  }

  void OnKeyEvent(int keyCode, KeyEventType type) {
    // 将 Windows 键盘事件转换为 Flutter KeyEvent
    FlutterKeyEvent event = {};
    event.struct_size = sizeof(FlutterKeyEvent);

    if(type == KeyEventType::kDown){
        event.type = kFlutterKeyEventTypeDown;
    } else {
        event.type = kFlutterKeyEventTypeUp;
    }

    //TODO: 这里需要将 keyCode 转换为 Flutter 可以理解的 key 符号和扫描码
    // event.physical = ...; // 物理键码
    // event.logical = ...; // 逻辑键码
    event.timestamp = GetTickCount(); // 使用 GetTickCount 获取时间戳
    event.synthesized = false;

    FlutterEngineResult result = FlutterEngineSendKeyEvent(engine_, &event);
    if (result != kSuccess) {
      std::cerr << "Failed to send key event: " << result << std::endl;
    }
  }

  FlutterEngine GetEngine() const { return engine_; }

 private:
  int width_;
  int height_;
  FlutterEngine engine_;
};

// 在创建窗口后,初始化 FlutterWindow
HWND CreateMyWindow(HINSTANCE hInstance, int nCmdShow) {
    const wchar_t CLASS_NAME[]  = L"Sample Window Class";

    WNDCLASS wc = {};

    wc.lpfnWndProc   = WndProc;
    wc.hInstance     = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0,                             // Optional window styles.
        CLASS_NAME,                    // Window class
        L"Flutter Desktop App",    // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,

        NULL,       // Parent window    
        NULL,       // Menu
        hInstance,  // Instance handle
        NULL        // Additional application data
        );

    if (hwnd == NULL)
    {
        return NULL;
    }

    // 创建 FlutterWindow 实例
    FlutterWindow* flutter_window = new FlutterWindow(800, 600);
    if (!flutter_window->Initialize()) {
        delete flutter_window;
        DestroyWindow(hwnd);
        return NULL;
    }

    // 关联 FlutterWindow 实例与窗口句柄
    SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(flutter_window));

    ShowWindow(hwnd, nCmdShow);

    //启动flutter engine
    flutter_window->Run();

    return hwnd;
}

WndProc 中使用已初始化的引擎

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  FlutterWindow* window = reinterpret_cast<FlutterWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));

  switch (message) {
    case WM_LBUTTONDOWN: {
      if (window) {
        // 获取鼠标坐标
        int xPos = LOWORD(lParam);
        int yPos = HIWORD(lParam);

        // 将鼠标点击事件传递给 Flutter Engine
        window->OnMouseButtonEvent(MouseButton::kPrimary, MouseButtonState::kDown, xPos, yPos);
      }
      return 0;
    }
    case WM_MOUSEMOVE: {
        if (window) {
          // 获取鼠标坐标
          int xPos = LOWORD(lParam);
          int yPos = HIWORD(lParam);

          // 将鼠标移动事件传递给 Flutter Engine
          window->OnMouseMoveEvent(xPos, yPos);
        }
        return 0;
    }
    case WM_KEYDOWN: {
        if (window) {
          // 获取按键代码
          int keyCode = wParam;

          // 将键盘事件传递给 Flutter Engine
          window->OnKeyEvent(keyCode, KeyEventType::kDown);
        }
        return 0;
    }
    case WM_DESTROY: {
        FlutterWindow* window = reinterpret_cast<FlutterWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
        if (window) {
            delete window;
            SetWindowLongPtr(hWnd, GWLP_USERDATA, NULL);
        }
      PostQuitMessage(0);
      return 0;
    }
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
}

总结

本文深入探讨了如何在 Windows 消息循环的 WndProc 函数中处理 Flutter Engine 的事件分发。我们讨论了 Windows 消息循环的基本概念、Flutter Engine 的集成挑战、事件分发的具体实现、线程安全问题、渲染集成、性能优化和调试技巧。通过理解这些概念和技术,我们可以更好地将 Flutter Engine 集成到 Windows 应用程序中,并构建高性能、稳定的跨平台应用。 关键在于正确拦截Windows消息,转换为Flutter Event,并通过Flutter Engine API发送。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注