为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未生效
以下是问题原因和修复方案:
问题根源
- COM对象被过早回收:
register_drop_target函数中创建的drop_target和drop_target_ptr在函数执行完毕后被Python垃圾回收器销毁,而RegisterDragDrop需要COM对象持续存活。 - COM线程模型不正确:仅调用
CoInitialize()未指定线程模型,IDropTarget要求运行在单线程单元(STA)中。 - 窗口句柄获取不可靠:通过标题查找窗口容易出现误匹配,且轮询效率低。
- 未释放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
相关产品推荐
相关产品推荐

