--- title: Компиляция кода C/C++ в WebAssembly slug: WebAssembly/C_to_wasm translation_of: WebAssembly/C_to_wasm --- <div>{{WebAssemblySidebar}}</div> <p class="summary">После того как вы написали код на C / C ++, вы можете скомпилировать его в WebAssembly, например, с помощью инструмента <a href="/en-US/docs/Mozilla/Projects/Emscripten">Emscripten</a>. Давайте посмотрим, как это работает.</p> <h2 id="Подготовка_рабочей_среды_для_Emscripten">Подготовка рабочей среды для Emscripten</h2> <p>Первым делом установим компоненты для дальнейшей работы.</p> <h3 id="Необходимые_компоненты">Необходимые компоненты</h3> <p>Установите Emscripten SDK и настройте рабочее окружение используя инструкции: <a href="https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html">https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html </a></p> <h2 id="Компиляция_примера">Компиляция примера</h2> <p>Когда рабочее окружение подготовлено, попробуем собрать пример кода на языке Си при помощи Emscripten. Вам доступно большое количество опций для настройки компиляции, но мы рассмотрим только два основных сценария компиляции с использованием Emscripten:</p> <ul> <li>Компиляция в wasm и создание HTML-страницы для запуска вашего кода, а также JavaScript-кода, необходимого для работы wasm модуля в веб-среде.</li> <li>Просто компиляция в wasm и создание JavaScript-кода.</li> </ul> <p>Мы рассмотрим оба способа ниже.</p> <h3 id="Создание_HTML_и_JavaScript">Создание HTML и JavaScript</h3> <p>Это самый простой способ, который мы рассмотрим. С его помощью вы сможете использовать Emscripten для создания всего что нужно, чтобы ваш код работал в браузере как модуль WebAssembly.</p> <ol> <li>Нам понадобится простой пример для компиляции. Скопируйте следующий код программы на Си и сохраните его в файле <code>hello.c</code> в новой папке на вашем локальном диске: <pre class="brush: cpp">#include <stdio.h> int main(int argc, char ** argv) { printf("Hello World\n"); }</pre> </li> <li>Теперь, используя терминал, перейдите в каталог, в котором находится ваш файл hello.c, и выполните следующую команду: <pre class="brush: bash">emcc hello.c -s WASM=1 -o hello.html</pre> </li> </ol> <p>Рассмотрим параметры, которые мы передали компилятору:</p> <ul> <li><code>-s WASM=1</code> — Указывает, что мы хотим получить wasm модуль. Если не использовать этот параметр, по умолчанию Emscripten просто создаёт <a href="http://asmjs.org/">asm.js</a>;</li> <li><code>-o hello.html</code> — Указывает, что мы хотим, чтобы Emscripten сгенерировал HTML-страницу <code>hello.html</code> запускающую наш код, а также сам модуль wasm и код JavaScript который позволит использовать модуль в веб-среде.</li> </ul> <p>На этом этапе в вашем каталоге должны находится:</p> <ul> <li>Бинарный код модуля wasm (<code>hello.wasm</code>)</li> <li>Файл JavaScript, содержащий код связывающий нативные функции Си и JavaScript/wasm (<code>hello.js</code>)</li> <li>HTML-страница для загрузки, компиляции и инициализации wasm модуля, и отображающий его вывод в браузере (<code>hello.html</code>)</li> </ul> <h3 id="Запуск_вашего_примера">Запуск вашего примера</h3> <p>Теперь, всё что нужно чтобы запустить полученный <code>hello.html</code> в браузере, это поддержка WebAssembly. Он включён по умолчанию в Firefox 52+, Chrome 57+ и последних версиях Opera. Также вы можете использовать модули WebAssembly в Firefox 47+, включив флаг <code>javascript.options.wasm</code> в <em>about:config</em>, или в Chrome 51+ и Opera 38+ перейдя в <em>chrome://flags</em> и включив флаг <em>Experimental WebAssembly.</em></p> <p>Если все работает как планировалось, вы должны увидеть надпись "Hello world" на открывшейся веб-странице и в JavaScript консоли вашего браузера. Поздравляем, вы только что скомпилировали программу на Си в WebAssembly и запустили её в своём браузере!</p> <div class="note"> <p><strong>Примечание</strong>: На самом деле, если просто открыть полученный <code>hello.html</code>, то ничего работать не будет. Подразумевается что все файлы находятся на веб-сервере и вы запускаете страницу через <code>localhost/hello.html</code>. Для этих целей можно использовать отладочный веб-сервер Emscripten. Чтобы его запустить, откройте терминал, перейдите в каталог, в котором находятся ваши файлы и выполните команду <code>emrun hello.html</code></p> </div> <h3 id="Использование_собственного_HTML_шаблона">Использование собственного HTML шаблона</h3> <p>Вы можете использовать собственный шаблон HTML. Давайте посмотрим, как это сделать:</p> <ol> <li> <p>Прежде всего, сохраните следующий код в файле hello2.c в новом каталоге:</p> <pre class="brush: cpp">#include <stdio.h> int main(int argc, char ** argv) { printf("Hello World\n"); }</pre> </li> <li> <p>Найдите файл <code>shell_minimal.html</code> в вашем репозитории emsdk. Скопируйте его в подкаталог <code>html_template</code> внутри вашего нового каталога.</p> </li> <li> <p>Теперь, используя терминал, перейдите в ваш новый каталог и выполните следующую команду:</p> <pre class="brush: bash">emcc -o hello2.html hello2.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html</pre> <p>В этот раз мы использовали немного другие параметры компиляции:</p> <ul> <li>Мы указали <code>-o hello2.html</code>, чтобы компилятор по прежнему генерировал необходимый JavaScript-код и <code>.html</code> файл.</li> <li>Также, мы указали <code>--shell-file html_template/shell_minimal.html </code>чтобы компилятор использовал ваш шаблон для создания HTML страницы запускающей этот пример.</li> </ul> </li> <li> <p>Теперь давайте запустим этот пример. Команда, указанная выше, сгенерирует файл <code>hello2.html</code>, который будет иметь тоже содержание что и шаблон, но с некоторым кодом, добавленным в процесс загрузки сгенерированного wasm, запускающим его и т.д. Откройте его в своём браузере, и вы увидите тот же результат, что и прошлом примере.</p> </li> </ol> <div class="note"> <p><strong>Примечание</strong>: вы можете указать компилятору создавать только JavaScript-кода, без HTML, используя внутри флага <code>-o</code>, <code>.js</code> вместо <code>.html</code> для формата выходного файла, например <code>emcc -o hello2.js hello2.c -O3 -s WASM=1</code>. После этого вы должны создать свой собственный HTML файл с нуля. Однако так делать не рекомендуется — Emscripten требуется большое количество связывающего кода для обработки операций выделения памяти, утечек памяти и других проблем, которые уже включены в предоставляемый шаблон. Намного легче использовать уже готовое решение, чем создавать свои собственные версии самому.</p> </div> <h3 id="Вызов_пользовательской_функции_определённой_в_Си">Вызов пользовательской функции, определённой в Си</h3> <p>Если у вас есть функция определённая в коде на Си, которую вы хотите по необходимости вызывать из JavaScript, то вы можете использовать для этого функцию <code>ccall()</code> из Emscripten, и объявление <code>EMSCRIPTEN_KEEPALIVE</code> которое добавит вашу функцию в список экспортируемых функций (см. <a href="https://kripken.github.io/emscripten-site/docs/getting_started/FAQ.html#why-do-functions-in-my-c-c-source-code-vanish-when-i-compile-to-javascript-and-or-i-get-no-functions-to-process">Почему функции в моем коде исчезают после компиляции и/или я получаю сообщение «Нет функций для обработки»</a>). Давайте посмотрим, как это работает.</p> <ol> <li> <p>Для начала сохраните следующий код в файле <code>hello3.c</code> в новом каталоге:</p> <pre class="brush: cpp">#include <stdio.h> #include <emscripten/emscripten.h> int main(int argc, char ** argv) { printf("Hello World\n"); } #ifdef __cplusplus extern "C" { #endif void EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) { printf("MyFunction Called\n"); } #ifdef __cplusplus } #endif</pre> <p>По умолчанию, код созданный Emscripten, всегда просто вызывает функцию <code>main()</code> , а остальные неиспользуемые функции удаляются. Добавьте определение <code>EMSCRIPTEN_KEEPALIVE</code> перед именем функции чтобы этого не происходило. Также вы должны подключить библиотеку <code>emscripten.h</code> для использования <code>EMSCRIPTEN_KEEPALIVE</code>.</p> <div class="note"> <p><strong>Примечание</strong>: Мы используем блоки <code>#ifdef</code> чтобы, пример оставался рабочим если вы попытаетесь использовать C++ код. Из за различия в правилах преобразования имён между Си и Си++, этот код может сломаться, но мы написали его так, что функция будет рассматриваться как функция Си даже если вы будете использовать Си++.</p> </div> </li> <li> <p>Теперь добавьте <code>html_template/shell_minimal.html</code> в ваш новый каталог, просто для удобства. В настоящем проекте стоит размещать его в специально определённый каталог.</p> </li> <li> <p>Теперь снова займёмся этапом компиляции. Внутри вашего последнего каталога, используя терминал, скомпилируйте ваш Си код следующей командой. (Обратите внимание что при компиляции обязательно нужно использовать опцию NO_EXIT_RUNTIME, иначе после выполнения функции <code>main()</code>, рабочий цикл будет завершён. Это приведёт, например, к вызову функции atexits и дальше будет невозможно использовать наш скомпилированный код. Другими словами это необходимо для правильной эмуляции Си.)</p> <pre class="brush: bash">emcc -o hello3.html hello3.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html -s NO_EXIT_RUNTIME=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall"]'</pre> </li> <li> <p>Если вы снова запустите пример в своём браузере, вы увидите тоже самое что и до этого!</p> </li> <li> <p>Теперь нам нужно вызвать нашу новую функцию <code>myFunction()</code> из JavaScript. Прежде всего, добавьте {{htmlelement("button")}} как показано ниже, чуть выше первого открывающего тега <code><script type='text/javascript'></code>.</p> <pre class="brush: html"><button class="mybutton">Run myFunction</button></pre> </li> <li> <p>Теперь добавьте следующий код в конце первого элемента {{htmlelement("script")}} (чуть выше закрывающего тега <code></script></code>):</p> <pre class="brush: js">document.querySelector('.mybutton').addEventListener('click', function(){ alert('check console'); var result = Module.ccall('myFunction', // name of C function null, // return type null, // argument types null); // arguments });</pre> </li> </ol> <p>Это показывает как использовать <code>ccall()</code> для вызова экспортируемой функции.</p> <h2 id="Смотрите_также">Смотрите также</h2> <ul> <li><a href="http://emscripten.org/">emscripten.org</a> — узнайте больше об Emscripten и разнообразии его настроек.</li> <li><a href="https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#calling-compiled-c-functions-from-javascript-using-ccall-cwrap">Calling compiled C functions from JavaScript using ccall/cwrap</a></li> <li><a href="https://kripken.github.io/emscripten-site/docs/getting_started/FAQ.html#why-do-functions-in-my-c-c-source-code-vanish-when-i-compile-to-javascript-and-or-i-get-no-functions-to-process">Why do functions in my C/C++ source code vanish when I compile to JavaScript, and/or I get No functions to process?</a></li> <li><a href="https://research.mozilla.org/webassembly/">WebAssembly on Mozilla Research</a></li> <li> <p><a href="/en-US/docs/WebAssembly/existing_C_to_wasm">Compiling an Existing C Module to WebAssembly</a></p> </li> </ul>