本来想着,自动化投喂拖放文件已经该翻篇了,但架不住强迫症的底子在那儿。打开手机的代办事项,其一清清楚楚写着:实现脚本模拟文件拖放。上次历经千辛万苦,虽然用 C/C++ 实现了这个目的,但在转译为 Groovy 的行程上最终还是为山九仞功亏于一篑:AI(具体来说是 Cursor)把代码改的越来越乱,已经要把作为人类的老夫脑子绕晕了,面对它前面捅出来的篓子,动不动就说这事简单,然后从最底层彻底换一种方式来[玩我]。且不论脑子,单就体力也跟不上,毕竟复制粘贴代码,修改小毛刺以正常编译,测试功能,这也需要时间和力气不是?可是既然那 TODO list 里写的是用“脚本”模拟文件拖放,这只搞定了 C/C++ 算怎么档子事儿?完成的那个勾,挑还是不挑上啊?不行,还得接着弄。没成想在硬盘各个目录里翻找了半天,之前的迭代都不见了!嚓,那就重头来吧。也不在 Android Studio 里做夹生饭了,下载了 Groovy,先根正苗红地开始。
上次折在 COM 接口布局安排上,看 Cursor 的意思,COM 虚表涉及到 native 内存操作,它摆布了几下都没能成功,就没辙了。这次换合作伙伴,Grok3 走起。很幸运的是,调教了一个下午加第二天半个上午,这个事儿竟然成了。虚表指针有了,虚表分配好了,对象实现也有了,QueryInterface
也有了输出,而且实现的方式较之前 Cursor 的既简洁又清晰。欣喜异常。但为了实现最终的功能,那些繁琐的业务代码仍然得去实现,JNA 对于 Windows API 的支持很迷,有的有现成的,有的没有,对系统预定义的结构和常量也是如此,一时之间难以适应那些需要找,哪些需要自己补。
昨晚上从 COM 基本实现的基础上继续前行,结果跟拖放 API 的具体交互上,又遇到了一些无人述及的琐碎但会直接影响功效的地方。由于 IDropSource
接口实在是太简单(除了 COM 的 QI/AddRef/Release 标准三件套,它只有 QueryContinueDrag
和 GiveFeedback
两个方法,前者就是根据情况向拖放控制者反馈拖放操作是否应该继续下去,如果不是是应该按放下处理还是取消处理,后者则是视情况看是自己根据传入的状态来设置更加适合的鼠标指针形状还是由系统代劳),所以这些幽微的地方主要涉及到的是 IDataObject
接口。
在实现一个 COM 接口时,由于设计者最初始为其规划了较高的灵活性,往往有的方法仅仅是为了小概率的情况开的口子,实现时不做任何实际的工作是很常见的做法,返回 E_NOTIMPL
就行了。但是对于 IDataObject
接口中的 GetCanonicalFormatEtc
方法,最佳实践则是返回 DATA_S_SAMEFORMATETC
。在测试过程中(用系统自带的 WordPad 也就是写字板作为拖放目标)发现,如果不返回这个值,那么后续的 QueryGetData
或者 GetData
方法恐怕都不会被触发。此外,涉及到 DAdvise 的三个方法,其缺省实现的返回值最好是 OLE_E_ADVISENOTSUPPORTED
而并非 E_NOTIMPL
。
另一个更吊诡的设施是 EnumFormatEtc
方法及其相关的 IFormatEtc
接口。它们是为拖放数据源支持不止一种数据交换格式的时候,使得拖放潜在的目标进行格式查询提供的,也就是说,从设计角度出发,如果数据仅支持单一格式,则根本无需实现它们,因为 QueryGetData
直接就可以应付得来。但是现实比较骨感,由于 1 仅仅是 n 的特例,所以对于某些通用性的实现来说,它会把 1 这个特例无视掉,强迫它也必须走 n 的通道,也就是说,即使数据格式只有一种,也得老老实实去实现 EnumFormatEtc
方法和 IFormatEtc
接口,否则它不认(360 加固助手使用的 Java 框架就是这个臭毛病)。
当然了,在 Grok 的辅助下,最终还是完整实现了 IFormatEtc
接口,一开始想把它独立为一个单独的文件,很可惜的是在本地 import 的时候总是报错,最后还是合并到了一个单一的文件里。就在把接口调试都通过,调试信息输出显示该调用的接口和方法都被执行到,理应完全正常的时候,偏偏结果就是跟 C/C++ 版本的差一点:拖放进目标窗口去的文件如泥牛入海。又排查许久,才发现竟然是 JNA 组装完的数据结构忘了同步写入到 native 的映射数据区中了。改完后,吼吼,完全正常啦!