$ git clone https://github.com/nayuki/QR-Code-generator
$ cd QR-Code-generator/c
$ clang --target=wasm32 -std=c99 -c qrcodegen.c
qrcodegen.c:24:10: fatal error: 'assert.h' file not found
#include <assert.h>
^~~~~~~~~~
1 error generated.
A QR Code Canister
Since we’re using a stock C compiler, we can readily turn any C project into a canister provided it is mostly self-contained. A good example is Project Nayuki’s QR Code generator library.
Let’s make a canister that uses this library to generate QR codes.
Trial and Error
We try compiling the C version of this library to wasm:
A missing assert.h is easy to work around:
$ echo '#define assert(x) ((void)(0))' > assert.h
But then we find we’re missing stdlib.h:
$ clang -isystem . --target=wasm32 -std=c99 -c qrcodegen.c
qrcodegen.c:26:10: fatal error: 'assert.h' file not found
#include <stdlib.h>
^~~~~~~~~~
1 error generated.
The quickest way to progress:
$ touch stdlib.h $ clang -isystem . --target=wasm32 -std=c99 -c qrcodegen.c
It then complains about string.h, so:
$ touch string.h $ clang -isystem . --target=wasm32 -std=c99 -c qrcodegen.c
The error messages are a bit of a mouthful. The salient parts:
$ clang -isystem . --target=wasm32 -std=c99 -c qrcodegen.c 2>&1 | grep note qrcodegen.c:132:19: note: include the header <string.h> or explicitly provide a declaration for 'strlen' qrcodegen.c:231:2: note: include the header <string.h> or explicitly provide a declaration for 'memset' qrcodegen.c:390:3: note: include the header <string.h> or explicitly provide a declaration for 'memmove' qrcodegen.c:466:15: note: include the header <stdlib.h> or explicitly provide a declaration for 'abs' qrcodegen.c:706:17: note: include the header <stdlib.h> or explicitly provide a declaration for 'labs' qrcodegen.c:824:7: note: include the header <string.h> or explicitly provide a declaration for 'strchr' qrcodegen.c:884:3: note: include the header <string.h> or explicitly provide a declaration for 'memcpy'
We tell the compiler what it wants to hear:
int abs(int);
long labs(long);
#include <stddef.h>
size_t strlen(const char*);
void *memset(void *, int, size_t);
void *memchr(const void *, int, size_t);
char *strchr(const char *, int);
void* memmove(void *, const void *, size_t);
void* memcpy(void *, const void *, size_t);
int memcmp(const void *, const void *, size_t);
It compiles!
$ clang -isystem . --target=wasm32 -std=c99 -c qrcodegen.c
We implement the functions we declared. We take this opportunity to declare and
define memcmp, because we’ll need it in a later app.
#include <stddef.h>
size_t strlen(const char* s) {
const char *p = s;
while (*p) p++;
return p - s;
}
void *memset(void *s, int c, size_t n) {
char *p = s;
while (n--) *p++ = c;
return s;
}
char *strchr(const char *p, int c) {
while(*p) if (*p++ == c) return (char *)p;
return 0;
}
void *memchr(const void *s, int c, size_t n) {
const char *p = s;
while(n--) if (*p++ == c) return (void *)p;
return 0;
}
void* memcpy(void *dst, const void *src, size_t n) {
char *p = dst;
const char *s = src;
while (n--) *p++ = *s++;
return dst;
}
void* memmove(void *dst, const void *src, size_t n) {
char *d = dst;
const char *s = src;
if (d <= s) {
while (n--) *d++ = *s++;
} else {
s += n;
d += n;
while (n--) *d-- = *s--;
}
return dst;
}
int memcmp(const void *s1, const void *s2, size_t n) {
for(size_t i = 0; i < n; i++) {
const unsigned char* l = s1;
const unsigned char* r = s2;
if (l[i] < r[i]) return -1;
if (l[i] > r[i]) return 1;
}
return 0;
}
int abs(int n) { return n < 0 ? -n : n; }
long labs(long n) { return n < 0 ? -n : n; }
Trivia: according to the C
standard, our memmove() implementation involves undefined behaviour.
Fortunately, no self-respecting compiler respects that part of the standard!
We compile our homebrew version of a tiny excerpt of the C standard library to wasm:
$ clang --target=wasm32 -std=c99 -c libc.c
[Back when I ran Clang 8, I didn’t have to reinvent as much of the standard C library. My guess is that the old version would use the standard system include files even if it was targeting wasm32.]
Canister Source
Now we can write a canister using this library. We copy doBasicDemo() from
the library test program qrcodegen-demo.c and modify it for the IC: we
replace fputs with reply_append, and call ic0.msg_arg_data_size and
ic0.msg_arg_data_copy to copy the input argument to the buffer buf.
qr.c:
#include "qrcodegen.h"
#define IMPORT(m,n) __attribute__((import_module(m))) __attribute__((import_name(n)));
#define EXPORT(n) asm(n) __attribute__((visibility("default")))
typedef unsigned u32;
void reply_append(void*, u32) IMPORT("ic0", "msg_reply_data_append");
void reply(void) IMPORT("ic0", "msg_reply");
u32 arg_size(void) IMPORT("ic0", "msg_arg_data_size");
void arg_copy(void*, u32, u32) IMPORT("ic0", "msg_arg_data_copy");
void printQr(const uint8_t qrcode[]) {
int size = qrcodegen_getSize(qrcode);
int border = 4;
for (int y = -border; y < size + border; y++) {
for (int x = -border; x < size + border; x++) {
reply_append(qrcodegen_getModule(qrcode, x, y) ? "##" : " ", 2);
}
reply_append("\n", 1);
}
reply_append("\n", 1);
}
void basic(const char *text) {
enum qrcodegen_Ecc errCorLvl = qrcodegen_Ecc_LOW;
uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX];
uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX];
bool ok = qrcodegen_encodeText(text, tempBuffer, qrcode, errCorLvl,
qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
if (ok)
printQr(qrcode);
}
enum { max = 2048 };
void go() EXPORT("canister_update go");
void go() {
u32 n = arg_size();
n = n > max ? max : n;
char buf[max + 1];
arg_copy(buf, 0, n);
buf[n] = 0;
basic(buf);
reply();
}
We compile and link with the other object files:
$ clang --target=wasm32 -std=c99 -c qr.c $ wasm-ld --no-entry --export-dynamic --allow-undefined libc.o qrcodegen.o qr.o -o qr.wasm
As before, we add a suitable canisters entry to dfx.json:
{"qr":
{"type":"custom"
,"build":""
,"candid":"did.not"
,"wasm":"qr.wasm"
}}
where did.not is an empty file, and run dfx deploy.
We now have a canister that produces a QR code from a given NUL-terminated string. We test it, mindful that in raw mode, both input and output are hex-enocded:
$ dfx canister call qr go --type raw `echo "Hello, QR Code!" | xxd -p` --output raw | xxd -r -p
############## ###### ## ##############
## ## #### ## ##
## ###### ## ## #### ## ###### ##
## ###### ## #### ## ## ###### ##
## ###### ## ########## ## ###### ##
## ## ## ##
############## ## ## ## ##############
####
######## ## ## #### ## ###### ##
#### ## ## #### #### ##
#### ## ###### ## ###### ####
###### ## #### ##
#### ######## ###### ## ##
## ###### ## ## ## ##
############## ###### #### ##
## ## ## ## ####
## ###### ## ## ## ######## ##
## ###### ## ###### #### ####
## ###### ## ## #### ## ##
## ## ## ########## ##
############## ## ## ## ## ##
$
With the right font and colours, if held just right, modern smartphones can check this QR code works as intended.