<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Developer Tooling | The .NET Blog</title><link>https://thedotnetblog.com/zh/tags/developer-tooling/</link><description>Articles, tutorials and insights from the .NET community.</description><generator>Hugo</generator><language>zh</language><managingEditor>@thedotnetblog (The .NET Blog)</managingEditor><webMaster>@thedotnetblog</webMaster><lastBuildDate>Tue, 21 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://thedotnetblog.com/zh/tags/developer-tooling/index.xml" rel="self" type="application/rss+xml"/><item><title>azd + GitHub Copilot：AI 驱动的项目设置和智能错误修复</title><link>https://thedotnetblog.com/zh/posts/emiliano-montesdeoca/azd-copilot-integration-ai-setup-troubleshooting/</link><pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate><author>Emiliano Montesdeoca</author><guid>https://thedotnetblog.com/zh/posts/emiliano-montesdeoca/azd-copilot-integration-ai-setup-troubleshooting/</guid><description>Azure Developer CLI 现已与 GitHub Copilot 集成，可以生成项目基础架构并修复部署错误——无需离开终端。</description><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;本文为自动翻译。如需阅读英文原文，请&lt;a href="https://thedotnetblog.com/zh/posts/emiliano-montesdeoca/azd-copilot-integration-ai-setup-troubleshooting/"&gt;点击这里&lt;/a&gt;。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你是否有过这样的经历：想把现有应用部署到 Azure，却盯着空白的 &lt;code&gt;azure.yaml&lt;/code&gt; 发呆，想不起来 Express API 到底该用 Container Apps 还是 App Service？这种时刻即将成为历史。&lt;/p&gt;
&lt;p&gt;Azure Developer CLI（&lt;code&gt;azd&lt;/code&gt;）现已与 GitHub Copilot 以两种实用的方式集成：&lt;code&gt;azd init&lt;/code&gt; 时的 AI 辅助项目脚手架，以及部署失败时的智能错误排查。两项功能都完全在终端内运行——正是我想要的方式。&lt;/p&gt;
&lt;h2 id="在-azd-init-中使用-copilot-设置"&gt;在 azd init 中使用 Copilot 设置&lt;/h2&gt;
&lt;p&gt;运行 &lt;code&gt;azd init&lt;/code&gt; 时，现在会出现&amp;quot;Set up with GitHub Copilot (Preview)&amp;ldquo;选项。选择它，Copilot 会分析你的代码库，根据实际代码生成 &lt;code&gt;azure.yaml&lt;/code&gt;、基础架构模板和 Bicep 模块。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;azd init
# 选择：&amp;#34;Set up with GitHub Copilot (Preview)&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;前提条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;azd 1.23.11 或更高版本&lt;/strong&gt; — 用 &lt;code&gt;azd version&lt;/code&gt; 检查，或用 &lt;code&gt;azd update&lt;/code&gt; 更新&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有效的 GitHub Copilot 订阅&lt;/strong&gt;（Individual、Business 或 Enterprise）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub CLI（&lt;code&gt;gh&lt;/code&gt;）&lt;/strong&gt; — 必要时 &lt;code&gt;azd&lt;/code&gt; 会提示登录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我觉得真正实用的是它能双向工作。从零开始构建？Copilot 从一开始就帮你配置正确的 Azure 服务。有现有应用想部署？把 Copilot 指向它，无需重构代码即可生成配置。&lt;/p&gt;
&lt;h3 id="它实际做什么"&gt;它实际做什么&lt;/h3&gt;
&lt;p&gt;假设你有一个 Node.js Express API，依赖 PostgreSQL。不必手动在 Container Apps 和 App Service 之间抉择，也不必从零编写 Bicep，Copilot 检测到你的技术栈后会生成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包含正确 &lt;code&gt;language&lt;/code&gt;、&lt;code&gt;host&lt;/code&gt; 和 &lt;code&gt;build&lt;/code&gt; 设置的 &lt;code&gt;azure.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Azure Container Apps 的 Bicep 模块&lt;/li&gt;
&lt;li&gt;Azure Database for PostgreSQL 的 Bicep 模块&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;并在做任何更改前运行预检——验证 git 工作目录是否干净，提前请求 MCP 服务器工具的授权。一切都在你知情的情况下进行。&lt;/p&gt;
&lt;h2 id="copilot-驱动的错误排查"&gt;Copilot 驱动的错误排查&lt;/h2&gt;
&lt;p&gt;部署错误无可避免。缺少参数、权限问题、SKU 可用性问题——而错误信息很少告诉你真正需要知道的那一件事：&lt;em&gt;如何修复&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;没有 Copilot 时的循环：复制错误 → 搜索文档 → 读三篇不相关的 Stack Overflow 回答 → 运行一些 &lt;code&gt;az&lt;/code&gt; CLI 命令 → 重试并祈祷。有了集成在 &lt;code&gt;azd&lt;/code&gt; 中的 Copilot，这个循环消失了。任何 &lt;code&gt;azd&lt;/code&gt; 命令失败时，它会立即提供四个选项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Explain&lt;/strong&gt; — 用通俗语言解释出了什么问题&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guidance&lt;/strong&gt; — 逐步修复指导&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diagnose and Guide&lt;/strong&gt; — 完整分析 + Copilot 应用修复（经你批准）+ 可选重试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skip&lt;/strong&gt; — 自己处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关键在于：Copilot 已经了解你的项目、失败的命令和错误详情。它的建议针对&lt;em&gt;你的具体情况&lt;/em&gt;，而非泛泛的文档。&lt;/p&gt;
&lt;h3 id="设置默认行为"&gt;设置默认行为&lt;/h3&gt;
&lt;p&gt;如果你总是选择同一个选项，可以跳过交互式提示：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;azd config set copilot.errorHandling.category troubleshoot
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可选值：&lt;code&gt;explain&lt;/code&gt;、&lt;code&gt;guidance&lt;/code&gt;、&lt;code&gt;troubleshoot&lt;/code&gt;、&lt;code&gt;fix&lt;/code&gt;、&lt;code&gt;skip&lt;/code&gt;。还可以启用自动修复和重试：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;azd config set copilot.errorHandling.fix allow
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;随时重置为交互模式：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;azd config unset copilot.errorHandling.category
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;这正是真正有价值的 Copilot 集成。运行 &lt;code&gt;azd update&lt;/code&gt; 获取最新版本，在下一个项目中试试 &lt;code&gt;azd init&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;阅读&lt;a href="https://devblogs.microsoft.com/azure-sdk/azd-copilot-integration/"&gt;原文公告&lt;/a&gt;。&lt;/p&gt;</content:encoded></item><item><title>用 C# 和 .NET Native AOT 编写 Node.js 原生插件</title><link>https://thedotnetblog.com/zh/posts/emiliano-montesdeoca/nodejs-addons-csharp-native-aot/</link><pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate><author>Emiliano Montesdeoca</author><guid>https://thedotnetblog.com/zh/posts/emiliano-montesdeoca/nodejs-addons-csharp-native-aot/</guid><description>C# Dev Kit 团队用 .NET Native AOT 替换了 C++ 编写的 Node.js 插件——结果更简洁、更安全，只需要 .NET SDK。</description><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;本文为自动翻译。如需阅读英文原文，请&lt;a href="https://thedotnetblog.com/zh/posts/emiliano-montesdeoca/nodejs-addons-csharp-native-aot/"&gt;点击这里&lt;/a&gt;。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是一个我很喜欢的场景：一个开发 .NET 工具的团队，用 C++ 编写了原生 Node.js 插件，通过 &lt;code&gt;node-gyp&lt;/code&gt; 编译。它能运行。但每个开发者的机器上都需要安装 Python——而且是旧版本的 Python——仅仅为了构建一个团队里没有人会直接接触的包。&lt;/p&gt;
&lt;p&gt;于是他们提出了一个很合理的问题：我们已经装了 .NET SDK，为什么还要写 C++？&lt;/p&gt;
&lt;p&gt;答案是 Native AOT，结果相当优雅。&lt;/p&gt;
&lt;h2 id="基本思路"&gt;基本思路&lt;/h2&gt;
&lt;p&gt;Node.js 原生插件是一个共享库（Windows 上的 &lt;code&gt;.dll&lt;/code&gt;，Linux 上的 &lt;code&gt;.so&lt;/code&gt;，macOS 上的 &lt;code&gt;.dylib&lt;/code&gt;），Node.js 可以在运行时加载它。接口叫做 &lt;a href="https://nodejs.org/api/n-api.html"&gt;N-API&lt;/a&gt;——一个稳定的、ABI 兼容的 C API。N-API 不关心是什么语言产生了这个库，只关心它是否导出了正确的符号。&lt;/p&gt;
&lt;p&gt;.NET Native AOT 恰好可以做到这一点。它将 C# 代码提前编译成具有任意入口点的本地共享库。这就是全部的技巧。&lt;/p&gt;
&lt;h2 id="项目配置"&gt;项目配置&lt;/h2&gt;
&lt;p&gt;项目文件非常简洁：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PublishAot&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/PublishAot&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;AllowUnsafeBlocks&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/AllowUnsafeBlocks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;PublishAot&lt;/code&gt; 告诉 SDK 在 &lt;code&gt;dotnet publish&lt;/code&gt; 时生成共享库。&lt;code&gt;AllowUnsafeBlocks&lt;/code&gt; 是 N-API interop 使用函数指针和固定缓冲区所必需的。&lt;/p&gt;
&lt;h2 id="导出入口点"&gt;导出入口点&lt;/h2&gt;
&lt;p&gt;Node.js 期望你的库导出 &lt;code&gt;napi_register_module_v1&lt;/code&gt;。在 C# 中，&lt;code&gt;[UnmanagedCallersOnly]&lt;/code&gt; 正是做这件事的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;unsafe&lt;/span&gt; &lt;span class="kd"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RegistryAddon&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt; [UnmanagedCallersOnly(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt; EntryPoint = &amp;#34;napi_register_module_v1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt; CallConvs = [typeof(CallConvCdecl)]&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;RegisterFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;readStringValue&amp;#34;&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ReadStringValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;值得注意的几点：&lt;code&gt;nint&lt;/code&gt; 是本机大小的整数——&lt;code&gt;intptr_t&lt;/code&gt; 的托管等价物。&lt;code&gt;u8&lt;/code&gt; 后缀生成包含 UTF-8 字符串字面量的 &lt;code&gt;ReadOnlySpan&amp;lt;byte&amp;gt;&lt;/code&gt;，直接传递给 N-API，无需任何编码分配。&lt;code&gt;[UnmanagedCallersOnly]&lt;/code&gt; 用 Node.js 所寻找的确切入口点名称导出方法。&lt;/p&gt;
&lt;h2 id="将-n-api-解析到宿主进程"&gt;将 N-API 解析到宿主进程&lt;/h2&gt;
&lt;p&gt;N-API 函数由 &lt;code&gt;node.exe&lt;/code&gt; 本身导出，而不是单独的库。因此不是链接到某个库，而是在启动时对运行中的进程进行解析：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;NativeLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetDllImportResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reflection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetExecutingAssembly&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ResolveDllImport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;ResolveDllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;libraryName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Assembly&lt;/span&gt; &lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DllImportSearchPath&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;searchPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libraryName&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;NativeLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetMainProgramHandle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;有了这个，P/Invoke 声明通过 &lt;code&gt;[LibraryImport]&lt;/code&gt; 和源生成的 marshalling 就能正常工作了。&lt;/p&gt;
&lt;h2 id="一个真实的导出函数"&gt;一个真实的导出函数&lt;/h2&gt;
&lt;p&gt;这是他们构建的注册表读取器：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)]&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;ReadStringValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;keyPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GetStringArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;valueName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GetStringArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyPath&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;valueName&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ThrowError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Expected two string arguments: keyPath, valueName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenSubKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valueName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;CreateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GetUndefined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ThrowError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;$&amp;#34;Registry read failed: {ex.Message}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关于 &lt;code&gt;try/catch&lt;/code&gt; 的重要说明：&lt;code&gt;[UnmanagedCallersOnly]&lt;/code&gt; 方法中未处理的异常会导致宿主进程崩溃。始终要捕获异常并通过 &lt;code&gt;ThrowError&lt;/code&gt; 转发给 JavaScript。&lt;/p&gt;
&lt;h2 id="从-typescript-调用"&gt;从 TypeScript 调用&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./native/win32-x64/RegistryAddon.node&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;RegistryAddon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdkPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readStringValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;SOFTWARE\\dotnet\\Setup\\InstalledVersions\\x64\\sdk&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;InstallLocation&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;TypeScript → C#，无需 Python，无需 C++。&lt;/p&gt;
&lt;h2 id="他们获得了什么"&gt;他们获得了什么&lt;/h2&gt;
&lt;p&gt;立竿见影的好处是改善了贡献者体验：不再需要特定版本的 Python，&lt;code&gt;yarn install&lt;/code&gt; 只需 Node.js 和 .NET SDK 就能工作。CI 流水线也得到了简化。性能与 C++ 实现相当。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;C# Dev Kit 团队用团队里每个人都能编写和调试的简洁 C# 代码，替换了 Python/C++ 的复杂性。如需包含所有字符串 marshalling 辅助方法的完整演练，请查看 &lt;a href="https://devblogs.microsoft.com/dotnet/writing-nodejs-addons-with-dotnet-native-aot/"&gt;.NET 博客上的原文&lt;/a&gt;。&lt;/p&gt;</content:encoded></item></channel></rss>