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
- made mistakes
- implemented anti-patterns
- created memory leaks
- introduced performance issues
- 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.