You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为Flet实现Python IDropTarget包装器时遇资源不足错误求解决

修复Flet Windows应用IDropTarget拖放注册失败问题

你尝试为Flet Windows应用实现文件拖放功能,使用pythoncom、Win32 API和ctypes编写了IDropTarget包装器,但遇到两个问题:

  • 注册拖放目标时返回错误:(-2147024882, "Insufficient memory resources available to complete the operation.", None, None)
  • 拖放效果DROPEFFECT未生效

以下是问题原因和修复方案:

问题根源

  1. COM对象被过早回收:register_drop_target函数中创建的drop_target和drop_target_ptr在函数执行完毕后被Python垃圾回收器销毁,而RegisterDragDrop需要COM对象持续存活。
  2. COM线程模型不正确:仅调用CoInitialize()未指定线程模型,IDropTarget要求运行在单线程单元(STA)中。
  3. 窗口句柄获取不可靠:通过标题查找窗口容易出现误匹配,且轮询效率低。
  4. 未释放STGMEDIUM资源:调用GetData后未释放返回的STGMEDIUM对象,可能导致资源泄漏。

修复后的完整代码

import pythoncom
import win32api
import win32con
import win32gui
from ctypes import windll, create_unicode_buffer, POINTER
import flet as ft
import threading

# 声明ReleaseStgMedium函数用于释放资源
windll.ole32.ReleaseStgMedium.argtypes = [POINTER(pythoncom.STGMEDIUM)]

class IDropTarget:
    _public_methods_ = ["DragEnter", "DragOver", "DragLeave", "Drop"]
    _com_interfaces_ = [pythoncom.IID_IDropTarget]

    def __init__(self, callback):
        self.callback = callback
        print("IDropTarget initialized")

    def DragEnter(self, dataObject, keyState, point, effect):
        # 检查是否支持CF_HDROP格式
        if dataObject.QueryGetData(pythoncom.FormatEtc(pythoncom.CF_HDROP)) == 0:
            effect[0] = pythoncom.DROPEFFECT_COPY
        else:
            effect[0] = pythoncom.DROPEFFECT_NONE
        print("DragEnter")
        return 0

    def DragOver(self, keyState, point, effect):
        effect[0] = pythoncom.DROPEFFECT_COPY
        print("DragOver")
        return 0

    def DragLeave(self):
        print("DragLeave")
        return 0

    def Drop(self, dataObject, keyState, point, effect):
        format_etc = pythoncom.FormatEtc(
            pythoncom.CF_HDROP,
            None,
            pythoncom.DVASPECT_CONTENT,
            -1,
            pythoncom.TYMED_HGLOBAL,
        )
        try:
            stgmedium = dataObject.GetData(format_etc)
            data = stgmedium.data
            num_files = windll.shell32.DragQueryFileW(data, -1, None, 0)
            file_list = []
            for i in range(num_files):
                length = windll.shell32.DragQueryFileW(data, i, None, 0)
                buffer = create_unicode_buffer(length + 1)
                windll.shell32.DragQueryFileW(data, i, buffer, length + 1)
                file_list.append(buffer.value)
            self.callback(file_list)
            print("Drop", file_list)
            effect[0] = pythoncom.DROPEFFECT_COPY
        finally:
            # 释放STGMEDIUM资源
            windll.ole32.ReleaseStgMedium(stgmedium)
        return 0

# 全局变量保存drop_target,避免被GC回收
global_drop_target = None

def register_drop_target(hwnd, callback):
    global global_drop_target
    global_drop_target = IDropTarget(callback)
    drop_target_ptr = pythoncom.WrapObject(
        global_drop_target, pythoncom.IID_IDropTarget, pythoncom.IID_IDropTarget
    )
    pythoncom.RegisterDragDrop(hwnd, drop_target_ptr)
    print("Drop target registered")

def handle_drop(files):
    for file in files:
        print(f"File dropped: {file}")

def main(page: ft.Page):
    page.title = "dnd"
    page.add(ft.Text("Drag and Drop files here"))

    def run_drop_target():
        # 初始化STA线程模型
        pythoncom.CoInitializeEx(pythoncom.COINIT_APARTMENTTHREADED)
        try:
            register_drop_target(page.window.hwnd, handle_drop)
            pythoncom.PumpMessages()
        except pythoncom.com_error as e:
            print(f"Error registering drop target: {e}")
        finally:
            pythoncom.CoUninitialize()

    # 启动拖放处理线程
    thread = threading.Thread(target=run_drop_target)
    thread.daemon = True
    thread.start()

    page.update()

if __name__ == "__main__":
    ft.app(target=main)

关键修复点说明

  • 保留COM对象引用:用全局变量global_drop_target保存IDropTarget实例,防止被垃圾回收。
  • 指定STA线程模型:使用CoInitializeEx(pythoncom.COINIT_APARTMENTTHREADED)初始化COM,符合IDropTarget的线程要求。
  • 直接获取Flet窗口句柄:通过page.window.hwnd直接获取窗口句柄,无需轮询查找标题。
  • 释放STGMEDIUM资源:在finally块中调用ReleaseStgMedium释放拖放数据资源,避免内存泄漏。
  • 添加格式检查:在DragEnter中检查数据对象是否支持CF_HDROP格式,避免无效拖放。

内容的提问来源于stack exchange,提问作者Edoardo Balducci

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 12:02:20