Emscripten is an open source LLVM to JavaScript compiler. With it, you can compile C and C++ code into JavaScript and run it on the web. And it's easy! This tutorial will show you how to use it.
To use Emscripten and complete this tutorial, you need a few things:
git clone git://github.com/kripken/emscripten.git
. The master branch is fine, it is guaranteed to always be stable. We merge to master only after all tests pass.)If you don't already have those things, get them now.
Operating system notes:
/usr/bin
or /usr/local/bin
.
Additional Notes:
Next, change directory to where you put the Emscripten code, the rest of this tutorial will assume that that is where you are running commands (or, add a path to another location if you prefer).
Before continuing, it's a good idea to make sure the requirements work. Try
clang tests/hello_world.cpp
./a.out
(Add the path to clang if it isn't installed systemwide.) That uses Clang and LLVM to compile a "hello world" app and run it. The second command there should print "hello, world!". Then, test Node.js with
node tests/hello_world.js
which should also print out "hello, world!". (As before, add the path to node if it isn't installed systemwide.)
If you haven't run Emscripten before, run it now with
./emcc
If you are on Windows, you will need to do
python emcc
instead. You will also need to do that if python2
is not defined in your system (python2
allows python 2 and 3 to be installed together on one system, which is increasingly common; as an alternative to python emcc
, you can also add a symlink to python
from python2
). In that case you should also update PYTHON in ~/.emscripten
, see below about that file.
"emcc" is the "Emscripten compiler frontend", an easy way to use Emscripten basically as a drop-in replacement for a standard compiler like gcc.
The first time you run emcc (or any other of the Emscripten tools), it will create a settings file at ~/.emscripten
(~
is your user's home directory) and exit. You should edit that file now, changing the directory locations of LLVM and Node to the right places in your setup (specifically, edit LLVM_ROOT
and NODE_JS
). If those paths are not right, Emscripten will not find LLVM, Clang or Node.js and it will fail. Look at the comments in that file that explain what the settings are and which you need to change.
After setting those paths, run ./emcc
again. It should do some sanity checks which test the specified paths in ~/.emscripten
. If they don't all pass, you might have a typo somewhere. When everything is set up properly, running ./emcc
should tell you emcc: no input files
(since we didn't specify any input files), and you can proceed to the next section in this tutorial.
You can now compile your first file! First, let's build the same "hello world" C++ file from before, but this time using Emscripten:
./emcc tests/hello_world.cpp
There should now be an a.out.js
file in the current directory. Run it with
node a.out.js
and it should print "hello, world!" as expected.
EMCC_DEBUG=1
in the environment, which will print out information that can help pinpoint the problem.Emscripten can also generate HTML with embedded JavaScript. Try this command:
./emcc tests/hello_world_sdl.cpp -o hello.html
By specifying that the output is an HTML file, you have told Emscripten to generate a complete HTML page. In this case, the source code uses the SDL API to show a colored cube. Open the web page in your web browser to see it (it should work in any browser that supports the Canvas element.)
Your C/C++ code can access files using the normal libc API (stdio: fopen, etc.). Try this command:
./emcc tests/hello_world_file.cpp -o hello.html --preload-file tests/hello_world_file.txt
Open hello.html
in a web browser and you will see the data from a file being written out. (Note: Chrome is unable to do file://
XHRs, so for hello.html
to work in that browser you need a webserver, for example python -m SimpleHTTPServer 8888
and then open localhost:8888/hello.html
.) Open tests/hello_world_file.cpp
to see the C++ source code, and tests/hello_world_file.txt
to see the data. The --preload-file
option will automatically preload the file before running the compiled code. This is useful because loading data from the network cannot be done synchronously in browsers outside Web Workers. Almost all C/C++ code is synchronous, so preloading is the simplest solution.
For an example of files with SDL, go to tests/ and run
../emcc sdl_image.c --preload-file screenshot.jpg -o a.html
Then browse to a.html. Note that we must run the command from tests/, since file preloading will generate files in the virtual filesystem that correspond to the current filesystem as it is seen at compile time. When we run the command in tests/, then the file is accessible as screenshot.jpg
and not tests/screenshot.jpg
, and the former is what the code expects.
See also: Synchronous virtual XHR-backed file-system usage.
Emscripten will by default generate unoptimized code, just like gcc does. You can generate slightly-optimized code with -O1
, for example
./emcc -O1 tests/hello_world.cpp
The "hello world" code here doesn't really need to be optimized, so you won't see a difference in speed when running it. But, you can look at the generated code to see the differences: -O1
applies several minor optimizations to the code (simple ones that don't increase compilation time), and removes some runtime assertions. For example, printf
will have been replaced by puts
in the generated code.
Further optimizations are done in -O2
,
./emcc -O2 tests/hello_world.cpp
If you inspect the generated code now, you will see it looks very different.
You can also try the asm.js code generation mode in emscripten, using
./emcc -O2 -s ASM_JS=1 tests/hello_world.cpp
This structures the generated code in a way that allows JavaScript engines to execute it at very high speed (for more see the asm.js site).
Emscripten has an extensive test suite. You can run it with
python tests/runner.py
This will take a long time, perhaps several hours - there are many many tests! (You can make it faster by parallelizing the tests using nose, see the comment at the top of runner.py
.) You can run an individual test as follows:
python tests/runner.py test_hello_world
If you want to view the generated code from that individual test, do EMCC_DEBUG=1 python tests/runner.py test_hello_world
, and then you can look inside the temp directory (TEMP_DIR/emscripten_temp
, where TEMP_DIR
is defined in ~/.emscripten
- by default it is /tmp
). Note that you can use EMCC_DEBUG
with emcc in general, not just with the test runner - it tells emcc to save the internal code generation stages.
Note that Node.js cannot run 100% of the tests in the suite; if you care about running them all, you should get the SpiderMonkey shell (a recent trunk version).
You can run the Emscripten benchmarks using
python tests/runner.py benchmark
This will compile a sequence of benchmarks and run them several times, reporting averaged statistics including a comparison to how fast the same code runs when compiled to a native executable.
The goal in this tutorial is to show you how to use emcc to compile code to JavaScript. The commands are very simple, and normally you don't need to understand what goes on underneath. However, if you're curious or you want to do something more advanced with Emscripten, then understanding more about how it works can be useful.
The main 'under the hood' topic to be aware of is that emcc is a cross-compiler: You are on a 'normal' OS, running native code, but using emcc you are building for a different environment, JavaScript. Other examples of cross-compiling are building for an ARM phone on an x86 desktop, etc. When cross-compiling, the thing to keep in mind is that you need to build with settings for the target platform, not the one you are currently on. For that reason, Emscripten (and other cross-compilers) ship with a complete build environment, including system headers and so forth. When you run emcc, it does not use your /usr/include directory, instead it uses the system headers bundled with Emscripten (in system/include). One thing to be aware of is if you build a project that has hardcoded includes, for example -I/usr/include/something
: Using system headers that way is dangerous when you are cross-compiling, since the headers are meant for your local system, not for the platform you are actually building for.
The Emscripten compiler (the core code called by emcc that translates LLVM assembly to JavaScript) has various options, which sometimes are useful to modify. To see the options look in src/settings.js
, they appear there with descriptions of what they do in comments. To modify a setting, use the -s
option to emcc, for example
emcc source.cpp -s TOTAL_STACK=10000000
This invocation of emcc will generate JavaScript that sets aside a lot of space for the stack.
After finishing this tutorial, here are some general tips for using Emscripten:
--pre-js
option to emcc works, search for --pre-js
in the test suite (tests/
, and usually the result will be in tests/runner.py
).src/settings.js
which describes the compiler options, and system/include/emscripten/emscripten.h
which describes JS-specific C APIs that your C/C++ programs can use when compiled with emscripten.或是邮件反馈可也:
askdama[AT]googlegroups.com
订阅 substack 体验古早写作:
关注公众号, 持续获得相关各种嗯哼: