<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Windows &#8211; 张三太爷</title>
	<atom:link href="https://www.somedoc.net/category/windows/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.somedoc.net</link>
	<description>看前面，黑洞洞</description>
	<lastBuildDate>Tue, 17 Mar 2026 04:03:16 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://www.somedoc.net/wp-content/uploads/2016/12/cropped-dandycheung-1-32x32.jpg</url>
	<title>Windows &#8211; 张三太爷</title>
	<link>https://www.somedoc.net</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>BIOS/MBR 引导的 Windows 10 就地转换为 UEFI/GPT 引导实操</title>
		<link>https://www.somedoc.net/2026/03/12/bios-mbr-%e5%bc%95%e5%af%bc%e7%9a%84-windows-10-%e5%b0%b1%e5%9c%b0%e8%bd%ac%e6%8d%a2%e4%b8%ba-uefi-gpt-%e5%bc%95%e5%af%bc%e5%ae%9e%e6%93%8d/</link>
					<comments>https://www.somedoc.net/2026/03/12/bios-mbr-%e5%bc%95%e5%af%bc%e7%9a%84-windows-10-%e5%b0%b1%e5%9c%b0%e8%bd%ac%e6%8d%a2%e4%b8%ba-uefi-gpt-%e5%bc%95%e5%af%bc%e5%ae%9e%e6%93%8d/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Thu, 12 Mar 2026 05:11:08 +0000</pubDate>
				<category><![CDATA[Windows]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[电脑]]></category>
		<category><![CDATA[问题解决]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6657</guid>

					<description><![CDATA[有一块祖传的 SATA SSD 上，有一份 Windows  <a href="https://www.somedoc.net/2026/03/12/bios-mbr-%e5%bc%95%e5%af%bc%e7%9a%84-windows-10-%e5%b0%b1%e5%9c%b0%e8%bd%ac%e6%8d%a2%e4%b8%ba-uefi-gpt-%e5%bc%95%e5%af%bc%e5%ae%9e%e6%93%8d/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[
<p>有一块祖传的 SATA SSD 上，有一份 Windows 10，引导模式还停留在 BIOS/MBR 状态。想把它接续着用起来，但是新机器已经放不下 SATA 盘了，而且也不支持传统引导方式了。正好有一块同容量的 NVMe SSD，于是产生把系统克隆过来，改造后再使用的想法。</p>



<p>在 Windows 10 下，把源盘（SATA 盘）和目标盘（NVMe 盘）都用 USB 方式连接好。</p>



<p>第一步是先用 DiskGenius 磁盘克隆，此后源盘抛开，不再参与后续步骤。成功克隆目标盘后的第二步是用 DiskGenius 的分区表类型转换功能把其 MBR 分区表格式转换为 GPT 分区表格式。</p>



<p>第三步有少许精微，其目的是要把 GPT 引导所需的 ESP 分区和 Windows 系统所需的 MSR 分区设定好。一个常见的 GPT 分区布局是，ESP 分区位于磁盘首，而 MSR 稍显随意，相邻地位于系统所在分区之前或者之后（暂不考虑 Windows 的恢复分区）。现实情况是在 MBR 布局下已经存在于系统所在分区之后，因此仅需将其分区类型在 DiskGenius 的编辑分区参数界面中重新调整为 MSR 类型即可。ESP 分区需要单独处理，主要原因是当前磁盘上没有空余空间可以使用，因此需要先用 DiskGenius 调整系统所在分区的大小，在其之前缩出 500MB 即可。此时即可通过 DiskGenius 的“建立ESP/MSR分区”菜单完成 ESP 分区的创建。</p>



<p>接下来还有两步路走。</p>



<p>首先是把刚刚创建好的 ESP 分区上置入引导文件。正好当前运行的主机也是 Windows 10，因此可以在管理员权限的命令行下执行 
			<span id="crayon-69bd30a43cec4018313225" class="crayon-syntax crayon-syntax-inline  crayon-theme-visual-assist crayon-theme-visual-assist-inline crayon-font-droid-sans-mono" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important;"><span class="crayon-pre crayon-code" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;"><span class="crayon-i">bcdboot</span><span class="crayon-h"> </span><span class="crayon-v">C</span><span class="crayon-o">:</span><span class="crayon-sy">\</span><span class="crayon-v">Windows</span><span class="crayon-h"> </span><span class="crayon-o">/</span><span class="crayon-i">s</span><span class="crayon-h"> </span><span class="crayon-v">S</span><span class="crayon-o">:</span><span class="crayon-h"> </span><span class="crayon-o">/</span><span class="crayon-i">f</span><span class="crayon-h"> </span><span class="crayon-v">UEFI</span><span class="crayon-h"> </span><span class="crayon-o">/</span><span class="crayon-i">l</span><span class="crayon-h"> </span><span class="crayon-v">zh</span><span class="crayon-o">-</span><span class="crayon-v">cn</span></span></span> 来完成。其中的 
			<span id="crayon-69bd30a43cecc545126202" class="crayon-syntax crayon-syntax-inline  crayon-theme-visual-assist crayon-theme-visual-assist-inline crayon-font-droid-sans-mono" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important;"><span class="crayon-pre crayon-code" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;"><span class="crayon-v">S</span><span class="crayon-o">:</span></span></span> 是给 ESP 分区分配的盘符，这个工作也可以在 DiskGenius 里做。</p>



<p>第二步则是确认上一步创建出来的 BCD 文件是否可用，最佳工具首推 Bootice。使用它打开 <code>S:\EFI\Microsoft\Boot\BCD</code> 文件，在高级编辑模式下，把左侧栏中的对象树里的必要节点仔细检查一遍（主要是 <code>Windows Boot Manager</code> 节点以及 <code>Application Objects</code> 和 <code>Windows Resume Objects</code> 分支下的节点，把带有 <code>Device</code> 字样的 key（一般是 <code>ApplicationDevice</code> 或者 <code>OSDevice</code>）都要仔细审视一番，把对应的磁盘和分区（要特别注意：<strong>引导分区和系统分区是不一样的！</strong>）选择正确后保存。理论上，这块磁盘应该准备就绪了。</p>



<p>在实践中，上述操作一次性成功。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2026/03/12/bios-mbr-%e5%bc%95%e5%af%bc%e7%9a%84-windows-10-%e5%b0%b1%e5%9c%b0%e8%bd%ac%e6%8d%a2%e4%b8%ba-uefi-gpt-%e5%bc%95%e5%af%bc%e5%ae%9e%e6%93%8d/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>老树孕苞待春回</title>
		<link>https://www.somedoc.net/2026/02/09/%e8%80%81%e6%a0%91%e5%ad%95%e8%8b%9e%e5%be%85%e6%98%a5%e5%9b%9e/</link>
					<comments>https://www.somedoc.net/2026/02/09/%e8%80%81%e6%a0%91%e5%ad%95%e8%8b%9e%e5%be%85%e6%98%a5%e5%9b%9e/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Sun, 08 Feb 2026 16:54:03 +0000</pubDate>
				<category><![CDATA[Windows]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[软件]]></category>
		<category><![CDATA[问题解决]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6651</guid>

					<description><![CDATA[最近 AI 的世界里，agent 作为明星是非常耀眼的存在。 <a href="https://www.somedoc.net/2026/02/09/%e8%80%81%e6%a0%91%e5%ad%95%e8%8b%9e%e5%be%85%e6%98%a5%e5%9b%9e/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[
<p>最近 AI 的世界里，agent 作为明星是非常耀眼的存在。微软公司不甘寂寞，也推出了自己的 agent 相关的贡献。作为一名上了岁数的 Windows 程序员，当再次看到 Microsoft Agent 这个词组时，却不由自主地想到了多年前的另一个同名技术。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Microsoft Agent 是 Microsoft 开发的一项技术，允许用户与应用程序和网页内的动画角色进行交互。它使用文本到语音（TTS）引擎来允许动画角色与最终用户对话。这些动画角色被称为“Microsoft Agent”。从 Windows 98 开始，它就预装在 Windows 中。Microsoft Agent 持续与所有 Windows 版本一起发布，直到 2009 年的 Windows 7，此后 Microsoft 停止提供 Microsoft Agent。Microsoft Agent 的最新版本是 1998 年 10 月 12 日发布的 2.0 版。</p>
</blockquote>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Microsoft Agent 允许软件用户通过 Microsoft Agent 角色编辑器创建角色。微软在原始页面上制作了四个默认角色，包括精灵、梅林、皮迪和罗比。其他一些值得注意的角色是 1997 年至 2003 年 Microsoft Office 套件中出现的角色，包括 Clippy、Dot 或 Office Logo。Windows XP 搜索助手也是 Microsoft Agent，包括 Rover。另一个值得一提的 Microsoft Agent 是 Bonzi，一只紫色大猩猩，它与 BonziBUDDY 捆绑在一起，直到其停产。Bonzi 角色后来因其多个 meme 视频而成为 meme，随后受到了广泛关注，最后又因一名主播直播了 XP 损毁而导致 Bonzi 角色的受欢迎程度上升。</p>
</blockquote>



<p>上述引言来自于一个围绕 Microsoft Agent 技术而创建的<a href="https://tmafe.com/" data-type="link" data-id="https://tmafe.com/">社区</a>页面，比较全面地介绍了 Microsoft Agent 是什么这一命题。</p>



<p>对于国人来讲，最知名的一个 Microsoft Agent 其实应该是瑞星杀毒软件中的小狮子，尽管在后期，由于各种原因，把小狮子的展示技术迁移到了 Adobe Flash。由于小狮子的形象备受人民群众的喜爱，甚至有爱好者自行制作了不包含瑞星杀毒软件本体的独立的小狮子安装包，如果你想体验的话，可以到这里下载：<a href="https://m.crsky.com/mip/soft/94024.html">瑞星小狮子独立版下载 &#8211; 非凡手机软件</a>。</p>



<p>当对技术的追思到二这个份儿上的时候，老夫突然想把非 SWF 版本的原始小狮子找到再看看。这个工作稍微绕了点，因为首先就是要找一个更老版本的瑞星杀毒软件的安装包。当然功夫不负有心人，还是被俺找到一份：<a href="https://mydown.yesky.com/pcsoft/204802.html">瑞星杀毒软件 2004 官方 PC 版免费下载-天极下载</a>。下载解包后看到里面确实存在 RsAgent.acs，这就和记忆对上了。</p>



<p>acs 文件是微软的角色构建工具（可以在<a href="https://agentpedia.tmafe.com/wiki/Microsoft%20Agent%20Character%20Editor" data-type="link" data-id="https://agentpedia.tmafe.com/wiki/Microsoft%20Agent%20Character%20Editor">这里</a>下载）生成的，而制作的原始素材会用到 bmp 位图文件以及 wav 音频文件。如果你想把现成的 acs 文件逆向恢复成素材集，最妥善的方法当然是去了解 acs 的文件格式，尽管微软官方并未公开，好在前人栽过这棵树的，可以参看这个<a href="http://www.lebeausoftware.org/download.aspx?ID=25001fc7-18e9-49a4-90dc-21e8ff46aa1d" data-type="link" data-id="http://www.lebeausoftware.org/download.aspx?ID=25001fc7-18e9-49a4-90dc-21e8ff46aa1d">链接</a>。文档作者也写好了一个现成的工具，在<a href="http://www.lebeausoftware.org/download.aspx?ID=83b03bd5-18dc-467d-9778-e941536897e4">这儿</a>下载。</p>



<p>微软官方文档站里目前还有相关内存，可以在<a href="https://learn.microsoft.com/en-us/windows/win32/lwef/microsoft-agent">这儿</a>参阅；一个简单的入门介绍文档可以参阅<a href="https://github.com/Planet-Source-Code/mahangu-the-complete-guide-to-ms-agent__1-13181">这儿</a>。</p>



<p>有了这些，俺又不免想到，怎么在这新时代里，旧物利用一下儿呢？</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2026/02/09/%e8%80%81%e6%a0%91%e5%ad%95%e8%8b%9e%e5%be%85%e6%98%a5%e5%9b%9e/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>在 Windows 上使用指定的权限级别运行程序</title>
		<link>https://www.somedoc.net/2025/08/20/%e5%9c%a8-windows-%e4%b8%8a%e4%bd%bf%e7%94%a8%e6%8c%87%e5%ae%9a%e7%9a%84%e6%9d%83%e9%99%90%e7%ba%a7%e5%88%ab%e8%bf%90%e8%a1%8c%e7%a8%8b%e5%ba%8f/</link>
					<comments>https://www.somedoc.net/2025/08/20/%e5%9c%a8-windows-%e4%b8%8a%e4%bd%bf%e7%94%a8%e6%8c%87%e5%ae%9a%e7%9a%84%e6%9d%83%e9%99%90%e7%ba%a7%e5%88%ab%e8%bf%90%e8%a1%8c%e7%a8%8b%e5%ba%8f/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Wed, 20 Aug 2025 03:43:45 +0000</pubDate>
				<category><![CDATA[Windows]]></category>
		<category><![CDATA[软件]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6497</guid>

					<description><![CDATA[跟前一篇一样，这也是来自于 V2EX 的帖子，原贴在这里。  <a href="https://www.somedoc.net/2025/08/20/%e5%9c%a8-windows-%e4%b8%8a%e4%bd%bf%e7%94%a8%e6%8c%87%e5%ae%9a%e7%9a%84%e6%9d%83%e9%99%90%e7%ba%a7%e5%88%ab%e8%bf%90%e8%a1%8c%e7%a8%8b%e5%ba%8f/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[
<p>跟前一篇一样，这也是来自于 V2EX 的帖子，原贴在<a href="https://www.v2ex.com/t/522595" data-type="link" data-id="https://www.v2ex.com/t/522595">这里</a>。</p>



<h2 class="wp-block-heading">Windows 上进程的权限</h2>



<p><a href="https://github.com/M2Team/Privexec">Privexec</a> 使用指定的权限级别运行程序。在 Windows 上，程序的权限级别可分为如下：</p>



<ul class="wp-block-list">
<li>TrustedInstaller 可信安装，此权限属于 
			<span id="crayon-69bd30a43d4ed580769061" class="crayon-syntax crayon-syntax-inline  crayon-theme-visual-assist crayon-theme-visual-assist-inline crayon-font-droid-sans-mono" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important;"><span class="crayon-pre crayon-code" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;"><span class="crayon-e">Windows </span><span class="crayon-e">Modules </span><span class="crayon-e">Installer </span><span class="crayon-e">service</span><span class="crayon-h"> </span><span class="crayon-sy">(</span><span class="crayon-v">TrustedInstaller</span><span class="crayon-sy">.</span><span class="crayon-v">exe</span><span class="crayon-sy">)</span></span></span>，常用于 Windows Update 和 Windows 资源保护。</li>



<li>System 本地服务权限。</li>



<li>Administrator 管理员(组)权限</li>



<li>Not Elevated 标准用户权限</li>



<li>Mandatory Integrity Control 低完整性</li>



<li>AppContainer UWP/Store APP 权限</li>
</ul>



<p>在 Windows 上，可能存在一些需求，比如在管理员权限获得 
			<span id="crayon-69bd30a43d4f3063059530" class="crayon-syntax crayon-syntax-inline  crayon-theme-visual-assist crayon-theme-visual-assist-inline crayon-font-droid-sans-mono" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important;"><span class="crayon-pre crayon-code" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;"><span class="crayon-v">System</span><span class="crayon-o">/</span><span class="crayon-v">TrustedInstaller</span></span></span> 权限去做一些事情（一帮不建议这么做）。也有可能以较低权限启动进程，或者实现进程权限从管理员降低到标准受限用户（这个建议）。还有一些，用于对 Windows 做安全分析，比如启动一个 AppContainer 进程，然后研究 Sandbox 逃逸机制。等等。</p>



<h2 class="wp-block-heading">Privexec 的特性</h2>



<p>Privexec 这个工具，自身以管理员权限运行时，可以启动 
			<span id="crayon-69bd30a43d4f6864327684" class="crayon-syntax crayon-syntax-inline  crayon-theme-visual-assist crayon-theme-visual-assist-inline crayon-font-droid-sans-mono" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important;"><span class="crayon-pre crayon-code" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;"><span class="crayon-v">System</span><span class="crayon-o">/</span><span class="crayon-v">TrustedInstaller</span></span></span> 权限进程，以标准用户启动时常常用于启动 
			<span id="crayon-69bd30a43d4f8354726154" class="crayon-syntax crayon-syntax-inline  crayon-theme-visual-assist crayon-theme-visual-assist-inline crayon-font-droid-sans-mono" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important;"><span class="crayon-pre crayon-code" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;"><span class="crayon-v">AppContainer</span></span></span> 进程。</p>



<p>在 Windows 8/8.1/10 中，应用商店，<strong>UWP</strong> 进程都是使用 <a href="https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/appcontainer-isolation">AppContainer 隔离</a>，当你启动 UWP 程序后你可以使用 <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer">Process Explorer</a> 或者 <a href="https://github.com/processhacker/processhacker">Process Hacker</a> 去分析进程的权限信息。</p>



<p>Privexec 能够支持在 以 AppContainer 启动 Win32 进程，并且支持 LPAC, 也就是 Windows 10 新增的 <a href="https://blogs.technet.microsoft.com/iftekhar/2017/08/28/threat-mitigation-in-windows-10/">Less Privileged AppContainer</a></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>App Container has access to resources protected with ALL APPLICATION PACKAGES SID: This SID has read permission on all folders by default. LPAC is a more restricted version of the App Container. it denies access by default for everything. One can access only the secured objects that are granted explicitly to LPAC.</p>
</blockquote>



<p>开启了 LPAC，你如果没有 lpacCOM，COM 对象都无法调用。LPAC 借鉴了 <a href="https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools/blob/master/NtApiDotNet/NtToken.cs#L2583">Google Project Zero sandbox-attacksurface-analysis-tools</a></p>



<p>管理员权限：</p>



<figure class="wp-block-image"><img decoding="async" src="https://github.com/M2Team/Privexec/raw/master/docs/images/admin.png" alt="Admin"/></figure>



<p>启动 
			<span id="crayon-69bd30a43d4fa152543276" class="crayon-syntax crayon-syntax-inline  crayon-theme-visual-assist crayon-theme-visual-assist-inline crayon-font-droid-sans-mono" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important;"><span class="crayon-pre crayon-code" style="font-size: 13px !important; line-height: 16px !important;font-size: 13px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;"><span class="crayon-v">AppContainer</span></span></span> 进程：</p>



<figure class="wp-block-image"><img decoding="async" src="https://github.com/M2Team/Privexec/raw/master/docs/images/appcontainer.png" alt="AppContainer"/></figure>



<p>Privexec 支持命令别名：</p>



<figure class="wp-block-image"><img decoding="async" src="https://github.com/M2Team/Privexec/raw/master/docs/images/alias.png" alt="Alias"/></figure>



<p>命令行版本：</p>



<figure class="wp-block-image"><img decoding="async" src="https://github.com/M2Team/Privexec/raw/master/docs/images/wsudo.png" alt="wsudo"/></figure>



<p>关于在 Windows 上以不同权限启动进程的原理有相应的博客：<a href="https://forcemz.net/windows/2018/12/01/PrivexecNew/">Privexec 杂谈</a></p>



<p>Privexec 主要用在 Windows 10 上，故代码仅支持 Windows 较高版本，建议至少是 1803 或者更高。</p>



<p>下载 CI 构建的 64bit: <a href="https://ci.appveyor.com/project/fcharlie/privexec/build/artifacts">https://ci.appveyor.com/project/fcharlie/privexec/build/artifacts</a></p>



<p>许可证 MIT。</p>



<h2 class="wp-block-heading">类似工具</h2>



<ol class="wp-block-list">
<li>NSudo &#8211; <a href="https://github.com/M2Team/NSudo">A Powerful System Administration Tool</a></li>
</ol>



<p>NSudo 主要擅长权限开启，Privexec 更擅长 AppContainer。NSudo 知名度更广，Privexec 基本只有少数人知道。</p>



<h2 class="wp-block-heading">其他</h2>



<p>Process Hacker 推荐下载 <strong>Nightly</strong> (v2 比较陈旧，有些特性未支持)：<a href="https://wj32.org/processhacker/nightly.php">https://wj32.org/processhacker/nightly.php</a></p>



<p>Privexec 安全论坛上的介绍：<a href="https://www.wilderssecurity.com/threads/privexec-run-the-program-with-the-specified-permission-level.410734/">https://www.wilderssecurity.com/threads/privexec-run-the-program-with-the-specified-permission-level.410734/</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>在评论区，还有人提到了 <a href="http://www.nirsoft.net/utils/advanced_run.html">AdvancedRun</a>，能把权限级别当配置文件保存。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2025/08/20/%e5%9c%a8-windows-%e4%b8%8a%e4%bd%bf%e7%94%a8%e6%8c%87%e5%ae%9a%e7%9a%84%e6%9d%83%e9%99%90%e7%ba%a7%e5%88%ab%e8%bf%90%e8%a1%8c%e7%a8%8b%e5%ba%8f/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Windows 下的 sudo 补丁</title>
		<link>https://www.somedoc.net/2025/08/18/windows-%e4%b8%8b%e7%9a%84-sudo-%e8%a1%a5%e4%b8%81/</link>
					<comments>https://www.somedoc.net/2025/08/18/windows-%e4%b8%8b%e7%9a%84-sudo-%e8%a1%a5%e4%b8%81/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Mon, 18 Aug 2025 06:11:41 +0000</pubDate>
				<category><![CDATA[Windows]]></category>
		<category><![CDATA[问题解决]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6458</guid>

					<description><![CDATA[Windows 下经常会遇到需要管理员权限来执行程序的情况， <a href="https://www.somedoc.net/2025/08/18/windows-%e4%b8%8b%e7%9a%84-sudo-%e8%a1%a5%e4%b8%81/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[
<p>Windows 下经常会遇到需要管理员权限来执行程序的情况，这跟 Linux 下的遭遇其实并无区别。如果在 GUI 里还好，右键菜单中一般都带有使用管理员权限运行的入口。</p>



<p>可在命令行下情况可就不同了，Linux 下的命令行里有个很方便的助手程序 sudo 来协助用户快速实现操作目的，Windows 下虽然也有个 runas，但其直接使用体验就很一言难尽了。</p>



<p>前几天发现有人为此给 Windows 打上了补丁：把 Windows 目录下保存两个文件，一个 <code>sudo.bat</code>，一个 <code>sudo.ps</code>，分别对应传统的命令提示符环境以及后来的 PowerShell 执行环境。</p>



<p><code>sudo.bat</code> 的文件内容为：</p>



<pre class="crayon-plain-tag">[crayon-69bd30a43d70a359020718 inline="true" ]@echo off
powershell -Command "(($arg='/k cd /d '+$pwd+' &amp;&amp; %*') -and (Start-Process cmd -Verb RunAs -ArgumentList $arg))| Out-Null"</pre>[/crayon]



<p>而 <code>sudo.ps1</code> 的文件内容则为：</p>



<pre class="crayon-plain-tag">[crayon-69bd30a43d70d613967068 inline="true" ]If($args){sudo.bat powershell -NoExit -Command "(cd "$pwd");("$args")"}Else{sudo.bat powershell -NoExit -Command "cd "$pwd}</pre>[/crayon]



<p>一眼看去层层叠叠套了好几重，不过确实能用。感兴趣就去试试吧。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2025/08/18/windows-%e4%b8%8b%e7%9a%84-sudo-%e8%a1%a5%e4%b8%81/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>BootCamp 余音</title>
		<link>https://www.somedoc.net/2025/08/18/bootcamp-%e4%bd%99%e9%9f%b3/</link>
					<comments>https://www.somedoc.net/2025/08/18/bootcamp-%e4%bd%99%e9%9f%b3/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Mon, 18 Aug 2025 03:33:27 +0000</pubDate>
				<category><![CDATA[macOS]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[电脑]]></category>
		<category><![CDATA[软件]]></category>
		<category><![CDATA[BootCamp]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6449</guid>

					<description><![CDATA[BootCamp 是当年苹果官方为了支持 Windows 与 <a href="https://www.somedoc.net/2025/08/18/bootcamp-%e4%bd%99%e9%9f%b3/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[
<p>BootCamp 是当年苹果官方为了支持 Windows 与 macOS 共存而提供的一套解决方案。其中包括如何使用工具从硬盘上划分出独立的分区供 Windows 使用，以及在安装 Windows 后为其提供所有适当的驱动程序，以免某些机载设备不能被 Windows 识别而无法工作。</p>



<p>出于各种各样的需要，有时候用户可能想选择性下载某个机型的 BootCamp 驱动合集，而不必非要借助于已经安装成功的 Windows 系统中被苹果植入 BootCamp 工具，于是催发了一些第三方工具的诞生。</p>



<p>有个图形化工具叫 Bombardier 的，可以用来执行这一任务。它会列出可供下载的机型列表，由用户选择后进行下载。它正常发挥了 GUI 程序的优点：直观且操作简单。这是一个开源项目，使用 Swift 语言写就，其官方地址在：<a href="https://github.com/ninxsoft/Bombardier">https://github.com/ninxsoft/Bombardier</a>。当你打开这个地址后，你会发现该项目已经归档，这通常意味着原作者不会再对它进行后续的维护和更新了。</p>



<p>另外还有一个工具叫 Brigadier，官方地址位于 <a href="https://github.com/timsutton/brigadier">https://github.com/timsutton/brigadier</a>。它与前述的 Bombardier 功能类似，不过它是脚本工具，用 Python 写就，对命令行选手更加适合一些。令人略微惊讶的是，它比 Bombardier 出现得更早，但最后更新的时间更晚一些。</p>



<p>如果真要遇到这样的需求，GUI 模式的 Bombardier 应该还是首先尝试的选项，实在有问题的话，不妨再试 Brigadier。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2025/08/18/bootcamp-%e4%bd%99%e9%9f%b3/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>继续用脚本投喂</title>
		<link>https://www.somedoc.net/2025/04/12/%e7%bb%a7%e7%bb%ad%e7%94%a8%e8%84%9a%e6%9c%ac%e6%8a%95%e5%96%82/</link>
					<comments>https://www.somedoc.net/2025/04/12/%e7%bb%a7%e7%bb%ad%e7%94%a8%e8%84%9a%e6%9c%ac%e6%8a%95%e5%96%82/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Sat, 12 Apr 2025 08:30:36 +0000</pubDate>
				<category><![CDATA[Windows]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[问题解决]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6356</guid>

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

					<description><![CDATA[雏形，并非久经考验。有缘看到的也请慎用，不敢保证一定能工作正 <a href="https://www.somedoc.net/2025/02/14/%e5%90%91-360-%e5%8a%a0%e5%9b%ba%e5%8a%a9%e6%89%8b%e6%8a%95%e5%96%82-apk/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>雏形，并非久经考验。有缘看到的也请慎用，不敢保证一定能工作正常。</p><pre class="crayon-plain-tag">#include &lt;windows.h&gt;
#include &lt;windowsx.h&gt;
#include &lt;ole2.h&gt;
#include &lt;oleidl.h&gt;
#include &lt;shlobj.h&gt; // 用于支持 OLE 拖放
#include &lt;tchar.h&gt;

#include &lt;iostream&gt;
#include &lt;iomanip&gt;
#include &lt;string&gt;
#include &lt;vector&gt;

// 将目标窗口激活并置于前台
void BringWindowToFront(HWND hwnd) {
    if (IsIconic(hwnd))
        ShowWindow(hwnd, SW_RESTORE);
    SetForegroundWindow(hwnd);
}

HWND g_hwndTarget = NULL;
POINT g_ptBestPosition;

// 获取目标窗口的最佳鼠标位置（屏幕坐标）（此处为目标窗口的中心）
POINT GetCursorBestPosition(HWND hwnd) {
    RECT rc;
    GetWindowRect(hwnd, &amp;rc);

    std::cout &lt;&lt; "目标窗口：(" &lt;&lt; rc.left &lt;&lt; ", " &lt;&lt; rc.top &lt;&lt; ", " &lt;&lt; rc.right &lt;&lt; ", " &lt;&lt; rc.bottom &lt;&lt; ")" &lt;&lt; std::endl;

    POINT pt;
    pt.x = rc.left + (rc.right - rc.left) / 2;
    pt.y = rc.top + (rc.bottom - rc.top) / 2;

    std::cout &lt;&lt; "中点位置：(" &lt;&lt; pt.x &lt;&lt; ", " &lt;&lt; pt.y &lt;&lt; ")" &lt;&lt; std::endl;

    return pt;
}

int g_iDragQueryTimes = 0;

// 判断当前鼠标位置以及窗口是否符合预期
HRESULT ShouldContinueDrag() {
    if (g_iDragQueryTimes == 0) {
        HWND hwndCapture = GetCapture();
        std::cout &lt;&lt; "ShouldContinueDrag(): 正在捕获鼠标事件的窗口句柄：0x" &lt;&lt; std::hex &lt;&lt; hwndCapture &lt;&lt; std::endl;

        RECT rc;
        GetWindowRect(hwndCapture, &amp;rc);
        std::cout &lt;&lt; "窗口位置：(" &lt;&lt; std::dec &lt;&lt; rc.left &lt;&lt; ", " &lt;&lt; rc.top &lt;&lt; ", " &lt;&lt; rc.right &lt;&lt; ", " &lt;&lt; rc.bottom &lt;&lt; ")" &lt;&lt; std::endl;

        POINT pt = g_ptBestPosition;
        ScreenToClient(hwndCapture, &amp;pt);
        std::cout &lt;&lt; "目标位置（以捕获窗口客户区坐标系）：(" &lt;&lt; pt.x &lt;&lt; ", " &lt;&lt; pt.y &lt;&lt; ")" &lt;&lt; std::endl;
    }

    // 获取鼠标位置
    POINT cursorPos;
    // GetCursorPos(&amp;cursorPos);
    DWORD dwPos = GetMessagePos();
    cursorPos.x = GET_X_LPARAM(dwPos);
    cursorPos.y = GET_Y_LPARAM(dwPos);

    if (cursorPos.x != g_ptBestPosition.x || cursorPos.y != g_ptBestPosition.y) {
        std::cout &lt;&lt; "ShouldContinueDrag(): 鼠标指针 (" &lt;&lt; cursorPos.x &lt;&lt; ", " &lt;&lt;cursorPos.y &lt;&lt; ") 没有在预期位置 ("
            &lt;&lt; g_ptBestPosition.x  &lt;&lt; ", " &lt;&lt; g_ptBestPosition.y &lt;&lt; ")" &lt;&lt; std::endl;

        // 校正鼠标位置，然后重试
        g_iDragQueryTimes++;
        std::cout &lt;&lt; "ShouldContinueDrag(): 第 " &lt;&lt; g_iDragQueryTimes &lt;&lt; " 次修正坐标" &lt;&lt; std::endl;

        // 当前处于 DoDragDrop 内部消息循环中，它虽然仅处理有限种类的事件，但输入事件在其处理范围内。
        // 如果直接 post 鼠标相关的消息的话，担心处理时它会使用 GetMessagePos 来获取消息发生时的鼠
        // 标指针位置，故此处 post 了一个按键释放消息，以试图强迫它读取当前指针的位置，而在 post 之前
        // 已经将鼠标指针位置进行了修正设置。—— 这些代码是预防性代码，尚无实际案例中用到
        SetCursorPos(g_ptBestPosition.x, g_ptBestPosition.y);
        PostMessage(NULL, WM_KEYUP, 0, 0);

        if (g_iDragQueryTimes &gt;= 3) {
            std::cout &lt;&lt; "ShouldContinueDrag(): 多次修正无效，放弃" &lt;&lt; std::endl;
            return DRAGDROP_S_CANCEL;
        }

        std::cout &lt;&lt; "ShouldContinueDrag(): 继续拖动，返回 S_OK" &lt;&lt; std::endl;
        return S_OK;
    }

    HWND hwnd = WindowFromPoint(cursorPos);
    if (hwnd == g_hwndTarget) {
        std::cout &lt;&lt; "ShouldContinueDrag(): 鼠标指针下是预期窗口，返回 DRAGDROP_S_DROP" &lt;&lt; std::endl;
        return DRAGDROP_S_DROP;
    }
    else {
        std::cout &lt;&lt; "ShouldContinueDrag(): 鼠标指针下不是预期窗口，返回 DRAGDROP_S_CANCEL" &lt;&lt; std::endl;
        return DRAGDROP_S_CANCEL;
    }
}

class SimpleEnumFormatEtc : public IEnumFORMATETC {
private:
    ULONG refCount;
    FORMATETC format;
    bool returned;

public:
    SimpleEnumFormatEtc(const FORMATETC&amp; fmt)
        : refCount(1), format(fmt), returned(false)
    {
    }

    // IUnknown 方法
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override {
        if (riid == IID_IUnknown || riid == IID_IEnumFORMATETC) {
            *ppvObject = static_cast&lt;IEnumFORMATETC*&gt;(this);
            AddRef();
            std::cout &lt;&lt; "SimpleEnumFormatEtc.QueryInterface(): 接口查询已结束 (IEnumFORMATETC/IUnknown)" &lt;&lt; std::endl;
            return S_OK;
        }
        *ppvObject = nullptr;
        return E_NOINTERFACE;
    }
    ULONG STDMETHODCALLTYPE AddRef(void) override {
        return ++refCount;
    }
    ULONG STDMETHODCALLTYPE Release(void) override {
        ULONG res = --refCount;
        if (res == 0)
            delete this;
        return res;
    }

    // IEnumFORMATETC 方法
    // Next 方法返回一个支持的 FORMATETC 值（只支持一种格式）
    HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) override {
        if (rgelt == nullptr)
            return E_POINTER;

        if (!returned &amp;&amp; celt &gt; 0) {
            rgelt[0] = format; // 返回我们支持的格式
            if (pceltFetched)
                *pceltFetched = 1;

            returned = true;
            return S_OK;
        }

        // 已经返回过了，后续返回 S_FALSE 表示枚举结束
        if (pceltFetched)
            *pceltFetched = 0;

        return S_FALSE;
    }

    HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override {
        if (!returned &amp;&amp; celt &gt; 0) {
            returned = true;
            return S_OK;
        }
        return S_FALSE;
    }

    HRESULT STDMETHODCALLTYPE Reset(void) override {
        returned = false;
        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnum) override {
        if (ppEnum == nullptr)
            return E_POINTER;

        // 创建一个新实例，将状态重置成初始状态
        SimpleEnumFormatEtc* pNew = new SimpleEnumFormatEtc(format);
        if (pNew) {
            *ppEnum = pNew;
            return S_OK;
        }

        *ppEnum = nullptr;
        return E_OUTOFMEMORY;
    }
};

// 简单的 IDataObject 实现
class SimpleDataObject : public IDataObject {
public:
    SimpleDataObject(const std::wstring&amp; filePath) : refCount(1) {
        formatEtc.cfFormat = CF_HDROP;
        formatEtc.ptd = nullptr;
        formatEtc.dwAspect = DVASPECT_CONTENT;
        formatEtc.lindex = -1;
        formatEtc.tymed = TYMED_HGLOBAL;

        // 设置文件路径
        files.push_back(filePath);
    }

    // 实现 QueryInterface 方法
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override {
        if (riid == IID_IUnknown || riid == IID_IDataObject) {
            *ppvObject = static_cast&lt;IDataObject*&gt;(this);
            AddRef();
            std::cout &lt;&lt; "SimpleDataObject.QueryInterface(): 接口查询已结束 (IDataObject/IUnknown)" &lt;&lt; std::endl;
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    // 实现 AddRef 方法
    ULONG STDMETHODCALLTYPE AddRef() override {
        return ++refCount;
    }

    // 实现 Release 方法
    ULONG STDMETHODCALLTYPE Release() override {
        ULONG count = --refCount;
        if (count == 0) {
            delete this;
        }
        return count;
    }

    // 实现 GetData 方法
    HRESULT STDMETHODCALLTYPE GetData(LPFORMATETC pFormatetc, LPSTGMEDIUM pMedium) override {
        if (!pFormatetc || !pMedium)
            return E_POINTER;

        if (pFormatetc-&gt;cfFormat == CF_HDROP) {
            // 分配一个全局内存块来保存文件路径
            size_t size = sizeof(DROPFILES) + (files[0].size() + 1 + 1) * sizeof(wchar_t);

            pMedium-&gt;hGlobal = GlobalAlloc(GHND, size);
            if (!pMedium-&gt;hGlobal)
                return E_OUTOFMEMORY;

            DROPFILES* dropFiles = (DROPFILES*)GlobalLock(pMedium-&gt;hGlobal);
            dropFiles-&gt;pFiles = sizeof(DROPFILES);
            dropFiles-&gt;fWide = TRUE;

            // 复制文件路径到 DROPFILES 结构后
            wcscpy_s((wchar_t*)dropFiles + sizeof(DROPFILES) / sizeof(wchar_t), files[0].size() + 1, files[0].c_str());

            GlobalUnlock(pMedium-&gt;hGlobal);

            pMedium-&gt;tymed = TYMED_HGLOBAL;
            pMedium-&gt;pUnkForRelease = nullptr;

            std::cout &lt;&lt; "SimpleDataObject.GetData(): HDROP 数据已返回" &lt;&lt; std::endl;

            return S_OK;
        }
        return DV_E_FORMATETC;
    }

    HRESULT STDMETHODCALLTYPE GetDataHere(LPFORMATETC pFormatetc, LPSTGMEDIUM pMedium) override { return E_NOTIMPL; }
    HRESULT STDMETHODCALLTYPE QueryGetData(LPFORMATETC pFormatetc) override {
        if (!pFormatetc)
            return E_POINTER;
        if ((pFormatetc-&gt;cfFormat == CF_HDROP) &amp;&amp; (pFormatetc-&gt;tymed &amp; TYMED_HGLOBAL))
            return S_OK;
        return DV_E_FORMATETC;
    }
    HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(LPFORMATETC pFormatetcIn, LPFORMATETC pFormatetcOut) override { return DATA_S_SAMEFORMATETC; }
    HRESULT STDMETHODCALLTYPE SetData(LPFORMATETC pFormatetc, LPSTGMEDIUM pMedium, BOOL fRelease) override { return E_NOTIMPL; }
    HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC** ppEnumFormatEtc) override {
        if (dwDirection == DATADIR_GET) {
            if (!ppEnumFormatEtc)
                return E_POINTER;

            // 创建一个简单的枚举器实例，返回我们支持的格式
            *ppEnumFormatEtc = new SimpleEnumFormatEtc(formatEtc);
            return (*ppEnumFormatEtc) ? S_OK : E_OUTOFMEMORY;
        }
        return E_NOTIMPL;
    }
    HRESULT STDMETHODCALLTYPE DAdvise(LPFORMATETC pFormatetc, DWORD advf, IAdviseSink* pAdvSink, DWORD* pdwConnection) override { return OLE_E_ADVISENOTSUPPORTED; }
    HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override { return OLE_E_ADVISENOTSUPPORTED; }
    HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA** ppEnumAdvise) override { return OLE_E_ADVISENOTSUPPORTED; }

private:
    ULONG refCount;
    FORMATETC formatEtc;
    std::vector&lt;std::wstring&gt; files;  // 存储文件路径
};

// 简单的 DropSource 实现
class SimpleDropSource : public IDropSource {
public:
    SimpleDropSource() : m_refCount(1) {}

    HRESULT STDMETHODCALLTYPE QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) override {
        return ShouldContinueDrag();
    }

    HRESULT STDMETHODCALLTYPE GiveFeedback(DWORD dwEffect) override {
        return DRAGDROP_S_USEDEFAULTCURSORS;
    }

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override {
        if (riid == IID_IUnknown || riid == IID_IDropSource) {
            *ppvObject = static_cast&lt;IDropSource*&gt;(this);
            AddRef();
            std::cout &lt;&lt; "SimpleDropSource.QueryInterface(): 接口查询已结束 (IDropSource/IUnknown)" &lt;&lt; std::endl;
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef() override {
        return ++m_refCount;
    }

    ULONG STDMETHODCALLTYPE Release() override {
        ULONG refCount = --m_refCount;
        if (refCount == 0) {
            delete this;
        }
        return refCount;
    }

private:
    ULONG m_refCount; // 引用计数
};

// 根据 HRESULT 输出错误码和对应的错误信息
void PrintHResultError(HRESULT hr) {
    LPVOID lpMsgBuf = nullptr;
    DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS;

    // 使用 FormatMessage 获取错误描述
    DWORD nChars = FormatMessage(dwFormatFlags, NULL, hr,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&amp;lpMsgBuf, 0, NULL);

    if (nChars == 0) {
        std::wcout &lt;&lt; L"Error code: 0x" &lt;&lt; std::hex &lt;&lt; hr
            &lt;&lt; L". Unable to retrieve error message." &lt;&lt; std::endl;
    }
    else {
        std::wstring errorMessage(reinterpret_cast&lt;LPTSTR&gt;(lpMsgBuf));
        std::wcout &lt;&lt; L"Error code: 0x" &lt;&lt; std::hex &lt;&lt; hr
            &lt;&lt; L", message: " &lt;&lt; errorMessage &lt;&lt; std::endl;
        LocalFree(lpMsgBuf); // 释放由 FormatMessage 分配的内存
    }
}

// 用于执行拖放操作的函数
void DoDrop(HWND hwndTarget, IDataObject* pDataObject, IDropSource* pDropSource) {
    // 记录当前鼠标的位置
    POINT originalCursorPos;
    GetCursorPos(&amp;originalCursorPos);
    std::cout &lt;&lt; "DoDrop(): 鼠标指针位置已保存" &lt;&lt; std::endl;

    // 将目标窗口带到前台并激活
    BringWindowToFront(hwndTarget);
    std::cout &lt;&lt; "DoDrop(): 窗口已带到前台" &lt;&lt; std::endl;

    // 获取鼠标指针到目标窗口的最佳位置
    g_ptBestPosition = GetCursorBestPosition(hwndTarget);
    std::cout &lt;&lt; "DoDrop(): 鼠标指针的合适位置已更新" &lt;&lt; std::endl;

    // 这里是个技巧性处理。
    // DoDragDrop 是一个模态调用，内部存在一个消息泵，需要向消息队列里预先存放一条消息才能触发它开
    // 始运转。否则它会一直陷于等待。因此才需要以下的 PostMessage 调用。
    // 对于一个拖放操作而言，用鼠标移动消息充当此角色显然非常适合的。但当前本线程内并不存在任何窗口，
    // 所以 hwnd 只能为 NULL。好在该 API 的文档中说明，hwnd 为 NULL 时表示消息的投递目标为所在
    // 线程，恰好可供 DoDragDrop 从线程消息队列中提取，真是天作之合。
    // 不过 DoDragDrop 调用进入模态后，会使用（或创建）剪贴板功能相关的一个隐藏窗口来捕捉鼠标输入，
    // 但在处理输入消息时，使用的鼠标指针坐标并非 lParam 参数，而是从消息结构（MSG）中获取的。因此
    // 以下调用代码中的 lParam 参数理论上是无作用的，它仅能起到触发消息泵开始运转的作用，从而使得
    // IDropSource-&gt;QueryContinueDrag 会被调用，然后在其中修正一次鼠标指针的坐标，以达到将文件
    // 放落到正确窗口的正确位置上的目的。这些看法来自于对以下链接内代码的粗略阅读：
    // https://github.com/tongzx/nt5src/blob/master/Source/XPSP1/NT/com/ole32/ole232/drag/drag.cpp#L1490
    // 
    // 但在 Windows 10 上实测时，该坐标竟然起了作用，使得对鼠标指针位置进行修正的补救措施勿需实施，
    // 也是吊诡。
    PostMessage(NULL, WM_MOUSEMOVE, 0, MAKELPARAM(g_ptBestPosition.x, g_ptBestPosition.y));

    // 调用 DoDragDrop 来执行拖放操作
    DWORD dwEffect;
    HRESULT hr = DoDragDrop(pDataObject, pDropSource, DROPEFFECT_COPY, &amp;dwEffect);
    if (!SUCCEEDED(hr))
        PrintHResultError(hr);

    // 恢复鼠标原位置
    // SetCursorPos(originalCursorPos.x, originalCursorPos.y);
    // std::cout &lt;&lt; "DoDrop(): 鼠标指针位置已恢复" &lt;&lt; std::endl;
}

void DropFileTo360Jiagu() {
    g_hwndTarget = FindWindow(L"SunAwtFrame", L"360加固助手");
    if (!g_hwndTarget) {
        std::cout &lt;&lt; "main(): 360加固助手 (Class: SunAwtFrame) 窗口未找到。" &lt;&lt; std::endl;
        return;
    }

    std::cout &lt;&lt; "main(): 360加固助手 (Class: SunAwtFrame) 窗口已找到。" &lt;&lt; std::endl;

    OleInitialize(NULL);

    // 创建 IDataObject 实例（使用文件路径）
    std::wstring filePath = L"C:\\Dandy\\dists.apk";
    SimpleDataObject* pDataObject = new SimpleDataObject(filePath);

    // 创建 IDropSource 实例
    SimpleDropSource* pDropSource = new SimpleDropSource();

    // 执行拖放操作
    DoDrop(g_hwndTarget, pDataObject, pDropSource);

    // 清理
    pDropSource-&gt;Release();
    pDataObject-&gt;Release();

    OleUninitialize();
}

// 主函数
int main() {
    DropFileTo360Jiagu();
    return 0;
}</pre><p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2025/02/14/%e5%90%91-360-%e5%8a%a0%e5%9b%ba%e5%8a%a9%e6%89%8b%e6%8a%95%e5%96%82-apk/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>让 Grub 把 Windows 系统设为默认</title>
		<link>https://www.somedoc.net/2025/02/14/%e8%ae%a9-grub-%e6%8a%8a-windows-%e7%b3%bb%e7%bb%9f%e8%ae%be%e4%b8%ba%e9%bb%98%e8%ae%a4/</link>
					<comments>https://www.somedoc.net/2025/02/14/%e8%ae%a9-grub-%e6%8a%8a-windows-%e7%b3%bb%e7%bb%9f%e8%ae%be%e4%b8%ba%e9%bb%98%e8%ae%a4/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Fri, 14 Feb 2025 08:04:19 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[问题解决]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6328</guid>

					<description><![CDATA[这个问题也是属于延宕了许久，实在烦不胜烦了，才决定要一劳永逸 <a href="https://www.somedoc.net/2025/02/14/%e8%ae%a9-grub-%e6%8a%8a-windows-%e7%b3%bb%e7%bb%9f%e8%ae%be%e4%b8%ba%e9%bb%98%e8%ae%a4/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>这个问题也是属于延宕了许久，实在烦不胜烦了，才决定要一劳永逸解决的，之前一直是手动修改 <code>/boot/grub/grub.cfg</code>，但比较烦人的地方在于，不管是 Grub 自己升级，还是有涉及到内核的其它升级，都会在完成后自动执行 <code>update-grub</code> 从而使得手动修改的部分被清空。而如果放任不改的话，Windows 又跳出来作乱，自动更新后重启系统，就会直接从 Grub 进入到 Ubuntu。</p>
<p>问了问 DeepSeek，给出的例子老是出问题，三五个回合以后，它又回到起初的回答上了。最后还是自己修修改改搞定了。</p>
<p>到 <code>/etc/grub.d/</code> 目录下，创建一个新的 Grub 钩子脚本，三太爷的命名为 <code>31_set_default_os</code>，内容如下：</p><pre class="crayon-plain-tag">#!/bin/sh

set_windows_as_default() {
    local windows_entry

    windows_entry=$(grep -i 'windows' /boot/grub/grub.cfg | grep -oP 'menuentry \K[\x27\x32].*?[\x27\x32]' | tr -d "'\"")
    if [ -n "$windows_entry" ]; then
cat &lt;&lt; EOF
set default="$windows_entry"
EOF
        echo "Windows is set as default OS." &gt;&amp;2
        # echo "Windows found." &gt;&amp;2
    else
        echo "Windows not found." &gt;&amp;2
    fi
}

set_windows_as_default</pre><p><code>\x27</code> 和 <code>\x32</code> 分别是单双引号字符，写成转义的形式是为了不影响命令行的识别和传递。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2025/02/14/%e8%ae%a9-grub-%e6%8a%8a-windows-%e7%b3%bb%e7%bb%9f%e8%ae%be%e4%b8%ba%e9%bb%98%e8%ae%a4/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>用程序来模拟文件拖放（二）</title>
		<link>https://www.somedoc.net/2025/02/11/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be%ef%bc%88%e4%ba%8c%ef%bc%89/</link>
					<comments>https://www.somedoc.net/2025/02/11/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be%ef%bc%88%e4%ba%8c%ef%bc%89/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Tue, 11 Feb 2025 05:01:43 +0000</pubDate>
				<category><![CDATA[Windows]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[问题解决]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6325</guid>

					<description><![CDATA[昨天写《用程序来模拟文件拖放》的时候，程序刚刚运行通过，测试 <a href="https://www.somedoc.net/2025/02/11/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be%ef%bc%88%e4%ba%8c%ef%bc%89/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>昨天写《<a href="https://www.somedoc.net/2025/02/10/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be/">用程序来模拟文件拖放</a>》的时候，程序刚刚运行通过，测试用例是向 WordPad 也即写字板里面扔一个文件。今天把程序改了改，向真实案例继续靠近，往 360 加固助手里扔 apk 包，不出意外地失败了。我就说嘛，老天爷怎么会如此轻易放过老夫。</p>
<p>不过这个情况并不棘手。因为截至目前，已经证明拖放的支持基础是很坚实的。为什么 360 加固助手不能像预期那样正确接收投喂，几乎可以百分百确定是格式枚举部分的支持没有完成而导致的。还好有前人在这块儿做出过泽惠吾等的努力，实现过一个开箱即用的格式枚举辅助类，略作修改后投入使用，再测试，就一马平川了。</p>
<p>涉及到对上一篇文章中代码的改动有两处，一是加上 <code>#include "enumfmt.h"</code>，二是对 <code>EnumFormatEtc</code> 方法进行填充，在原有的一条返回语句前增加以下三行：</p><pre class="crayon-plain-tag">FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
        if(dwDirection == DATADIR_GET)
            return CEnumFormatEtc::Create(1, &amp;fmt, ppEnumFormatEtc);</pre><p>此处附上 <code>enumfmt.h</code> 头文件的内容如下：</p><pre class="crayon-plain-tag">#include &lt;windows.h&gt;

class CEnumFormatEtc : public IEnumFORMATETC
{
    static HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC *pFormatEtc, IEnumFORMATETC **ppEnumFormatEtc)
    {
        if(nNumFormats == 0 || pFormatEtc == 0 || ppEnumFormatEtc == 0)
            return E_INVALIDARG;

        *ppEnumFormatEtc = new CEnumFormatEtc(pFormatEtc, nNumFormats);
        return (*ppEnumFormatEtc) ? S_OK : E_OUTOFMEMORY;
    }

    //	Helper function to perform a "deep" copy of a FORMATETC
    static void DeepCopyFormatEtc(FORMATETC *dest, FORMATETC *source)
    {
        // copy the source FORMATETC into dest
        *dest = *source;
	
        if(source-&gt;ptd)
        {
            // allocate memory for the DVTARGETDEVICE if necessary
            dest-&gt;ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));

            // copy the contents of the source DVTARGETDEVICE into dest-&gt;ptd
            *(dest-&gt;ptd) = *(source-&gt;ptd);
        }
    }

public:
    //	"Drop-in" replacement for SHCreateStdEnumFmtEtc. Called by CDataObject::EnumFormatEtc
    static HRESULT Create(UINT nNumFormats, FORMATETC *pFormatEtc, IEnumFORMATETC **ppEnumFormatEtc)
    {
        return  CreateEnumFormatEtc(nNumFormats, pFormatEtc, ppEnumFormatEtc);
    }

public:
	//
	// IUnknown members
	//
	HRESULT __stdcall  QueryInterface (REFIID iid, void ** ppvObject)
    {
        // check to see what interface has been requested
        if(iid == IID_IEnumFORMATETC || iid == IID_IUnknown)
        {
            AddRef();
            *ppvObject = this;
            return S_OK;
        }

        *ppvObject = 0;
        return E_NOINTERFACE;
    }
	ULONG	__stdcall  AddRef (void)
    {
        // increment object reference count
        return InterlockedIncrement(&amp;m_lRefCount);
    }
	ULONG	__stdcall  Release (void)
    {
        // decrement object reference count
        LONG count = InterlockedDecrement(&amp;m_lRefCount);
        if(count == 0)
        {
            delete this;
            return 0;
        }

        return count;
    }

	//
	// IEnumFormatEtc members
	//
    //	If the returned FORMATETC structure contains a non-null "ptd" member, then
    //  the caller must free this using CoTaskMemFree (stated in the COM documentation)
    //
	HRESULT __stdcall  Next  (ULONG celt, FORMATETC * rgelt, ULONG * pceltFetched)
    {
        // validate arguments
        if(celt == 0 || rgelt == 0)
            return E_INVALIDARG;

        // copy FORMATETC structures into caller's buffer
        ULONG copied  = 0;
        while(m_nIndex &lt; m_nNumFormats &amp;&amp; copied &lt; celt)
        {
            DeepCopyFormatEtc(&amp;rgelt[copied], &amp;m_pFormatEtc[m_nIndex]);
            copied++;
            m_nIndex++;
        }

        // store result
        if(pceltFetched != 0) 
            *pceltFetched = copied;

        // did we copy all that was requested?
        return (copied == celt) ? S_OK : S_FALSE;
    }
	HRESULT __stdcall  Skip  (ULONG celt)
    {
        m_nIndex += celt;
        return (m_nIndex &lt;= m_nNumFormats) ? S_OK : S_FALSE;
    }
	HRESULT __stdcall  Reset (void)
    {
        m_nIndex = 0;
        return S_OK;
    }
	HRESULT __stdcall  Clone (IEnumFORMATETC ** ppEnumFormatEtc)
    {
        // make a duplicate enumerator
        HRESULT hResult = CreateEnumFormatEtc(m_nNumFormats, m_pFormatEtc, ppEnumFormatEtc);
        if(hResult == S_OK)
        {
            // manually set the index state
            ((CEnumFormatEtc *) *ppEnumFormatEtc)-&gt;m_nIndex = m_nIndex;
        }

        return hResult;
    }

	//
	// Construction / Destruction
	//
	CEnumFormatEtc(FORMATETC *pFormatEtc, int nNumFormats)
    {
        m_lRefCount   = 1;
        m_nIndex      = 0;
        m_nNumFormats = nNumFormats;
        m_pFormatEtc  = new FORMATETC[nNumFormats];
	
        // copy the FORMATETC structures
        for(int i = 0; i &lt; nNumFormats; i++)
        {
            DeepCopyFormatEtc(&amp;m_pFormatEtc[i], &amp;pFormatEtc[i]);
        }
    }

	~CEnumFormatEtc()
    {
        if(m_pFormatEtc)
        {
            for(ULONG i = 0; i &lt; m_nNumFormats; i++)
            {
                if(m_pFormatEtc[i].ptd)
                    CoTaskMemFree(m_pFormatEtc[i].ptd);
            }

            delete[] m_pFormatEtc;
        }
    }

private:
	LONG		m_lRefCount;		// Reference count for this COM interface
	ULONG		m_nIndex;			// current enumerator index
	ULONG		m_nNumFormats;		// number of FORMATETC members
	FORMATETC * m_pFormatEtc;		// array of FORMATETC objects
};</pre><p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2025/02/11/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be%ef%bc%88%e4%ba%8c%ef%bc%89/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>用程序来模拟文件拖放</title>
		<link>https://www.somedoc.net/2025/02/10/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be/</link>
					<comments>https://www.somedoc.net/2025/02/10/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be/#comments</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Mon, 10 Feb 2025 01:52:15 +0000</pubDate>
				<category><![CDATA[Windows]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[问题解决]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=6315</guid>

					<description><![CDATA[这个课题属于没苦硬吃，实际价值很难说有多大，但既然奋战一番， <a href="https://www.somedoc.net/2025/02/10/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>这个课题属于没苦硬吃，实际价值很难说有多大，但既然奋战一番，记录一下也好。</p>
<p>一开始的直觉就是使用 <code>WM_DROPFILES</code> 消息，没什么其他原因，简单嘛。实现完成后测试，当目标窗口是记事本的时候工作正常，但是换成三太爷想要操作的 360 加固助手就崴泥。稍微观察了一下，该程序的界面是使用 Java 技术栈实现的，而且在从资源管理器向其中投放文件时，也根本收不到 <code>WM_DROPFILES</code> 消息。加之还可以观察到，尽管由于 Java 的原因，主界面在 Windows 世界里只体现为唯一的一个 Window（因为其中的子窗口或者控件是 Java 自己管理的非标准 Windows 窗口实现），但是接收放下文件的区域又并非整个 Window 所占用的区域，可以推测出，接收拖放应该是使用了 OLE 的 drag and drop。</p>
<p>攻坚之旅无须多言，共三个阶段：一开始时尝试用 Groovy 直接搞定，未果；接下来和 ChatGPT 合作写 Windows SDK 程序，程序写出来了，效果没达到；最后跟 DeepSeek 合作写 Windows SDK 程序，达到了预期效果。在实践中发现，ChatGPT 和 DeepSeek 写程序的优缺点各有千秋，在反复提示、反复修改的输出过程中，ChatGPT 输出较为简洁，会减少大量的与上一次回答中相同的部分，而 DeepSeek 则动不动全量输出，不利于前后翻阅浏览。而且，在这个小程序的编制过程中，ChatGPT 在关键问题上（很可能）犯了大错误，以至于没能实现目标，而 DeepSeek 则表现为在很多地方的输出都有“毛刺”（例如调用 API 时，已经默认全局设定为 Unicode 宽字符情境，却使用了不带明确的 <code>W</code> 后缀的 Unicode API 版本，以至于编译时又被默认替换成了带 <code>A</code> 后缀的 ANSI 版本），只不过无伤大雅，手动纠正过来后就好了，关键地方没有掉链子。这里只是如实记录一下过程，并非要论定二者的优劣，毕竟这中间还存在老夫的思路切换对它们造成的影响。</p>
<p>三个源文件的内容附上。首先是 <code>resource.h</code>，</p><pre class="crayon-plain-tag">#define IDD_DIALOG1 101
#define IDC_BUTTON_SIMULATE_DROP 1001
#define IDC_BUTTON_CLOSE 1002</pre><p>接着是 <code>resource.rc</code>，</p><pre class="crayon-plain-tag">#pragma code_page(65001)  // UTF-8 代码页

#include "resource.h"
#include &lt;windows.h&gt;

IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "文件拖放模拟"
FONT 9, "Segoe UI", 0, 0, 0x0
BEGIN
    DEFPUSHBUTTON "模拟文件拖放", IDC_BUTTON_SIMULATE_DROP, 20, 20, 160, 30
    PUSHBUTTON "关闭", IDC_BUTTON_CLOSE, 20, 60, 160, 30
END</pre><p>最后是 <code>main.cpp</code>，</p><pre class="crayon-plain-tag">#include &lt;windows.h&gt;
#include &lt;shlobj.h&gt;
#include &lt;string&gt;
#include &lt;vector&gt;
#include &lt;sstream&gt;

#include "resource.h"

#pragma comment(lib, "ole32.lib")

bool IsCursorInSameProcess() {
    POINT pt;
    if (GetCursorPos(&amp;pt)) {
        HWND hwnd = WindowFromPoint(pt);
        if (hwnd) {
            DWORD pid;
            GetWindowThreadProcessId(hwnd, &amp;pid);

            DWORD currentPid = GetCurrentProcessId();
            return pid == currentPid;
        }
    }
    return false;
}

class CDataObject : public IDataObject {
public:
    CDataObject(const std::vector&lt;std::wstring&gt;&amp; filePaths) : m_refCount(1) {
        FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
        STGMEDIUM stg = { TYMED_HGLOBAL, { 0 }, NULL };

        size_t totalSize = sizeof(DROPFILES);
        for (const auto&amp; path : filePaths) {
            totalSize += (path.size() + 1) * sizeof(wchar_t);
        }
        totalSize += sizeof(wchar_t);

        stg.hGlobal = GlobalAlloc(GHND, totalSize);
        if (stg.hGlobal) {
            DROPFILES* pDropFiles = (DROPFILES*)GlobalLock(stg.hGlobal);
            pDropFiles-&gt;pFiles = sizeof(DROPFILES);
            pDropFiles-&gt;fWide = TRUE;

            wchar_t* pData = (wchar_t*)(pDropFiles + 1);
            for (const auto&amp; path : filePaths) {
                wcscpy(pData, path.c_str());
                pData += path.size() + 1;
            }
            *pData = L'\0';

            GlobalUnlock(stg.hGlobal);
        }

        m_stg = stg;
        m_fmt = fmt;
    }

    ~CDataObject() {
        if (m_stg.hGlobal) {
            ReleaseStgMedium(&amp;m_stg);
        }
    }

    STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override {
        if (riid == IID_IDataObject || riid == IID_IUnknown) {
            *ppvObject = this;
            AddRef();
            return S_OK;
        }
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }

    STDMETHODIMP_(ULONG) AddRef() override {
        return InterlockedIncrement(&amp;m_refCount);
    }

    STDMETHODIMP_(ULONG) Release() override {
        ULONG refCount = InterlockedDecrement(&amp;m_refCount);
        if (refCount == 0) {
            delete this;
        }
        return refCount;
    }

    STDMETHODIMP GetData(FORMATETC* pFormatetc, STGMEDIUM* pMedium) override {
        if (pFormatetc-&gt;cfFormat == m_fmt.cfFormat &amp;&amp;
            pFormatetc-&gt;tymed &amp; m_fmt.tymed &amp;&amp;
            pFormatetc-&gt;dwAspect == m_fmt.dwAspect) {
            pMedium-&gt;tymed = TYMED_HGLOBAL;
            pMedium-&gt;hGlobal = OleDuplicateData(m_stg.hGlobal, m_fmt.cfFormat, 0);
            return S_OK;
        }
        return DV_E_FORMATETC;
    }

    STDMETHODIMP GetDataHere(FORMATETC* pFormatetc, STGMEDIUM* pMedium) override {
        return E_NOTIMPL;
    }

    STDMETHODIMP QueryGetData(FORMATETC* pFormatetc) override {
        return (pFormatetc-&gt;cfFormat == m_fmt.cfFormat &amp;&amp;
                pFormatetc-&gt;tymed &amp; m_fmt.tymed &amp;&amp;
                pFormatetc-&gt;dwAspect == m_fmt.dwAspect) ? S_OK : DV_E_FORMATETC;
    }

    STDMETHODIMP GetCanonicalFormatEtc(FORMATETC* pFormatetcIn, FORMATETC* pFormatetcOut) override {
        return DATA_S_SAMEFORMATETC;
    }

    STDMETHODIMP SetData(FORMATETC* pFormatetc, STGMEDIUM* pMedium, BOOL fRelease) override {
        return E_NOTIMPL;
    }

    STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC** ppEnumFormatEtc) override {
        return E_NOTIMPL;
    }

    STDMETHODIMP DAdvise(FORMATETC* pFormatetc, DWORD advf, IAdviseSink* pAdvSink, DWORD* pdwConnection) override {
        return OLE_E_ADVISENOTSUPPORTED;
    }

    STDMETHODIMP DUnadvise(DWORD dwConnection) override {
        return OLE_E_ADVISENOTSUPPORTED;
    }

    STDMETHODIMP EnumDAdvise(IEnumSTATDATA** ppEnumAdvise) override {
        return OLE_E_ADVISENOTSUPPORTED;
    }

private:
    ULONG m_refCount;
    FORMATETC m_fmt;
    STGMEDIUM m_stg;
};

class CDropSource : public IDropSource {
public:
    CDropSource() : m_refCount(1) {}

    STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override {
        if (riid == IID_IDropSource || riid == IID_IUnknown) {
            *ppvObject = this;
            AddRef();
            return S_OK;
        }
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }

    STDMETHODIMP_(ULONG) AddRef() override {
        return InterlockedIncrement(&amp;m_refCount);
    }

    STDMETHODIMP_(ULONG) Release() override {
        ULONG refCount = InterlockedDecrement(&amp;m_refCount);
        if (refCount == 0) {
            delete this;
        }
        return refCount;
    }

    STDMETHODIMP QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) override {
        if (fEscapePressed) {
            return DRAGDROP_S_CANCEL;
        }
        if (!IsCursorInSameProcess() &amp;&amp; !(grfKeyState &amp; MK_LBUTTON)) {
            return DRAGDROP_S_DROP;
        }
        return S_OK;
    }

    STDMETHODIMP GiveFeedback(DWORD dwEffect) override {
        return DRAGDROP_S_USEDEFAULTCURSORS;
    }

private:
    ULONG m_refCount;
};

std::wstring GetErrorMessage(HRESULT hr) {
    LPWSTR pszMessage = nullptr;
    DWORD dwLength = FormatMessageW(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        hr,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPWSTR)&amp;pszMessage,
        0,
        NULL
    );

    std::wstring message;
    if (dwLength &gt; 0) {
        message = pszMessage;
        LocalFree(pszMessage);
    } else {
        message = L"未知错误";
    }

    return message;
}

void SimulateFileDrop(const std::vector&lt;std::wstring&gt;&amp; filePaths) {
    if (SUCCEEDED(OleInitialize(NULL))) {
        CDataObject* pDataObject = new CDataObject(filePaths);
        CDropSource* pDropSource = new CDropSource();

        DWORD dwEffect;
        HRESULT hr = DoDragDrop(pDataObject, pDropSource, DROPEFFECT_COPY, &amp;dwEffect);
        if (FAILED(hr)) {
            std::wstringstream ss;
            ss &lt;&lt; L"拖放操作失败: " &lt;&lt; GetErrorMessage(hr);
            MessageBoxW(NULL, ss.str().c_str(), L"错误", MB_ICONERROR);
        } else if (hr == DRAGDROP_S_DROP) {
            MessageBoxW(NULL, L"拖放操作成功！", L"提示", MB_OK);
        } else if (hr == DRAGDROP_S_CANCEL) {
            MessageBoxW(NULL, L"拖放操作被取消。", L"提示", MB_OK);
        }

        pDataObject-&gt;Release();
        pDropSource-&gt;Release();

        OleUninitialize();
    }
}

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_INITDIALOG:
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_BUTTON_SIMULATE_DROP: {
                    std::vector&lt;std::wstring&gt; filePaths = { L"C:\\Dandy\\开发 JD.txt" };
                    SimulateFileDrop(filePaths);
                    MessageBoxW(hwndDlg, L"文件拖放模拟完成！", L"提示", MB_OK);
                    break;
                }

                case IDC_BUTTON_CLOSE:
                    EndDialog(hwndDlg, 0);
                    break;
            }
            return TRUE;

        case WM_CLOSE:
            EndDialog(hwndDlg, 0);
            return TRUE;
    }
    return FALSE;
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) {
    DialogBoxParamW(hInstance, (LPCWSTR)MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc, 0);
    return 0;
}</pre><p>最后是构建命令，</p><pre class="crayon-plain-tag">rc resource.rc
cl /EHsc /Fe:fdsim.exe /source-charset:utf-8 main.cpp resource.res user32.lib gdi32.lib</pre><p>仅此而已。</p>
<p>后记：当然了，上面最后可以说“仅此而已”这四个字的前提是我们的需求确实很简单，事实上 OLE 拖放相关的知识非常繁杂，MFC 封装了许多以简化使用，而如果你恰好是一个 SDK 级别的开发员的话，就要痛苦不少。有一个前辈程序员在很多年前写过一个助力 SDK 开发者开发拖放相关功能的库，文章在<a href="https://anton.maurovic.com/posts/win32-api-approach-to-windows-drag-and-drop/">这儿</a>，代码在<a href="https://sourceforge.net/projects/win32cdnd/files/">这儿</a>。CSDN 上有个博主，看上去应该是以 Delphi 作为主力技术的，<a href="https://blog.csdn.net/b2020b?type=blog">他的博客转载了一系列 OLE 拖放相关的博文</a>。</p>
<p>思考：在 OLE 的世界里，信息庞杂但多浮于表层，有很多都是人云亦云的传抄。在和 ChatGPT 的合作当中，由于 IDropSource 的 GetData 方法不会被调用，程序被挂起，在对接口进行近距离观测后，感觉很可能是因为没有实现 <code>EnumFormatEtc</code> 方法而导致的。推测的依据是，如果没有实现这个方法，拖放接收端就很可能因无法获知有待接收的数据的格式，从而无法反馈拖放源是否可以放落。转而去增加这个方法的实现，结果又引入了一大坨与 <code>IEnumFORMATETC</code> 接口关联的实现和周边。最后到头来再看 DeepSeek 的实现，会发现不实现 <code>EnumFormatEtc</code> 方法其实也没有问题。那么，ChatGPT 的版本为什么挂起了呢，目前的推测是，老夫当时实现的是一个控制台程序，因为没有创建自己的窗口（以及消息循环），所以进入 <code>DoDragDrop</code> 模态后，它的内部消息循环接收不到任何事件，就僵住了。而和 DeepSeek 合作时，起手就有意改变了程序的类型，实现了一个对话框，进展就很顺利（这也是为什么前面并没有敢妄下结论，说它们两者存在着显著的优劣差别）。当然，这个推测出的结论还有待验证。想说明的是，即便在前面给出的那个系列文章里，其中也写道，“庆幸的是，为了简化 OLE 拖放，我们仅仅需要实现 <code>GetData</code>、<code>QueryGetData</code> 和 <code>EnumFormatEtc</code>”，而我们已经用实际代码证实了，连 <code>EnumFormatEtc</code> 也不是必须的。而这些必须经过实践才能得来的关键知识，网络上其实并不很多。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2025/02/10/%e7%94%a8%e7%a8%8b%e5%ba%8f%e6%9d%a5%e6%a8%a1%e6%8b%9f%e6%96%87%e4%bb%b6%e6%8b%96%e6%94%be/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
