Share via


JavaScript에서 .NET 실행

이 문서에서는 JS[JSImport]/[JSExport] interop을 사용하여 JavaScript(JS)에서 .NET을 실행하는 방법을 설명합니다.

추가 지침은 .NET 런타임(dotnet/runtime) GitHub 리포지토리에서 .NET WebAssembly 애플리케이션 구성 및 호스팅 지침을 참조하세요. 2023년 후반 또는 2024년 초에 상호 연결된 지침에 새 정보를 포함하도록 이 문서를 업데이트할 계획입니다.

기존 JS 앱은 .NET 7 이상에서 확장된 클라이언트 쪽 WebAssembly 지원을 사용하여 .NET 라이브러리 JS 를 다시 사용하거나 새 기능을 빌드할 수 있습니다. NET 기반 앱 및 프레임워크.

참고 항목

이 문서에서는 Blazor에 대한 종속성 없이 JS 앱에서 .NET을 실행하는 데 중점을 둡니다. Blazor WebAssembly 앱에서 [JSImport]/[JSExport] interop을 사용하는 방법에 대한 지침은 ASP.NET Core Blazor를 사용하는 JavaScript JSImport/JSExport interop을 참조하세요.

이러한 방법은 WebAssembly(WASM)에서만 실행될 것으로 예상되는 경우에 적합합니다. 라이브러리는 OperatingSystem.IsBrowser를 호출하여 앱이 WASM에서 실행 중 인지 확인하기 위해 런타임 검사를 수행할 수 있습니다.

필수 조건

.NET 7.0 SDK

.NET Core SDK의 최신 버전을 설치합니다.

관련 MSBuild 대상을 가져오는 wasm-tools 워크로드를 설치합니다.

dotnet workload install wasm-tools

필요에 따라 브라우저 앱(WebAssembly Browser 앱) 또는 Node.js 기반 콘솔 앱(WebAssembly 콘솔 앱)에서 WebAssembly에서 .NET을 시작하기 위한 실험적 프로젝트 템플릿이 포함된 wasm-experimental 워크로드를 설치합니다. JS[JSImport]/[JSExport] interop을 기존 JS 앱에 통합하려는 경우에는 이 워크로드가 필요하지 않습니다.

dotnet workload install wasm-experimental

자세한 내용은 실험적 워크로드 및 프로젝트 템플릿 섹션을 참조하세요.

네임스페이스

이 문서에 설명된 JS interop API는 System.Runtime.InteropServices.JavaScript 네임스페이스의 특성에 의해 제어됩니다.

프로젝트 구성

JS interop을 사용하도록 프로젝트(.csproj)를 구성하려면 다음을 수행합니다.

  • 대상 net7.0 이상:

    <TargetFramework>net7.0</TargetFramework>
    
  • 런타임 식별자에 대해 browser-wasm을 지정합니다.

    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    
  • 실행 파일 출력 형식을 지정합니다.

    <OutputType>Exe</OutputType>
    
  • AllowUnsafeBlocks 속성을 사용하도록 설정하면 Roslyn 컴파일러의 코드 생성기가 JS interop에 대한 포인터를 사용할 수 있습니다.

    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    

    Warning

    JS interop API를 사용하려면 AllowUnsafeBlocks를 사용하도록 설정해야 합니다. 보안 및 안정성 위험을 초래할 수 있는 .NET 앱에서 안전하지 않은 코드를 구현할 때는 주의해야 합니다. 자세한 내용은 안전하지 않은 코드, 포인터 형식 및 함수 포인터를 참조하세요.

  • WasmMainJSPath을 디스크의 파일을 가리키도록 지정합니다. 이 파일은 앱과 함께 게시되지만 .NET을 기존 JS 앱에 통합하는 경우에는 파일을 사용할 필요가 없습니다.

    다음 예제에서 디스크의 JS 파일은 main.js이지만 모든 JS 파일 이름은 허용됩니다.

    <WasmMainJSPath>main.js</WasmMainJSPath>
    

구성 후 예제 프로젝트 파일(.csproj):

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
    <OutputType>Exe</OutputType>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <WasmMainJSPath>main.js</WasmMainJSPath>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

WASM에서의 JavaScript interop

다음 예제의 API는 dotnet.js에서 가져옵니다. 이러한 API를 사용하면 C# 코드로 가져올 수 있는 명명된 모듈을 설정하고 Program.Main를 포함하여 .NET 코드에서 노출하는 메서드를 호출할 수 있습니다.

Important

이 문서 전체의 "가져오기" 및 "내보내기"는 .NET의 관점에서 정의됩니다.

  • 앱은 .NET에서 호출할 수 있도록 JS 메서드를 가져옵니다.
  • 앱은 JS에서 호출할 수 있도록 .NET 메서드를 내보냅니다.

다음 예제에서

  • dotnet.js 파일은 .NET WebAssembly 런타임을 만들고 시작하는 데 사용됩니다. dotnet.js는 앱의 빌드 출력의 일부로 생성되고 AppBundle 폴더에 있습니다.

    bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle

    {BUILD CONFIGURATION} 자리 표시자는 빌드 구성(예: Debug, Release)이고 {TARGET FRAMEWORK} 자리 표시자는 대상 프레임워크(예: net7.0)입니다.

    Important

    기존 앱과 통합하려면, 나머지 앱과 함께 제공될 수 있도록 AppBundle 폴더의 내용을 복사합니다. 프로덕션 배포의 경우 명령 셸에서 dotnet publish -c Release 명령을 사용하여 앱을 게시하고 앱을 사용하여 AppBundle 폴더를 배포합니다.

  • dotnet.create()는 .NET WebAssembly 런타임을 설정합니다.

  • setModuleImports는 이름을 .NET으로 가져오기 위한 JS 함수 모듈과 연결합니다. JS 모듈에는 현재 페이지 주소(URL)를 반환하는 window.location.href 함수가 포함되어 있습니다. 모듈의 이름은 모든 문자열일 수 있지만(파일 이름이 될 필요는 없음) JSImportAttribute와 함께 사용되는 이름과 일치해야 합니다(이 문서의 뒷부분에서 설명). window.location.href 함수는 C#으로 가져오고 C# 메서드 GetHRef에 의해 호출됩니다. GetHRef 메서드는 이 섹션의 뒷부분에 나와 있습니다.

  • exports.MyClass.Greeting()MyClass.Greeting에서 .NET(JS)을 호출합니다. Greeting은 C# 메서드는 window.location.href 함수 호출 결과를 포함하는 문자열을 반환합니다. Greeting 메서드는 이 섹션의 뒷부분에 나와 있습니다.

  • dotnet.run()Program.Main를 실행합니다.

JS 모듈:

import { dotnet } from './dotnet.js'

const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);

const { setModuleImports, getAssemblyExports, getConfig } = 
  await dotnet.create();

setModuleImports("main.js", {
  window: {
    location: {
      href: () => globalThis.window.location.href
    }
  }
});

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const text = exports.MyClass.Greeting();
console.log(text);

document.getElementById("out").innerHTML = text;
await dotnet.run();

C#에서 호출할 수 있도록 JS 함수를 가져오려면 일치하는 메서드 서명에서 새 JSImportAttribute를 사용합니다. JSImportAttribute의 첫 번째 매개 변수는 가져올 JS 함수의 이름이고 두 번째 매개 변수는 모듈의 이름입니다.

다음 예제에서는 GetHRef 메서드가 호출되면 main.js 모듈에서 window.location.href 함수가 호출됩니다.

[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();

가져온 메서드 서명에서 런타임에 의해 자동으로 마샬링되는 매개 변수 및 반환 값에 .NET 형식을 사용할 수 있습니다. JSMarshalAsAttribute<T>를 사용하여 가져온 메서드 매개 변수를 마샬링하는 방법을 제어합니다. 예를 들어, longSystem.Runtime.InteropServices.JavaScript.JSType.Number 또는 System.Runtime.InteropServices.JavaScript.JSType.BigInt로 마샬링 하도록 선택할 수 있습니다. 호출 가능한 JS 함수로 마샬링되는 매개 변수로 Action/Func<TResult> 콜백을 전달할 수 있습니다. JS 및 관리형 개체 참조를 모두 전달할 수 있으며, 프록시 개체로 마샬링되어 프록시가 가비지 수집될 때까지 경계를 넘어 개체를 활성 상태로 유지합니다. JS 약속으로 마샬링되는 Task 결과를 사용하여 비동기 메서드를 가져오고 내보낼 수도 있습니다. 마샬링된 형식의 대부분은 가져온 메서드와 내보낸 메서드 모두에서 매개 변수 및 반환 값으로 양방향으로 작동합니다.

다음 표에서는 지원되는 형식 매핑을 보여줍니다.

.NET JavaScript Nullable Task-Promise JSMarshalAs 선택사항 Array of
Boolean Boolean 지원됨 지원됨 지원됨 지원되지 않음
Byte Number 지원됨 지원됨 지원됨 지원됨
Char String 지원됨 지원됨 지원됨 지원되지 않음
Int16 Number 지원됨 지원됨 지원됨 지원되지 않음
Int32 Number 지원됨 지원됨 지원됨 지원됨
Int64 Number 지원됨 지원됨 지원되지 않음 지원되지 않음
Int64 BigInt 지원됨 지원됨 지원되지 않음 지원되지 않음
Single Number 지원됨 지원됨 지원됨 지원되지 않음
Double Number 지원됨 지원됨 지원됨 지원됨
IntPtr Number 지원됨 지원됨 지원됨 지원되지 않음
DateTime Date 지원됨 지원됨 지원되지 않음 지원되지 않음
DateTimeOffset Date 지원됨 지원됨 지원되지 않음 지원되지 않음
Exception Error 지원되지 않음 지원됨 지원됨 지원되지 않음
JSObject Object 지원되지 않음 지원됨 지원됨 지원됨
String String 지원되지 않음 지원됨 지원됨 지원됨
Object Any 지원되지 않음 지원됨 지원되지 않음 지원됨
Span<Byte> MemoryView 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Span<Int32> MemoryView 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Span<Double> MemoryView 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
ArraySegment<Byte> MemoryView 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
ArraySegment<Int32> MemoryView 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
ArraySegment<Double> MemoryView 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Task Promise 지원되지 않음 지원되지 않음 지원됨 지원되지 않음
Action Function 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Action<T1> Function 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Action<T1, T2> Function 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Action<T1, T2, T3> Function 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Func<TResult> Function 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Func<T1, TResult> Function 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Func<T1, T2, TResult> Function 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음
Func<T1, T2, T3, TResult> Function 지원되지 않음 지원되지 않음 지원되지 않음 지원되지 않음

다음 조건은 형식 매핑 및 마샬링된 값에 적용됩니다.

  • Array of 열은 .NET 형식을 JSArray로 마샬링할 수 있는지를 나타냅니다. 예: Number의 JSArray에 매핑된C# int[](Int32).
  • 잘못된 형식의 값을 사용하여 C#에 JS 값을 전달할 때, 프레임워크는 대부분의 경우 예외를 throw합니다. 프레임워크는 JS에서 컴파일 시간 형식 검사를 수행하지 않습니다.
  • JSObject, Exception, Task, ArraySegmentGCHandle 및 프록시를 만듭니다. 개발자 코드에서 삭제를 트리거하거나 .NET GC(가비지 수집)가 나중에 개체를 삭제하도록 허용할 수 있습니다. 이러한 형식은 상당한 성능 오버헤드를 수행합니다.
  • Array: 배열을 마샬링하면 JS 또는 .NET에서 배열의 복사본이 만들어집니다.
  • MemoryView
    • MemoryViewSpanArraySegment를 마샬링하는 .NET WebAssembly 런타임에 대한 JS 클래스입니다.
    • 배열 마샬링과 달리 Span 또는 ArraySegment를 마샬링해도 기본 메모리의 복사본이 만들어지지 않습니다.
    • MemoryView는 .NET WebAssembly 런타임에서만 올바르게 인스턴스화할 수 있습니다. 따라서 Span 또는 ArraySegment의 매개 변수가 있는 .NET 메서드로 JS 함수를 가져올 수 없습니다.
    • Span에 대해 만들어진 MemoryView는 interop 호출 기간 동안에만 유효합니다. interop 호출 후에도 유지되지 않는 호출 스택에 Span이 할당되기 때문에, Span를 반환 하는 .NET 메서드를 내보낼 수 없습니다.
    • ArraySegment에 대해 만들어진 MemoryView는 interop 호출 후에 유지되며 버퍼를 공유하는 데 유용합니다. ArraySegment에 대해 만들어진 MemoryView에서 dispose()를 호출하면 프록시가 삭제되고 기본 .NET 배열의 고정이 해제됩니다. MemoryView에 대한 try-finally 블록에서 dispose()를 호출하는 것이 좋습니다.

전역 네임스페이스에서 액세스할 수 있는 함수는 함수 이름의 globalThis 접두사를 사용하고 모듈 이름을 제공하지 않고 [JSImport] 특성을 사용하여 가져올 수 있습니다. 다음 예제에서는 console.logglobalThis로 접두사를 지정합니다. 가져온 함수는 C# 문자열 메시지(message)를 수락하고 C# 문자열을 console.log에 대해 JSString에 마샬링하는 C# Log 메서드에 의해 호출됩니다.

[JSImport("globalThis.console.log")]
internal static partial void Log([JSMarshalAs<JSType.String>] string message);

JS에서 호출할 수 있도록 .NET 메서드를 내보내려면 JSExportAttribute를 사용합니다.

다음 예제에서 Greeting 메서드는 GetHRef 메서드를 호출한 결과를 포함하는 문자열을 반환합니다. 앞에서 설명한 것처럼, GetHref C# 메서드는 main.js 모듈의 window.location.href 함수에 대해 JS을 호출합니다. window.location.href는 현재 페이지 주소(URL)를 반환합니다.

[JSExport]
internal static string Greeting()
{
    var text = $"Hello, World! Greetings from {GetHRef()}";
    Console.WriteLine(text);

    return text;
}

실험적 워크로드 및 프로젝트 템플릿

JS interop 기능을 시연하고 JS interop 프로젝트 템플릿을 가져오려면 wasm-experimental 워크로드를 설치합니다.

dotnet workload install wasm-experimental

wasm-experimental 워크로드에는 wasmbrowserwasmconsole의 두 가지 프로젝트 템플릿이 포함되어 있습니다. 이러한 템플릿은 현재 실험적입니다. 즉, 템플릿에 대한 개발자 워크플로는 진화하고 있습니다. 그러나 템플릿에 사용되는 .NET 및 JS API는 .NET 7에서 지원되며 JS에서부터 WASM에서 .NET을 사용하기 위한 기반을 제공합니다.

브라우저 앱

wasmbrowser 템플릿을 사용하여 브라우저 앱을 만들 수 있습니다. 이 템플릿은 .NET 및 JS를 브라우저에서 함께 사용하는 방법을 보여 주는 웹앱을 만듭니다.

dotnet new wasmbrowser

Visual Studio에서 앱을 빌드하거나 .NET CLI를 사용하여 앱을 빌드합니다.

dotnet build

빌드된 앱은 bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle 디렉터리에 있습니다. {BUILD CONFIGURATION} 자리 표시자는 빌드 구성(예: Debug, Release)입니다. {TARGET FRAMEWORK} 자리 표시자는 대상 프레임워크 모니커(예: net7.0)입니다.

Visual Studio 또는 .NET CLI를 사용하여 앱을 빌드하고 실행합니다.

dotnet run

또는 AppBundle 디렉터리에서 정적 파일 서버를 시작합니다.

dotnet serve -d:bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle

앞의 예제 에서, {TARGET FRAMEWORK} 자리 표시자는 대상 프레임워크 모니커(예: net7.0)입니다.

Node.js 콘솔 앱

wasmconsole 템플릿을 사용하여 콘솔 앱을 만들 수 있습니다. 이 템플릿은 Node.js 또는 V8 콘솔 앱으로 WASM에서 실행되는 앱을 만듭니다.

dotnet new wasmconsole

Visual Studio에서 앱을 빌드하거나 .NET CLI를 사용하여 앱을 빌드합니다.

dotnet build

빌드된 앱은 bin/{BUILD CONFIGURATION}/{TARGET FRAMEWORK}/browser-wasm/AppBundle 디렉터리에 있습니다. {BUILD CONFIGURATION} 자리 표시자는 빌드 구성(예: Debug, Release)입니다. {TARGET FRAMEWORK} 자리 표시자는 대상 프레임워크 모니커(예: net7.0)입니다.

Visual Studio 또는 .NET CLI를 사용하여 앱을 빌드하고 실행합니다.

dotnet run

또는 AppBundle 디렉터리에서 정적 파일 서버를 시작합니다.

node bin/$(Configuration)/{TARGET FRAMEWORK}/browser-wasm/AppBundle/main.mjs

앞의 예제 에서, {TARGET FRAMEWORK} 자리 표시자는 대상 프레임워크 모니커(예: net7.0)입니다.

추가 리소스