<?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/ko/tags/developer-tooling/</link><description>Articles, tutorials and insights from the .NET community.</description><generator>Hugo</generator><language>ko</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/ko/tags/developer-tooling/index.xml" rel="self" type="application/rss+xml"/><item><title>azd + GitHub Copilot: AI 기반 프로젝트 설정과 스마트 오류 해결</title><link>https://thedotnetblog.com/ko/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/ko/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/ko/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;ldquo;Set up with GitHub Copilot (Preview)&amp;rdquo; 옵션이 나타납니다. 선택하면 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;PostgreSQL 의존성이 있는 Node.js Express API가 있다고 합시다. 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 답변 3개 읽기 → &lt;code&gt;az&lt;/code&gt; CLI 명령어 실행 → 다시 시도하며 기도. &lt;code&gt;azd&lt;/code&gt;에 Copilot이 통합되면 이 루프가 사라집니다. &lt;code&gt;azd&lt;/code&gt; 명령이 실패하면 즉시 4가지 옵션을 제공합니다:&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;&lt;code&gt;azd update&lt;/code&gt;로 최신 버전을 받고 다음 프로젝트에서 &lt;code&gt;azd init&lt;/code&gt;을 써보세요. 진정한 가치를 주는 Copilot 통합입니다.&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/ko/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/ko/posts/emiliano-montesdeoca/nodejs-addons-csharp-native-aot/</guid><description>C# Dev Kit 팀이 C++로 작성된 Node.js 애드온을 .NET Native AOT로 교체했습니다 — 결과는 더 깔끔하고 안전하며, .NET SDK만 있으면 됩니다.</description><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;이 글은 자동 번역되었습니다. 영어 원문은 &lt;a href="https://thedotnetblog.com/ko/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++로 작성하고 &lt;code&gt;node-gyp&lt;/code&gt;으로 컴파일하는 네이티브 Node.js 애드온을 사용하고 있었습니다. 동작했습니다. 하지만 팀원 누구도 직접 건드리지 않을 패키지를 빌드하기 위해 모든 개발자 기기에 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 네이티브 애드온은 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;)입니다. 인터페이스는 &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;은 &lt;code&gt;dotnet publish&lt;/code&gt; 시 공유 라이브러리를 생성하도록 SDK에 지시합니다. &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;와 소스 생성 마샬링으로 올바르게 동작합니다.&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 팀은 Python/C++ 복잡성을 팀 모두가 이미 작성하고 디버그할 줄 아는 깔끔한 C# 코드로 대체했습니다. 모든 문자열 마샬링 헬퍼를 포함한 전체 안내는 &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>