6
\$\begingroup\$

My aim was to provide functionality for a Flutter app on Windows to accept drag-and-dropped files. Flutter apps are written in Dart and compiled to native code, but since this is a cross-platform framework there needs to be a native part - the runner app - which kickstarts the Dart part of the app.

So I changed the C++ code of the Windows runner app to support drag and drop. The code works but I have very little C++ experience, so I likely

  1. made mistakes
  2. implemented anti-patterns
  3. created memory leaks
  4. introduced performance issues
  5. all of the above

Here's the code, please be gentle with me:

flutter_window.h

#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_

#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>

#include <memory>

#include "run_loop.h"
#include "win32_window.h"

// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window, public IDropTarget {
 public:
  // Creates a new FlutterWindow driven by the |run_loop|, hosting a
  // Flutter view running |project|.
  explicit FlutterWindow(RunLoop* run_loop,
                         const flutter::DartProject& project);
  virtual ~FlutterWindow();

 protected:
  // Win32Window:
  bool OnCreate() override;
  void OnDestroy() override;
  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
                         LPARAM const lparam) noexcept override;

  // IUnknown implementation
  HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject);
  ULONG __stdcall AddRef (void);
  ULONG __stdcall Release (void);

  // IDropTarget implementation
  HRESULT __stdcall DragEnter(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
  HRESULT __stdcall DragOver(DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
  HRESULT __stdcall DragLeave(void);
  HRESULT __stdcall Drop(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);

 private:
  // The run loop driving events for this window.
  RunLoop* run_loop_;

  // The project to run.
  flutter::DartProject project_;

  // The Flutter instance hosted by this window.
  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;

  // the D&D registered object
  HWND _hwndRegistered;

  // helper function to convert 16-bit wstring to utf-8 encoded string
  std::string utf8_encode(const std::wstring &wstr);
  
};

#endif  // RUNNER_FLUTTER_WINDOW_H_

flutter_window.cpp

#include "flutter_window.h"

#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>

#include <optional>

#include "flutter/generated_plugin_registrant.h"

FlutterWindow::FlutterWindow(RunLoop *run_loop,
                             const flutter::DartProject &project)
    : run_loop_(run_loop), project_(project) {}

const char kChannelName[] = "desktop_drop";

std::unique_ptr<flutter::MethodChannel<>> channel;

FlutterWindow::~FlutterWindow() {}

bool FlutterWindow::OnCreate()
{
  if (!Win32Window::OnCreate())
  {
    return false;
  }

  RECT frame = GetClientArea();

  // The size here must match the window dimensions to avoid unnecessary surface
  // creation / destruction in the startup path.
  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
      frame.right - frame.left, frame.bottom - frame.top, project_);
  // Ensure that basic setup of the controller was successful.
  if (!flutter_controller_->engine() || !flutter_controller_->view())
  {
    return false;
  }
  RegisterPlugins(flutter_controller_->engine());
  run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
  SetChildContent(flutter_controller_->view()->GetNativeWindow());

  //DnD: Initialize OLE
  OleInitialize(nullptr);

  //DnD: Register Drag & Drop
  if (SUCCEEDED(RegisterDragDrop(flutter_controller_->view()->GetNativeWindow(), this)))
  {
    _hwndRegistered = flutter_controller_->view()->GetNativeWindow();
  }

  channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
      flutter_controller_->engine()->messenger(), kChannelName,
      &flutter::StandardMethodCodec::GetInstance());

  return true;
}

void FlutterWindow::OnDestroy()
{
  if (flutter_controller_)
  {
    run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
    flutter_controller_ = nullptr;
  }

  //DnD: Unregister Drag & Drop
  if (_hwndRegistered)
  {
    RevokeDragDrop(_hwndRegistered);
    _hwndRegistered = NULL;
  }

  //DnD: Uninitialize OLE
  OleUninitialize();

  Win32Window::OnDestroy();
}

LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
                              WPARAM const wparam,
                              LPARAM const lparam) noexcept
{
  // Give Flutter, including plugins, an opportunity to handle window messages.
  if (flutter_controller_)
  {
    std::optional<LRESULT> result =
        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
                                                      lparam);
    if (result)
    {
      return *result;
    }
  }

  switch (message)
  {
  case WM_FONTCHANGE:
    flutter_controller_->engine()->ReloadSystemFonts();
    break;
  }

  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

//DnD: Implement IUnknown
HRESULT STDMETHODCALLTYPE
FlutterWindow::QueryInterface(REFIID riid, _COM_Outptr_ void **ppvObject)
{

  return S_OK;
}

//DnD: Implement IUnknown
ULONG STDMETHODCALLTYPE FlutterWindow::AddRef()
{
  return 0;
}

//DnD: Implement IUnknown
ULONG STDMETHODCALLTYPE FlutterWindow::Release()
{
  return 0;
}

//DnD: Implement IDropTarget
HRESULT __stdcall FlutterWindow::DragEnter(IDataObject *pDataObject, DWORD grfKeyState,
                                           POINTL pt, DWORD *pdwEffect)
{
  channel.get()->InvokeMethod("entered", nullptr);
  // TODO: Implement
  return S_OK;
}

//DnD: Implement IDropTarget
HRESULT __stdcall FlutterWindow::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
  channel.get()->InvokeMethod("updated", nullptr);
  // TODO: Implement
  return S_OK;
}

//DnD: Implement IDropTarget
HRESULT __stdcall FlutterWindow::DragLeave(void)
{
  // TODO: Implement
  channel.get()->InvokeMethod("exited", nullptr);
  return S_OK;
}

//DnD: Implement IDropTarget
HRESULT __stdcall FlutterWindow::Drop(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
  //wchar_t caFileName[MAX_PATH];
  FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT,
                    -1, TYMED_HGLOBAL};
  STGMEDIUM stgm;
  if (SUCCEEDED(pDataObject->GetData(&fmte, &stgm)))
  {
    HDROP hDropInfo = reinterpret_cast<HDROP>(stgm.hGlobal);

    // Get the number of files
    UINT nNumOfFiles = DragQueryFileW(hDropInfo, 0xFFFFFFFF, NULL, 0);

    flutter::EncodableList list = {};

    wchar_t sItem[MAX_PATH];
    for (UINT nIndex = 0; nIndex < nNumOfFiles; ++nIndex)
    {
      //fetch the length of the path
      UINT cch = DragQueryFileW(hDropInfo, nIndex, NULL, 0);

      //fetch the path and store it in 16bit wide char
      DragQueryFileW(hDropInfo, nIndex, (LPWSTR)sItem, cch + 1);

      std::wstring pathInfoUtf16(sItem, cch);
      std::string pathInfoUtf8 = FlutterWindow::utf8_encode(pathInfoUtf16);
      list.push_back(flutter::EncodableValue(pathInfoUtf8));
    }

    DragFinish(hDropInfo);

    ReleaseStgMedium(&stgm);

    channel.get()->InvokeMethod("dropped", std::make_unique<flutter::EncodableValue>(list));
  }
  return S_OK;
}

// helper function to convert 16-bit wstring to utf-8 encoded string
std::string FlutterWindow::utf8_encode(const std::wstring &wstr)
{
  if (wstr.empty())
    return std::string();
  int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
  std::string strTo(size_needed, 0);
  WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
  return strTo;
}

Most of this code is auto-generated when creating a new Flutter project on Windows. Everything that is annotated with //DnD: is my code.

I specifically would like to know whether I implemented the IUnknown and IDropTarget interfaces correctly. The base line for my app is Windows 10 64 bit, so I don't need to be compatible with older versions of Windows.

\$\endgroup\$
2
  • \$\begingroup\$ We may not be able to spot memory leaks unless the code that calls the functions is included (main?). Excellent first question other than that. \$\endgroup\$
    – pacmaninbw
    Dec 9, 2020 at 14:21
  • 1
    \$\begingroup\$ Unfortunately it's impossible to provide all files to run this example. To be able to run it, one has to set up the flutter framework on Windows (flutter.dev/docs/get-started/install/windows) and then create an empty project. The above files (flutter_window.h and flutter_window.cpp) are part of each flutter app and their contents can be replaced with the above code. \$\endgroup\$
    – technolion
    Dec 9, 2020 at 17:25

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.