WebAssembly용 Qt
WebAssembly용 Qt를 사용하면 웹에서 Qt 애플리케이션을 실행할 수 있습니다.
WebAssembly(줄여서 Wasm)는 웹 브라우저와 같은 가상 머신에서 실행되도록 설계된 이진 명령어 형식입니다.
Qt for WebAssembly를 사용하면 애플리케이션을 브라우저 샌드박스에서 실행되는 웹 애플리케이션 형태로 배포할 수 있습니다. 이 방식은 호스트 기기의 기능에 대한 전체적인 접근 권한이 필요하지 않은 웹 기반 분산 애플리케이션에 적합합니다.
참고: Qt for WebAssembly는 지원되는 플랫폼이지만, 일부 모듈은 아직 지원되지 않거나 기술 미리보기(Tech Preview) 단계에 있습니다. ‘지원되는 Qt 모듈’을 참조하십시오.
Qt for WebAssembly 시작하기
WebAssembly용 Qt 애플리케이션을 빌드하는 방법은 다른 플랫폼용 Qt를 빌드하는 방법과 유사합니다. SDK(Emscripten)를 설치하고, Qt를 설치(또는 소스 코드에서 Qt를 빌드)한 다음, 마지막으로 애플리케이션을 빌드해야 합니다. 몇 가지 차이점이 있는데, 예를 들어 Qt for WebAssembly는 다른 Qt 빌드에 비해 지원하는 모듈과 기능이 더 적습니다.
Emscripten 설치
Emscripten은 WebAssembly로 컴파일하기 위한 툴체인입니다. 이를 통해 브라우저 플러그인 없이도 웹에서 Qt를 네이티브에 가까운 속도로 실행할 수 있습니다.
Qt의 각 마이너 버전은 특정 Emscripten 버전을 대상으로 하며, 패치 릴리스에서는 이 버전이 변경되지 않습니다. Qt의 바이너리 패키지는 대상 Emscripten 버전을 사용하여 빌드됩니다. Emscripten은 버전 간 ABI 호환성을 보장하지 않으므로, 애플리케이션은 동일한 버전을 사용해야 합니다.
6.12.0에서 지원되는 Emscripten 버전은 Emscripten 5.0.5입니다.
Emscripten SDK 설치에 대한 자세한 내용은 Emscripten 문서를 참조하십시오.
emsdk 를 사용하여 특정 버전의 Emscripten 를 설치할 수 있습니다. 예를 들어, 6.12.0용 버전을 설치하려면 다음 명령을 입력하십시오:
- ./emsdk install 5.0.5
- ./emsdk activate 5.0.5
설치가 완료되면 Emscripten 컴파일러가 경로에 포함되어야 합니다. 다음 명령어를 사용하여 확인하십시오:
em++ --versionWindows에서는 설치 후 Emscripten이 경로에 자동으로 추가됩니다. macOS나 Linux에서는 다음과 같이 경로에 추가해야 합니다:
source /path/to/emsdk/emsdk_env.sh다음 명령어를 실행하여 확인해 보세요:
em++ --versionEmscripten 버전을 선택할 때 더 많은 유연성이 필요하다면 소스 코드에서 Qt를 빌드할 수 있습니다. 이 경우 위에 언급된 버전들은 최소 요구 버전입니다. 이후 버전들도 작동할 것으로 예상되지만, 동작상의 변화가 발생하여 Qt에 대한 수정이 필요할 수 있습니다.
Qt 설치
Qt 계정의 ‘다운로드’ 섹션에서 Qt를 다운로드하십시오. 개발 플랫폼으로 Linux, macOS 및 Windows용 빌드를 제공합니다.
이 바이너리 빌드는 가능한 한 많은 브라우저에서 실행되도록 설계되었으며, 단일 스레드 및 다중 스레드 버전으로 제공됩니다. Wasm SIMD 및 Wasm exceptions 와 같은 비표준 기능은 바이너리 빌드에서 지원되지 않습니다.
소스 코드를 사용하여 Qt 빌드하기
소스 코드를 사용하여 빌드하면 스레드 지원, OpenGL ES 레벨, SIMD 지원과 같은 Qt 구성 옵션을 설정할 수 있습니다. Qt 계정의 ‘다운로드’ 섹션에서 Qt 소스 코드를 다운로드하십시오.
wasm-emscripten 플랫폼을 위한 크로스 컴파일 빌드로 Qt를 구성하십시오. 이렇게 하면 -static, -no-feature-thread 및 -no-make examples 구성 옵션이 설정됩니다. -feature-thread 구성 옵션을 사용하여 스레드 지원을 활성화할 수 있습니다. 공유 라이브러리 빌드는 지원되지 않습니다.
동일한 버전의 Qt 호스트 빌드가 필요합니다. 또한 QT_HOST_PATH CMake 변수를 설정하거나 -qt-host-path 구성 인수를 사용하여 호스트 빌드의 경로를 지정해야 합니다.
./configure -qt-host-path /path/to/Qt -platform wasm-emscripten -prefix $PWD/qtbase참고: ` ninja ` 실행 파일이 있는 경우,configure는 항상 Ninja 생성기 및 빌드 도구를 사용합니다. Ninja는 크로스 플랫폼이며, 기능이 풍부하고 성능이 뛰어나 모든 플랫폼에서 권장됩니다. 다른 생성기를 사용하는 것도 가능할 수 있지만, 공식적으로 지원되지는 않습니다.
Windows에서는 PATH 에 Mingw-w64가 포함되어 있는지 확인한 후, 다음과 같이 configure를 실행하십시오:
configure -qt-host-path C:\Path\to\Qt -no-warnings-are-errors -platform wasm-emscripten -prefix %CD%\qtbase그런 다음 필요한 모듈을 빌드하십시오:
cmake --build . -t qtbase -t qtdeclarative [-t another_module]명령줄에서 애플리케이션 빌드하기
Qt for WebAssembly는 CMake와 ninja 또는 make를 사용하여 애플리케이션을 빌드할 수 있도록 지원합니다.
$ /path/to/qt-wasm/qtbase/bin/qt-cmake .
$ cmake --build .참고: Linux의 qt-cmake 이나 Windows의 qt-cmake.bat 와 달리 기본 CMake 을 사용할때는 , 다른 크로스 플랫폼 빌드와 마찬가지로 "-DCMAKE_TOOLCHAIN_FILE" 옵션을 사용하여 툴체인 파일을 지정해야 합니다. 자세한 내용은 여기에서 확인하세요: CMake 시작하기.
애플리케이션을 빌드하면 애플리케이션과 Qt 코드(정적 링크됨)가 포함된 .wasm 파일, 브라우저에서 열어 애플리케이션을 실행할 수 있는 .html 파일 등 여러 출력 파일이 생성됩니다.
참고: Emscripten은 "-g" 디버그 수준에서 비교적 큰 .wasm 파일을 생성합니다. 디버그 빌드 시에는 "-g2"로 링크하는 것을 고려해 보십시오.
애플리케이션 실행
애플리케이션을 실행하려면 웹 서버가 필요합니다. 빌드 출력 파일은 모두 정적 콘텐츠이므로 어떤 웹 서버라도 사용할 수 있습니다. 일부 사용 사례의 경우, HTTPS 인증서를 제공하거나 멀티스레딩 지원을 활성화하는 데 필요한 HTTP 헤더를 설정하는 등 특별한 서버 구성이 필요할 수 있습니다.
Emrun
Emscripten은 애플리케이션 테스트 실행을 위한 emrun 유틸리티를 제공합니다. Emrun은 웹 서버를 시작하고 브라우저를 실행하며, stdout/stderr(일반적으로 JavaScript 콘솔로 전송됨)를 캡처하여 전달합니다.
/path/to/emscripten/emrun --browser=firefox appname.htmlPython http.server
또 다른 방법은 개발용 웹 서버를 시작한 다음 웹 브라우저를 별도로 실행하는 것입니다. 가장 간단한 방법 중 하나는 파이썬의 `http.server`를 사용하는 것입니다:
python -m http.server단, 이는 단순한 웹 서버일 뿐이며, 아래에서 언급할 필수 COOP(Cross-Origin-Opener-Policy) 및 COEP(Cross-Origin-Embedder-Policy) 헤더가 전송되지 않기 때문에 스레딩에 필요한 SharedArrayBuffer를 지원하지 않는다는 점에 유의하십시오.
qtwasmserver
Qt는 mkcert를 사용하여 HTTPS 인증서를 생성하는 개발자용 웹 서버를 제공합니다. 이를 통해 보안 컨텍스트가 필요한 웹 기능을 테스트할 수 있습니다. 또한 http://localhost를 통한 전송은 인증서 없이도 안전한 것으로 간주된다는 점에 유의하십시오.
또한 이 웹 서버는 COOP 및 COEP 헤더를 특정 값으로 설정하여 SharedArrayBuffer 및 멀티스레딩을 지원합니다.
qtwasmserver 스크립트는 기본적으로 localhost에 바인딩되는 서버 하나를 시작합니다. -a 명령줄 인수를 사용하여 추가 주소를 지정하거나, --all 를 사용하여 사용 가능한 모든 주소에 바인딩할 수 있습니다.
qtwasmserver 스크립트는 다음 소스 디렉터리에 있습니다.
qtbase/util/wasm/qtwasmserver/qtwasmserver.py에 위치해 있으며, pip를 사용하여 설치할 수도 있습니다:
pip install qtwasmserverqtwasmserver에 대한 자세한 정보는 qtwasmserver를 참조하십시오.
qtwasmserver 사용법:
python /path/to/qtbase/util/wasm/qtwasmserver/qtwasmserver.py --all다음 방법을 사용하여 애플리케이션 빌드하기 Qt Creator
웹에 애플리케이션 배포하기
애플리케이션을 빌드하면 여러 파일이 생성됩니다(다음 표에서 “app”을 해당 애플리케이션 이름으로 대체하십시오).
| 생성된 파일 | 간략한 설명 |
|---|---|
| app.html | HTML 컨테이너 |
| qtloader.js | Qt 애플리케이션 로딩을 위한 JavaScript API |
| app.js | Emscripten으로 생성된 JavaScript 런타임 |
| app.wasm | 앱 바이너리 |
app.html을 있는 그대로 배포하거나, 사용자 정의 HTML 파일을 사용하기 위해 이를 삭제할 수 있습니다. 스플래시 화면 이미지를 Qt 로고에서 앱 로고로 변경하는 것과 같은 사소한 조정도 가능합니다. 두 경우 모두 qtloader.js는 애플리케이션을 로드하기 위한 JavaScript API를 제공합니다.
배포하기 전에 gzip 또는 brotli 을 사용하여 Wasm 파일을 압축하십시오. 이 도구들은 다른 도구들보다 더 높은 압축률을 제공합니다. 자세한 내용은 ‘바이너리 크기 최소화’를 참조하십시오.
멀티스레딩이나 SIMD와 같은 특정 기능을 활성화하면, 해당 기능을 지원하지 않는 브라우저와 호환되지 않는 .wasm 바이너리가 생성됩니다. 여러 개의 .wasm 파일을 빌드한 다음 JavaScript 기능 감지를 통해 올바른 파일을 선택함으로써 이 제한 사항을 우회할 수 있지만, Qt는 이를 위한 기능을 제공하지 않는다는 점에 유의하십시오.
qtloader 사용
Qt는 Qt for WebAssembly 애플리케이션을 다운로드, 컴파일 및 인스턴스화하기 위한 JavaScript API를 제공합니다. 이 로딩 API는 Emscripten이 제공하는 로딩 기능을 래핑하며, Qt 기반 애플리케이션에 유용한 추가 기능을 제공합니다. 이 API는 qtloader.js 파일에 구현되어 있습니다. 이 파일의 사본은 빌드 시점에 빌드 디렉터리에 작성됩니다.
일반적인 사용법은 다음과 같습니다:
const app_container_element = ...;
const instance = await qtLoad({
qt: {
containerElements: [ app_container_element ],
onLoaded: () => { /* handle application load completed */ },
onExit: () => { /* handle application exit */ },
}
});코드는 구성 객체를 인수로 전달하여 qtLoad() 로더 함수를 호출합니다. 이 구성 객체에는 모든 emscripten 구성 옵션은 물론, 특수한 “qt” 구성 객체도 포함될 수 있습니다. qt 구성 객체는 다음 속성을 지원합니다:
| 속성 | 간략한 설명 |
|---|---|
| containerElements | HTML 컨테이너 요소의 배열입니다. 애플리케이션은 이를 QScreen으로 인식합니다. |
| onLoaded | 애플리케이션 로딩이 완료되었을 때 호출되는 콜백입니다. |
| onExit | 애플리케이션이 종료될 때 호출되는 콜백입니다. |
containerElements 배열은 Qt와 웹 페이지 간의 주요 인터페이스이며, 이 배열에 포함된 HTML 요소(일반적으로 <DIV> 요소)는 웹 페이지에서 애플리케이션 콘텐츠의 위치를 지정합니다.
애플리케이션은 각 컨테이너 요소를 ` QScreen ` 인스턴스로 인식하며, 평소와 같이 화면 인스턴스 위에 애플리케이션 창을 배치할 수 있습니다. ` Qt::WindowFullScreen ` 상태가 설정된 창은 화면 전체 영역을 사용하며, "전체 화면"이 아닌 창에는 창 장식 요소가 표시됩니다.
qtLoad() 함수는 프로미스를 반환하며, 이 프로미스를 기다리면 Emscripten 인스턴스를 반환합니다. 이 인스턴스를 통해 Embind에서 내보낸 함수에 접근할 수 있습니다. Qt는 이러한 함수를 여러 개 내보내며, 이 함수들이 인스턴스 API를 구성합니다.
Qt 인스턴스 API 사용
Qt는 여러 인스턴스 함수를 제공합니다. 현재 이 함수들은 런타임 시 컨테이너 요소의 추가 및 제거를 지원합니다.
| 속성 | 간략한 설명 |
|---|---|
| qtAddContainerElement | 컨테이너 요소를 추가합니다. 요소를 추가하면 새로운 ` QScreen`가 추가됩니다. |
| qtRemoveContainerElement | 컨테이너 요소와 이에 대응하는 화면을 제거합니다. |
| qtSetContainerElements | 모든 컨테이너 요소를 설정합니다. |
| qtResizeContainerElement | Qt가 컨테이너 요소의 크기 변경 사항을 반영하도록 합니다. |
Qt 6.6 qtloader로 이식
Qt 6.6에는 구현이 간소화되고 적용 범위가 축소된 새로운 qtloader가 포함되어 있습니다. 여기에는 애플리케이션의 JavaScript 코드를 이식해야 할 수도 있는 API 변경 사항이 포함되어 있습니다. Qt는 원활한 전환을 돕기 위해 호환성 API를 제공합니다. 사용 사례에 따라 다음과 같은 여러 가지 방법이 있습니다:
- 생성된 `
app.html` 파일을 직접 사용하고 있다면, 빌드 시 이 파일도 함께 업데이트됩니다. 별도의 조치가 필요하지 않습니다. - 기본 qtloader 기능 세트를 사용하고 있는 경우, 임시 조치로 Qt 6.6에 포함된 호환성 API를 사용할 수 있습니다. 이 API는 향후 릴리스에서 제거될 예정이므로, 새로운 qtloader를 사용하도록 업데이트할 계획을 세워야 합니다. 아래의 포팅 단계 1이 필요합니다.
- 고급 기능(예: 런타임 시 컨테이너 요소 추가)을 사용하고 있는 경우, 새로운 로더나 인스턴스 API로 이식해야 합니다. 아래의 이식 단계 1과 2를 수행해야 합니다.
이식 단계
- 로딩 HTML 파일에서
app.js(Emscripten으로 생성된 JavaScript 런타임)을 포함시킵니다.<script src="app.js"></script>Qt 6.6 이전 버전에서는 qtloader가 이 JavaScript 파일을 로드하고 평가했습니다. 이제는 더 이상 그렇게 처리되지 않으므로, <script> 태그를 사용하여 파일을 포함시켜야 합니다.
- 새로운 JavaScript 및 인스턴스 API를 사용하도록 포팅하십시오.
위의 문서 섹션을 참조하십시오.
지원되는 브라우저
데스크톱
Qt for WebAssembly는 다음 브라우저에서 개발 및 테스트되었습니다:
- Chrome
- 파이어폭스
- 사파리
- Edge
브라우저가 WebAssembly를 지원하면 Qt가 실행되어야 합니다. WebAssembly를 지원하는 브라우저는 대개 WebGL도 지원하지만, 일부 브라우저는 구형이나 지원되지 않는 GPU를 차단하기도 합니다.s/qtloader.js는 WebGL 사용 가능 여부를 확인하는 API를 제공합니다.
Qt는 운영 체제 기능을 직접 사용하지 않으므로, 예를 들어 파이어폭스가 Windows에서 실행되든 macOS에서 실행되든 아무런 차이가 없습니다. 다만 Qt는 macOS에서 Ctrl/Cmd 키 처리를 위한 것과 같은 일부 운영 체제별 최적화 기능을 사용합니다.
모바일
WebAssembly용 Qt 애플리케이션은 모바일 Safari나 Android Chrome과 같은 모바일 브라우저에서 실행됩니다.
지원되는 Qt 모듈
WebAssembly용 Qt는 Qt 모듈 및 기능 중 일부를 지원합니다. 테스트를 거친 모듈은 아래에 나열되어 있으며, 그 외의 모듈은 정상적으로 작동할 수도 있고 그렇지 않을 수도 있습니다.
- Qt Core
- Qt GUI
- Qt Network
- Qt Widgets
- Qt Qml
- Qt Quick
- Qt Quick Controls
- Qt Quick Layouts
- Qt 5 Core Compatibility APIs
- Qt Image Formats
- Qt OpenGL
- Qt SVG
- Qt WebSockets
- Qt Concurrent
- Qt Charts
- Qt Graphs
- Qt Quick 3D
다음 모듈들은 기술 미리보기(Technology Preview) 단계에 있습니다. 기능이 제한적일 수 있으며, 향후 릴리스에서 크게 변경될 수 있습니다.
어떤 경우든 모듈 지원이 완전하지 않을 수 있으며, 브라우저 샌드박스나 Qt 플랫폼 포팅이 미완성된 탓에 추가적인 제한 사항이 있을 수 있습니다. 자세한 내용은 ‘Qt for WebAssembly를 활용한 개발’을 참조하십시오.
Qt for WebAssembly를 이용한 개발
CMake를 사용한 빌드
CMake에서 Emscripten에 특화된 구성이 필요한 경우, 다음 코드를 활용할 수 있습니다:
if(EMSCRIPTEN)
# WebAssembly specific code
else()
# other platforms
endif()이 코드를 사용하면 다른 플랫폼과의 호환성을 보장하면서 Emscripten 전용 설정을 적용할 수 있습니다.
OpenGL 및 WebGL
WebAssembly용 Qt는 https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API WebGL을 활용한 하드웨어 가속 렌더링을 지원합니다.
WebGL은 OpenGL ES를 엄격히 준수하며, 버전 매핑은 다음과 같습니다:
| OpenGL | WebGL |
|---|---|
| OpenGL ES 2.0 | WebGL 1 |
| OpenGL ES 3.0 | WebGL 2 |
Qt는 사용 가능한 최신 버전의 WebGL을 사용합니다. 현재 브라우저에서는 일반적으로 WebGL 2를 사용하지만, 하드웨어 사양에 따라 WebGL 1로 제한될 수도 있습니다. Qt for WebAssembly를 사용하여 WebGL 2를 지원하는 기기를 대상으로 개발할 것을 권장합니다.
웹과 데스크톱 OpenGL의 차이점은 WebGL 및 OpenGL 차이점에 설명되어 있습니다. WebGL 1.0과 WebGL 2.0 사이에는 추가적인 차이점이 있으며, 이는 WebGL 2.0 사양에 설명되어 있습니다.
기본적으로 ES2(및 ES3)의 WebGL 호환 하위 집합이 사용됩니다. 바운드 버퍼 없이 ` glDrawArrays ` 및 ` glDrawElements `를 사용해야 하는 경우, 다음을 추가하여 전체 ES2 지원을 활성화할 수 있습니다.
target_link_options(<your target> PRIVATE -s FULL_ES2=1)를 추가하여 완전한 ES2 에뮬레이션을 활성화하거나,
target_link_options(<your target> PRIVATE -s FULL_ES3=1)를 프로젝트의 ` CMakeLists.txt` 파일에 추가하여ES3를 완전히 에뮬레이션할 수도 있습니다.
Emscripten의 OpenGL 지원에 대한 자세한 내용은 https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html을 참조하십시오.
OpenGL 컨텍스트의 제한 사항
WebGL은 하나의 서피스당 여러 컨텍스트를 지원하지 않습니다. 이는 ` QOpenGLContext `을 직접 사용하거나 ` QOpenGLWidget`과 같은 다른 클래스를 통해 간접적으로 사용하는 애플리케이션에 영향을 미칩니다.
각 QOpenGLContext 인스턴스는 단 하나의 서피스와만 함께 사용해야 합니다. 실제로 컨텍스트는 첫 번째 makeCurrent() 호출 시 해당 서피스와 연결됩니다. 이후 다른 서피스에서 makeCurrent()를 호출하거나, 동일한 서피스를 가진 다른 QOpenGLContext 인스턴스에서 makeCurrent()를 호출하는 것은 지원되지 않습니다.
OpenGL 컨텍스트 공유는 지원되지 않습니다. ` QOpenGLContext::setShareContext()`를 호출해도 아무런 효과가 없으며, ` QOpenGLContext::shareContext()`는 항상 `nullptr`를 반환합니다.
서피스(예: QWindow)를 소멸시키면 관련 컨텍스트가 손실됩니다. 애플리케이션은 컨텍스트를 다시 생성하여 이 문제를 처리해야 합니다.
QOpenGLWidget 그리고 내부적으로 컨텍스트 공유를 사용하는 다른 클래스들도 지원되지 않습니다.
멀티스레딩
Qt for WebAssembly는 Emscripten의 Pthreads 지원을 통해 멀티스레딩을 지원하며, 각 스레드는 웹 워커로 구현됩니다. Qt Maintenance Tool 에서 "WebAssembly (multi-threaded)" 컴포넌트를 설치하거나, 소스에서 Qt를 빌드하고 configure에 "-feature-thread" 플래그를 전달하여 멀티스레딩을 활성화할 수 있습니다.
기존 스레딩 코드는 일반적으로 재사용할 수 있지만, pthread 구현의 특정 사항을 해결하기 위해 수정해야 할 수도 있습니다. 일부 Emscripten 및 Qt 기능은 지원되지 않으며, 여기에는 스레드 프록시 기능과 Qt Quick 의 스레드 기반 렌더 루프가 포함됩니다.
Qt for WebAssembly에서는 메인 스레드가 보조 스레드의 요청을 처리해야 할 수 있으므로, 메인 스레드를 차단하지 않는 것이 특히 중요하다는 점에 유의하십시오. 예를 들어, Qt의 모든 타이머는 메인 스레드에서 스케줄링되며, 메인 스레드가 차단된 경우 타이머가 작동하지 않습니다. 또 다른 예로, 새로운 웹 워커(스레드용)를 생성하는 작업은 메인 스레드에서만 수행할 수 있습니다.
Emscripten은 이에 대한 몇 가지 해결책을 제공합니다. 뮤텍스 잠금 획득과 같은 단기 대기 작업은 바쁜 대기(busy-waiting)와 잠금을 기다리는 동안 이벤트를 처리하는 방식으로 지원됩니다. 메인 스레드에서의 장시간 대기 작업은 피해야 합니다. 특히, QThread::wait()이나 pthread_join()을 호출하여 보조 스레드를 기다리는 일반적인 방식은, 애플리케이션이 해당 스레드(및 웹 워커)가 이미 시작되었으며 wait() 또는 join() 호출 시점에 메인 스레드의 도움 없이 완료될 수 있음을 보장할 수 없는 한 작동하지 않습니다.
멀티스레딩 기능을 사용하려면 브라우저가 SharedArrayBuffer API를 지원해야 합니다. (일반적으로 Emscripten은 힙을 ArrayBuffer 객체에 저장합니다. 멀티스레딩을 위해서는 힙을 웹 워커와 공유해야 하므로 SharedArrayBuffer가 필요합니다.) 이 API는 일반적으로 모든 최신 브라우저에서 사용할 수 있지만, 특정 보안 요구 사항이 충족되지 않으면 비활성화될 수 있습니다. 이 경우 스레드 지원이 활성화된 WebAssembly 바이너리는 바이너리가 실제로 스레드를 시작하지 않더라도 실행에 실패합니다.
SharedArrayBuffer를 활성화하려면 보안 브라우징 컨텍스트(페이지가 https:// 또는 http://localhost를 통해 제공되는 경우)가 필요하며, 페이지가 크로스 오리진 격리 모드에 있어야 합니다. 후자는 웹 서버에서 소위 COOP 및 COEP 헤더를 설정하여 구현할 수 있습니다:
- Cross-Origin-Opener-Policy: same-origin
- Cross-Origin-Embedder-Policy: require-corp
SIMD
Emscripten은 WebAssembly용 128비트 SIMD 유형 및 연산을 제공하는 WebAssembly SIMD를 지원합니다.
이 기능을 활성화하려면 소스 코드에서 Qt를 빌드하고 -feature-wasm-simd128 플래그를 사용하여 구성하십시오. 이렇게 하면 컴파일 및 링크 시 -msimd128 플래그가 전달됩니다. 현재 Qt에는 wasm-simd에 최적화된 코드 경로가 포함되어 있지 않다는 점에 유의하십시오. 그러나 wasm-simd를 활성화하면 컴파일러가 SIMD 명령어를 사용할 수 있는 경우 컴파일러의 자동 벡터화가 활성화됩니다.
GCC/Clang SIMD 벡터 확장 또는 WASM SIMD128 내장 함수를 사용하여 WebAssembly SIMD를 직접 대상으로 지정할 수 있습니다. 자세한 내용은 Emscripten SIMD 문서를 참조하십시오 .
또한, Emscripten은 x86 SSE 명령어를 Wasm SIMD 명령어로 에뮬레이션하거나 변환하는 기능을 지원합니다. Qt는 이 에뮬레이션을 사용하지 않습니다. 네이티브 Wasm SIMD에 상응하는 명령어가 없는 SSE SIMD 명령어를 사용하면 성능이 저하될 수 있기 때문입니다.
SIMD가 활성화된 바이너리는 WebAssembly SIMD를 지원하지 않는 브라우저와 호환되지 않으며, 실행 시점에 SIMD 코드 경로가 호출되지 않는 경우에도 마찬가지입니다. SIMD 지원은 'about:config'나 'chrome:flags'와 같은 브라우저의 고급 설정에서 활성화해야 할 수 있습니다.
네트워킹
Qt Network는 네트워킹에 대해 제한적인 지원을 제공합니다. 일반적으로 웹에서 이미 사용 중인 네트워크 프로토콜은 Qt에서도 사용할 수 있지만, 웹 샌드박스 때문에 다른 프로토콜은 직접 사용할 수 없습니다.
다음과 같은 프로토콜이 지원됩니다:
- QNetworkAccessManager 웹 페이지의 원본 서버 또는 CORS를 지원하는 서버에 대한 HTTP 요청. 여기에는 QML의 XMLHttpRequest도 포함됩니다.
- QWebSocket 모든 호스트에 대한 연결. 보안 https 프로토콜을 통해 제공되는 웹 페이지는 보안 wss 프로토콜을 통해서만 웹소켓 연결을 허용한다는 점에 유의하십시오.
- Emscripten이 제공하는 기능을 사용하여 WebSockets를 통해 에뮬레이션된 POSIX TCP 소켓. 소켓 변환을 처리하는 포워딩 서버를 실행해야 한다는 점에 유의하십시오.
그 외의 모든 네트워크 프로토콜은 지원되지 않습니다.
참고: 브라우저의 제한 사항으로 인해QWebSocketServer 는 지원되지 않습니다. 브라우저는 웹 샌드박스 내의 보안을 보장하기 위해 서버 측 소켓 기능을 제한합니다. 따라서 들어오는 네트워크 연결을 수락하기 위해 QWebSocketServer 에 의존하는 모든 기능은 웹 환경 내에서 사용할 수 없습니다.
참고: Qt MQTTQtRemoteObjects 모듈은 QtWebSockets 를 전송 프로토콜로 사용하여 작동할 수 있습니다. 이 모듈들은 공식적으로 지원되지 않으며, 작동할 수도 있고 그렇지 않을 수도 있으며, 일부 기능이 누락되어 있을 수도 있습니다. 자세한 내용은 QMqttClient::connectToHostWebSocket 및 QtRemoteObjects WebSockets Applications 예제를 참조하십시오.
크로스 오리진 리소스 공유(CORS) 및 정책(CORP)
네트워킹을 사용하는 WebAssembly 애플리케이션의 경우, 서버 측에서 크로스 오리진 리소스 공유(CORS) 및 크로스 오리진 리소스 정책(CORP) 응답 헤더를 설정해야 할 수 있습니다. 이러한 헤더는 다른 도메인에서 발생하는 HTTP 요청을 제한하며, 이는 보안 위험을 초래할 수 있습니다.
QHttpServer 를 사용하여 이러한 헤더를 설정하는 예시는 다음과 같습니다:
auto headers = response.headers();
headers.append("Access-Control-Allow-Origin", "*");
headers.append("Access-Control-Allow-Origin", "localhost");
headers.append("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
headers.append("Cross-Origin-Opener-Policy", "same-origin");
headers.append("Cross-Origin-Embedder-Policy", "require-corp");
headers.append("Cross-Origin-Resource-Policy", "cross-origin");다른 잘 알려진 서버들은 이러한 헤더 전송을 구성하는 자체적인 방식을 가지고 있습니다.
Access-Control-Allow-Origin에 와일드카드 "*"를 사용하면, Access-Control-Allow-Credentials와 함께 사용할 경우 오류가 발생합니다.
포함된 유틸리티 스크립트 qtwasmserver.py는 ` --cross-origin-isolation ` 옵션이 사용될 경우 이러한 헤더를 설정합니다.
로컬 파일 액세스
웹에서는 파일 시스템 접근이 샌드박스 처리되며, 이는 애플리케이션이 파일을 다루는 방식에 영향을 미칩니다. 웹 플랫폼은 사용자가 제어할 수 있는 방식으로 로컬 파일 시스템에 접근할 수 있는 API와 영구 저장소에 접근할 수 있는 API를 제공합니다. Emscripten과 Qt는 이러한 기능을 래핑하여 C++ 및 Qt 기반 애플리케이션에서 더 쉽게 사용할 수 있는 API를 제공합니다.
웹 플랫폼은 로컬 파일 및 영구 저장소에 접근할 수 있는 기능을 제공합니다:
- <input type="file">: 사용자가 파일을 선택할 수 있는 네이티브 파일 열기 대화 상자를 표시합니다.
- IndexedDB는 영구적인 로컬 저장소(브라우저 외부에서는 접근할 수 없음)를 제공합니다.
Emscripten은 POSIX와 유사한 API를 갖춘 여러 파일 시스템을 제공합니다. 여기에는 다음이 포함됩니다:
- 파일을 메모리 내에 저장하는 MEMFS 일시적 파일 시스템
- IndexedDB를 사용하여 파일을 저장하는 IDBFS 영구 파일 시스템
Emscripten은 앱 시작 시 임시 MEMFS 파일 시스템을 "/"에 마운트합니다. 즉, ` QFile `를 사용할 수 있으며, 기본적으로 메모리에 파일을 읽고 씁니다. 이 파일 시스템은 브라우저를 다시 로드하면 유지되지 않습니다.
웹 페이지는 로컬 파일 시스템에 직접 액세스할 수 없기 때문에, Qt는 Qt for WebAssembly와 함께 사용할 수 있는 QFileDialog API를 제공합니다.
- QFileDialog::getOpenFileContent()는 사용자가 파일을 선택할 수 있는 네이티브 파일 대화 상자를엽니다 .
- QFileDialog::saveFileContent() 는 파일 다운로드를 통해 파일을 로컬 파일 시스템에 저장합니다.
클립보드 접근
Qt는 시스템 클립보드를 통해 텍스트, URL, 알려진 파일 유형 및 이미지를 복사하고 붙여넣을 수 있도록 지원하지만, 웹 샌드박스로 인해 일부 차이가 있습니다. 임의의 application/octet-stream 바이너리 데이터는 지원하지 않습니다. 일반적으로 클립보드에 접근하려면 사용자 권한이 필요하며, 이는 입력 이벤트(예: CTRL+c)를 처리하거나 클립보드 API를 사용하여 얻을 수 있습니다.
글꼴
Qt WASM 모듈에는 "Bitstream Vera Sans"(대체 글꼴), "DejaVu Sans", "DejaVu Sans Mono" 등 3가지 내장 글꼴이 포함되어 있습니다.
이 글꼴들은 제한된 문자 집합을 제공합니다. Qt는 추가 글꼴을 추가하기 위한 몇 가지 옵션을 제공합니다:
그 중 하나는 QML에서 ` FontLoader `을 사용하는 것으로, URL을 통해 글꼴을 불러오거나 Qt 리소스 시스템 (일반 데스크톱 앱과 동일한 방식) 을 사용하여 글꼴을 불러올 수 있습니다.
글꼴을 사용하는 또 다른 방법은 ` QFontDatabase::addApplicationFontFromData`을 통해 추가하는 것입니다.
접근성 및 화면 낭독기
Qt for WebAssembly는 화면 리더에 대한 기본적인 지원을 제공합니다. 버튼이나 체크박스 같은 간단한 UI 요소는 정상적으로 작동하지만, 테이블이나 트리 뷰와 같은 복잡한 UI 요소의 경우 일부 기능이 지원되지 않을 수 있습니다. Qt Widgets 와 Qt Quick 모두 지원됩니다.
다음의 스크린 리더 및 브라우저 조합은 테스트를 거쳐 정상 작동하는 것으로 확인되었습니다. 다른 브라우저와 스크린 리더에서도 작동할 수 있습니다.
- macOS의 Safari에서 VoiceOver
- macOS의 Chrome에서 VoiceOver 사용
이 접근성 기능은 Qt UI 요소에 대한 접근성 정보를 제공하는 “shadow” HTML 요소를 생성하여 작동합니다. 이 기능은 기본적으로 비활성화되어 있습니다. 최종 사용자는 스크린 리더를 사용하여 “스크린 리더 활성화” 버튼을 선택함으로써 이 기능을 활성화할 수 있습니다. 활성화되면 웹 페이지에 접근성 요소가 추가됩니다.
드래그 앤 드롭
- 드롭 기능이 지원됩니다(브라우저에서 지원하는 파일 및 MIME 유형).
- 애플리케이션 내부 드래그 앤 드롭이 지원됩니다.
- 애플리케이션 외부로 드래그하는 기능은 지원되지 않습니다.
애플리케이션 시작 및 이벤트 루프
Qt for WebAssembly는 애플리케이션이 ` QApplication ` 객체를 생성하고 `exec` 함수를 호출하는 표준 Qt 시작 방식을 지원합니다:
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QWindow appWindow;
return app.exec();
}위의 exec() 호출은 일반적으로 애플리케이션이 종료될 때까지 이벤트를 처리하며 블록 상태를 유지합니다. 하지만 메인 스레드의 블록을 허용하지 않는 웹 플랫폼에서는 이러한 방식이 불가능합니다. 대신, 각 이벤트를 처리한 후에는 제어권을 브라우저의 이벤트 루프로 반환해야 합니다.
Qt는 스택을 유지하면서 exec()가 메인 스레드의 제어권을 브라우저에 반환하도록 함으로써 이 문제를 해결합니다. 애플리케이션 코드의 관점에서 보면, exec() 함수에 진입하고 이벤트 처리는 평소와 같이 이루어집니다. 그러나 exec() 호출은 애플리케이션 종료 시에도 결코 반환되지 않습니다.
브라우저가 애플리케이션 종료 시 애플리케이션 메모리를 해제하기 때문에 이 동작은 일반적으로 허용됩니다. 하지만 이는 애플리케이션 객체가 누수되어 소멸자가 실행되지 않으므로, 종료 코드가 실행되지 않음을 의미합니다.
Emscripten은 main()이 반환될 때 런타임을 종료하지 않기 때문에, main()을 비동기식으로 재작성하여 이 문제를 피할 수 있습니다. 그러면 애플리케이션 코드는 exec() 호출을 생략하고, 최상위 창 및 애플리케이션 객체를 삭제함으로써 Qt를 정상적으로 종료할 수 있습니다.
QApplication *g_app = nullptr;
AppWindow *g_appWindow = nullptr;
int main(int argc, char **argv)
{
g_app = new QApplication(argc, argv);
g_appWindow = new AppWindow();
return 0;
}참고: 이는 Asyncify나 JSPI를 사용하지 않는 경우에 해당합니다. Asyncify나 JSPI를 사용할 때는 main()에서 exec()를 호출해야 합니다.
Asyncify 및 JSPI
WebAssembly용 기본 Qt 빌드는, 동기식 C++ 코드가 비동기식 JavaScript API를 호출하는 것을 방지하는 웹 플랫폼 제한 사항으로 인해, ` QEventLoop::exec()` 또는 ` QDialog::exec()` 호출과 같은 이벤트 루프 재진입을 지원하지 않습니다.
asyncify/JSPI가 필요한 기능은 다음과 같습니다:
- QDialogs, 반환 값이 있는 QMessageBoxes.
- 드래그 앤 드롭(특히 드래그).
- 중첩된/2차 이벤트 루프 exec().
Emscripten은 Asyncify 기능을 사용하여 이러한 제한 사항을 우회할 수 있도록 지원합니다. 이 기능은 두 가지 버전으로 제공됩니다:
- WebAssembly 코드 후처리 단계를 통해 구현된 Asyncify.
- WebAssembly JS Promise 통합 기능을 사용하여 구현된 JSPI.
각 옵션을 활성화하는 방법과 관련된 장단점에 대한 자세한 내용은 아래 섹션을 참조하십시오. 간단히 말해, Asyncify는 현재 사용할 수 있지만 빌드 시간 증가, 바이너리 크기 증가, 런타임 성능 저하 등의 오버헤드를 초래합니다. JSPI는 오버헤드가 거의 없거나 전혀 없지만, 아직 모든 브라우저에서 지원되지는 않습니다.
참고: 이전 버전의 Qt for WebAssembly에서는 main() 내에서 application.exec()를호출하는 것이 선택 사항이었으나, 현재는 Asyncify 또는 JSPI를 사용할 때 필수 사항입니다.
Asyncify
링크러 옵션에 “-sASYNCIFY -Os” 플래그를 추가하여 asyncify를 활성화합니다:
CMake:
target_link_options(<your target> PUBLIC -sASYNCIFY -Os)qmake:
QMAKE_LFLAGS += -sASYNCIFY -Osasyncify를 활성화하면 바이너리 크기가 커지고 CPU 사용량이 증가하는 등의 오버헤드가 발생합니다. 오버헤드를 최소화하려면 최적화 옵션을 활성화한 상태로 빌드하십시오.
JSPI (JS Promise Integration)
Asyncify와 달리 JSPI를 사용하려면 소스 코드에서 Qt를 빌드해야 합니다. 이를 활성화하려면 Qt configure 스크립트에 -feature-wasm-jspi 및 -feature-wasm-exceptions 플래그를 전달하십시오. (JSPI는 Emscripten의 기본 에뮬레이션 예외와 호환되지 않습니다.)
그런 다음, 링커 옵션에 -sJSPI 플래그를 추가하여 애플리케이션에서 JSPI를 활성화하십시오:
CMake:
target_link_options(<your target> PUBLIC -sJSPI)qmake:
QMAKE_LFLAGS += -sJSPI디버깅 및 프로파일링
Wasm 디버깅은 브라우저의 자바스크립트 콘솔에서 수행됩니다. Qt Creator 내에서 Wasm 애플리케이션을 직접 디버깅하는 것은 불가능합니다.
- Qt 디버깅 및 로깅 출력은 JavaScript 콘솔에 표시되며, 이는 브라우저의 “개발자 도구” 등을 통해 확인할 수 있습니다.
- 코드 단계별 실행을 위한 소스 맵은
--device-option QT_WASM_SOURCE_MAP=1을 사용하여 재구성하고 디버그 빌드를 생성함으로써 Qt용으로 만들 수 있습니다. 애플리케이션은 QT_WASM_SOURCE_MAP을 재정의하고 다음을 설정할 수 있습니다: set(QT_WASM_SOURCE_MAP_BASE "http://localhost:8000/") - 프로그램이 -g 플래그와 함께 링크된 경우 DWARF를 통한 디버그 심볼도 활성화됩니다(Chrome에서 테스트됨).
- 이를 위해서는 다음 확장 프로그램이 필요합니다: https://goo.gle/wasm-debugging-extension
- 관련 문서: https://developer.chrome.com/blog/wasm-debugging-2020/
- 모바일 브라우저에서는 원격 디버깅을 사용할 수 있습니다
- 특정 줄에서 실행을 중지하고 프로그래밍 방식으로 브라우저 디버거를 표시하려면, 애플리케이션 소스 코드에 emscripten_debugger(); 함수를 추가할 수 있습니다.
- 프로파일링은 디버그 빌드와 자바스크립트 콘솔의 프로파일링 기능을 사용하여 수행할 수 있습니다. Qt는 디버그 빌드 시 링커 인수에 `
--profiling-funcs`를 추가하여, 프로파일링 시 함수 이름을 보존합니다.
Emscripten 링커 인수를 사용하여 디버깅에 도움이 되는 더 자세한 정보를 출력하도록 설정할 수 있습니다:
- -s LIBRARY_DEBUG=1 (라이브러리 호출 출력)
- -s SYSCALL_DEBUG=1 (시스템 호출 출력)
- -s FS_LOG=1 (파일 시스템 작업 출력)
- -s SOCKET_DEBUG (소켓 및 네트워크 데이터 전송 내역 출력)
CMake:
target_link_options(<your target> PRIVATE -s LIBRARY_DEBUG=1)qmake:
QMAKE_LFLAGS_DEBUG += -s LIBRARY_DEBUG=1최적화
WebAssembly용 Qt는 Emscripten 툴체인을 사용하여 바이너리를 생성하며, 성능과 바이너리 크기에 영향을 미칠 수 있는 다양한 옵션이 있습니다. 자세한 내용은 Emscripten: 코드 최적화를 참조하십시오.
일반 C++ 애플리케이션과 마찬가지로 링커 및 컴파일러 플래그를 전달할 수 있습니다:
target_compile_options(<your target> PRIVATE -oz -flto)
target_link_options(<your target> PRIVATE -flto)QMAKE_CXXFLAGS += -oz -flto
QMAKE_LFLAGS += -flto바이너리 파일의 크기 최소화
원활한 사용자 경험을 제공하려면 WebAssembly 애플리케이션의 다운로드 및 로딩 시간을 단축하는 것이 중요합니다. 애플리케이션 바이너리 크기를 줄이는 것은 다운로드 속도를 높이는 데 중요한 요소 중 하나입니다. 바이너리 크기를 줄이려면 다음 방법을 활용하세요:
- 반드시 릴리스 빌드를 배포하십시오. 디버그 빌드에는 디버그 심볼이 포함되어 있어 크기가 훨씬 큽니다.
- 서버에서 압축을 활성화하십시오.
gzip, Brotli와 같은 가장 일반적인 알고리즘은 Wasm 바이너리에 효과적이며 크기를 대폭 줄일 수 있습니다. - 더 작은 바이너리를 생성할 수 있는 컴파일러 및 링커 플래그(예: '-os', '-oz')를 사용해 보세요. 결과는 특정 애플리케이션에 따라 달라질 수 있습니다.
- 소스 코드에서 Qt for WebAssembly를 컴파일할 때 사용되지 않는 기능을 비활성화하십시오(아래 참조).
기능 제외
WebAssembly 애플리케이션은 기본적으로 Qt 라이브러리에 정적 링크를 수행하므로, 컴파일러가 사용되지 않는 코드를 제거할 수 있습니다. 그러나 Qt의 동적 특성으로 인해 컴파일러가 항상 이러한 최적화를 수행할 수 있는 것은 아닙니다.
소스 코드에서 Qt for WebAssembly를 빌드하는 경우, 기능을 비활성화하여 Qt 바이너리의 크기를 줄일 수 있으며, 결과적으로 .wasm 바이너리의 크기도 줄일 수 있습니다. Qt는 WebAssembly 플랫폼을 위해 기본적으로 일부 기능을 비활성화하지만, 애플리케이션에서 사용하지 않는 기능은 직접 비활성화할 수도 있습니다. 자세한 내용은 비활성화된 기능을 참조하십시오.
다음 기능을 비활성화하면 바이너리 크기를 줄일 수 있습니다(일반적으로 10~15% 정도):
| 구성 인수 | 간략한 설명 |
|---|---|
| -no-feature-cssparser | 캐스케이딩 스타일 시트(CSS) 파서. |
| -no-feature-datetimeedit | 날짜 및 시간 편집 (datetimeparser에 의존). |
| -no-feature-datetimeparser | 날짜-시간 텍스트 구문 분석. |
| -no-feature-dockwidget | 위젯을 QMainWindow 내에 도킹하거나, 바탕화면에서 최상위 창으로 띄우기. |
| -no-feature-gestures | 제스처용 프레임워크. |
| -no-feature-mimetype | MIME 유형 처리. |
| -no-feature-qml-network | 네트워크 투명성. |
| -no-feature-qml-list-model | ListModel QML 유형. |
| -no-feature-qml-table-model | TableModel QML 유형. |
| -no-feature-quick-canvas | 캔버스 항목. |
| -no-feature-quick-path | 경로 요소. |
| -no-feature-quick-pathview | PathView 항목. |
| -no-feature-quick-treeview | TreeView 항목. |
| -no-feature-style-stylesheet | CSS를 통해 구성할 수 있는 위젯 스타일. |
| -no-feature-tableview | 테이블 뷰의 기본 모델/뷰 구현. |
| -no-feature-texthtmlparser | HTML용 파서. |
| -no-feature-textmarkdownreader | 마크다운(CommonMark 및 GitHub) 리더. |
| -no-feature-textodfwriter | ODF 작성기. |
Wasm 예외
Qt는 기본적으로 예외 처리를 지원하지 않도록 빌드되며, 이 경우 예외가 발생하면 프로그램이 중단됩니다. 소스 코드에서 직접 빌드하고 Qt configure에 -feature-wasm-exceptions 플래그를 전달하면 WebAssembly 예외 처리를 활성화할 수 있습니다. 이렇게 하면 컴파일 및 링크 시점에 컴파일러에 -fwasm-exceptions 플래그가 전달됩니다. Qt는 이전의 JavaScript 기반 예외 구현에 대한 Emscripten의 지원을 활성화하는 기능을 지원하지 않습니다.
내부 구현상의 이유로, 예외가 활성화된 상태에서는 ` QApplication::exec()` 호출이 지원되지 않는다는 점에 유의하십시오. 대신, ‘애플리케이션 시작 및 이벤트 루프’ 섹션에 설명된 대로, 조기 반환하고 `exec()`를 호출하지 않는 형태로 `main()`을 작성하십시오.
공유 라이브러리 및 동적 링크 (기술 미리 보기)
Qt for WebAssembly는 기본적으로 정적 링크를 사용하며, 이 경우 애플리케이션은 Qt 라이브러리와 애플리케이션 코드를 포함하는 단일 WebAssembly 파일로 배포됩니다. 동적 링크는 각 라이브러리와 플러그인을 개별적으로 배포하는 대체 빌드 모드입니다.
예를 들어, Qt Quick 를 사용하는 애플리케이션은 다음과 같은 라이브러리와 플러그인을 활용할 수 있습니다:
- <qtpath>/lib/libQt6Core.so
- <qtpath>/lib/libQt6Gui.so
- <qtpath>/lib/libQt6Qml.so
- <qtpath>/lib/libQt6Quick.so
- <qtpath>/plugins/imageformats/libqjpeg.so
- <qtpath>/plugins/imageformats/libqjgif.so
- <qtpath>/qml/QtQuick/Window/libquickwindowplugin.so
6.12 버전의 동적 링크 지원 기능은 기술 미리보기(Technology Preview) 단계에 있습니다. 이 구현은 프로토타이핑 및 평가 용도로는 적합하지만, 실제 운영 환경에서는 사용하지 않는 것이 좋습니다. 현재 다음과 같은 제한 사항이 있습니다:
- 멀티스레딩이 지원되지 않습니다.
- Asyncify는 지원되지 않습니다.
동적 링크로 빌드된 Qt 애플리케이션의 경우, 바이너리 파일과 함께 qt_plugins.json 및 qt_qml_imports.json이라는 두 개의 추가 파일이 있어야 합니다. 이 파일들은 애플리케이션 시작 시 로드될 공유 라이브러리 목록을 지정합니다. 이 파일들을 생성하는 데 사용할 수 있는 보조 도구인 wasmdeployqt가 있습니다. 이 도구의 사용법을 확인하려면 --help 플래그와 함께 실행하여 도구 실행에 필요한 필수 플래그와 사용 예제를 확인할 수 있습니다.
애플리케이션을 호스팅하는 웹 서버에는 Qt 공유 라이브러리가 설치되어 있어야 합니다. 이를 위해 Qt 설치 폴더의 내용을 웹 서버로 복사하거나 파일 시스템 링크를 생성하면 됩니다.
빠른 시작
빌드 및 배포 절차는 정적 WASM 및 공유 데스크톱 빌드와는 약간 다릅니다. 전체 애플리케이션 빌드로 넘어가기 전에 간단한 예제부터 시작해 보시기 바랍니다.
- 소스 코드에서 Qt를 빌드하고, Qt configure 스크립트에 `
-shared` 옵션을 전달하십시오. '-prefix' 옵션을 사용하여 설치 디렉터리를 설정하십시오. - 1단계에서 빌드한 Qt를 사용하여 애플리케이션을 빌드하십시오.
- 애플리케이션 빌드 디렉터리에서 배포 도구를 실행하여 플러그인 사전 로딩 목록을 생성하십시오.
<qtpath>/qtbase/bin/wasmdeployqt --qt-wasm-dir=<Path to the Qt for WebAssembly directory> --qml-root-path=<Root directory for QML files> --qt-host-dir=<Path to the Qt host directory>
공유 라이브러리 배포에 대한 심층 설명
Qt의 공유 라이브러리 빌드는 두 단계로 배포되는데, 첫 번째 단계에서는 웹 서버에서 Qt 및 애플리케이션 빌드를 다운로드할 수 있도록 하고, 두 번째 단계에서는 애플리케이션 시작 시 필요한 Qt 플러그인과 Qt Quick 임포트를 다운로드합니다.
첫 번째 단계에서는 웹 서버에서 Qt 설치 파일을 다운로드할 수 있도록 설정합니다. 웹 서버 설정의 세부 사항에 따라 이를 수행하는 방법은 다양할 수 있습니다. 공통적으로 Qt 로더는 애플리케이션을 로드하는 HTML 파일을 기준으로 “qt”라는 이름의 디렉터리에서 Qt 라이브러리와 플러그인을 찾기를 기대합니다.
배포 과정의 일환으로 이미 애플리케이션을 웹 서버에 복사하고 있다면, Qt도 함께 복사하는 것이 한 가지 방법입니다. 개발 단계에서 흔히 그렇듯이 빌드 디렉터리에서 직접 애플리케이션을 제공하는 경우, Qt에 대한 심볼릭 링크를 생성하는 것이 효과적일 수 있습니다.
두 번째 단계를 준비하기 위해 플러그인 및 Qt Quick 임포트와 같은 Qt 구성 요소에 대한 사전 로딩 목록을 생성하십시오. 사전 로딩을 통해 애플리케이션 시작 시 필요한 모든 Qt 구성 요소를 사용할 수 있게 됩니다. 구성 요소를 필요에 따라 다운로드하는 지연 로딩도 가능하지만, 여기서는 다루지 않습니다.
사전 로딩은 Qt 자바스크립트 로더에 의해 구현되며, 이 로더는 웹 서버에서 파일을 다운로드하여 Emscripten이 제공하는 메모리 내 파일 시스템으로 전송합니다. 다운로드할 파일은 JSON 형식의 다운로드 목록을 사용하여 지정됩니다. Qt는 사전 로딩 목록을 생성하는 도구를 제공하며, 자세한 내용은 위의 ‘빠른 시작’ 섹션을 참조하십시오.
알려진 문제
- 중첩된 이벤트 루프는 실험적인 Asyncify 또는 JSPI 기능을 사용할 때만 지원됩니다.
- 인쇄 기능은 지원되지 않습니다.
- QDnsLookup 웹 샌드박스 때문에 조회(lookups) 및 SSL 인증서( QSsl ) 기능이 작동하지 않으며 지원되지 않습니다. 브라우저가 DNS 조회와 SSL 인증서를 처리합니다. DNS 조회가 필요한 애플리케이션의 경우 HTTP를 통한 DNS(DNS over HTTP)를 사용할 수 있습니다.
- QTcpSockets는 사용할 수 있지만, 모든 POSIX 소켓 함수가 프록시 처리되는 것은 아닙니다. Websockify와 같은 WebSockets 서버 프록시를 사용해야 합니다.
- 이 플랫폼에서는 모든 Q*Server 클래스가 지원되지 않습니다.
- QWebSocket Emscripten에서는 메인 스레드에서만 연결이 지원됩니다.
- WebAssembly용 QWebSockets는 웹 페이지와 브라우저에서 사용할 수 있는 API가 이 기능을 노출하지 않기 때문에 ping 또는 pong 프레임 전송을 지원하지 않습니다.
- QtWebsockets를 사용하려면 QtMqtt 를 사용하기 위해 서브프로토콜을 'mqtt'로 설정해야 할 수 있습니다. QWebSocket 를 열 때는 QWebSocketHandshakeOptions 를 사용하십시오.
- 글꼴: Wasm 샌드박스는 시스템 글꼴에 대한 액세스를 허용하지 않습니다. 글꼴 파일은 애플리케이션과 함께 배포되어야 하며, 예를 들어 Qt 리소스에 포함하거나 다운로드를 통해 제공해야 합니다. Qt for WebAssembly 자체에는 이러한 글꼴 중 하나가 내장되어 있습니다.
- 체크박스 등 일부 Qt Quick Controls 2 컴포넌트에서 초기화되지 않은 그래픽 메모리의 잔여 현상이 나타날 수 있습니다. 이는 HighDPi 디스플레이에서 가끔 관찰될 수 있습니다.
- Wasm 플랫폼은 해당 기능을 제공하지 않으므로 Windows 및 macOS용 네이티브 스타일은 지원되지 않습니다.
- "wasm-ld: error: initial memory too small"과 같은 링크 시간 오류가 발생하면 초기 메모리 크기를 조정해야 합니다. QT_WASM_INITIAL_MEMORY를 사용하여 초기 크기를 kb 단위로 설정할 수 있으며, 이 값은 64KB(65536)의 배수여야 합니다. 기본값은 50MB입니다. CMakeLists.txt에서: set_target_properties(<target> PROPERTIES QT_WASM_INITIAL_MEMORY "150MB")
- CMakeLists.txt의 add_executable은 <target>.html 파일을 생성하지 않으며 qtloader.js를 복사하지도 않습니다. 대신 qt_add_executable을 사용하십시오.
기타 주제
Qt 구성 옵션 참조
소스 코드에서 WebAssembly용 Qt를 빌드할 때 다음 configure 옵션이 적용됩니다.
| configure 인수 | 간략한 설명 |
|---|---|
| -feature-thread | 멀티스레드 Wasm. |
| -feature-wasm-simd128 | WebAssembly SIMD 지원 활성화. |
| -feature-wasm-exceptions | WebAssembly 예외 지원을 활성화합니다. |
| -device-option QT_EMSCRIPTEN_ASYNCIFY=1 | asyncify 지원을 활성화합니다. |
| -device-option QT_EMSCRIPTEN_ASYNCIFY=2 | asyncify(JSPI) 지원을 활성화합니다. |
Qt는 바이너리 크기를 줄이기 위해 WebAssembly 플랫폼에서 기본적으로 일부 기능을 비활성화합니다. WebAssembly용 Qt를 구성할 때 특정 기능을 명시적으로 활성화할 수 있습니다:
| 구성 인수 | 간략한 설명 |
|---|---|
| -feature-topleveldomain | 도메인이 최상위 도메인인지 확인하는 기능을 지원합니다. |
일반적인 다운로드 크기
예상 용량(다운로드 크기): 컴파일러에서 생성된 Wasm 모듈은 크기가 클 수 있지만 압축 효율이 높습니다:
| 예시 | gzip | brotli |
|---|---|---|
| helloglwindow (QtCore + QtGui) | 2.8M | 2.1M |
| wiggly widget (QtCore + QtGui + QtWidgets) | 4.3M | 3.2M |
| SensorTag (QtCore + QtGui + QtWidgets + QtQuick + QtCharts) | 8.6M | 6.3M |
압축은 일반적으로 웹 서버 측에서 표준 압축 기능을 사용하여 처리됩니다. 즉, 서버가 파일을 자동으로 압축하거나 미리 압축된 버전의 파일을 가져옵니다. 일반적으로 Wasm 파일에 대해 특별한 처리가 필요하지 않습니다.
자세한 내용은 ‘바이너리 크기 최소화’를 참조하십시오.
예시
웹 브라우저에서 실행되는 Qt 애플리케이션의 예제 및 데모: Qt 데모
외부 자료
라이선스
WebAssembly용 Qt는 The Qt Company에서 제공하는 상용 라이선스에 따라 이용할 수 있습니다. 또한 GNU 일반 공중 사용 허가서(GNU General Public License) 버전 3에 따라 이용할 수도 있습니다. 자세한 내용은 Qt 라이선싱을 참조하십시오.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS 및 https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy도 참조하십시오 .
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.