(Part 2)How does Wasm module interact with JavaScripte in browser?
Reference:
- WebAssembly in Action. Chapter 4.2
Part1中为了使得JS代码可以直接调用Wasm函数,并传递内存,使用了Emscripten提供的ccall
,UTF8ToString
,_malloc
和
_free
函数,这些helper functions提供了对标准JavaScripte
WebAssembly API的封装。若Emscripten不生成JS plumbing
code,即没有这些help functions时,使用下面的方式来使JS和Wasm
Module交互:
1. 修改代码
因为原始的validate.cpp代码中使用了一些例如strlen
的C/C++标准库函数,当让Emscripten生成side
module时其并不会生成相应的函数,并且,此时并没有相应的_malloc
和
_free
函数了,我们需要自己实现。
下面首先实现自己的库函数,创建一个side_module_system_functions.h
:
#pragma once
#ifndef
SIDE_MODULE_SYSTEM_FUNCTIONS_H_
#define SIDE_MODULE_SYSTEM_FUNCTIONS_H_
#include <stdio.h>
void InsertIntoAllocatedArray(int new_item_index, int offset_start, int size_needed);
int create_buffer(int size_needed);
void free_buffer(int offset);
char* strcpy(char* destination, const char* source);
size_t strlen(const char* value);
int atoi(const char* value);
#endif // SIDE_MODULE_SYSTEM_FUNCTIONS_H_
创建side_module_system_functions.cpp
:
#include <stdio.h>
#include <emscripten.h>
#ifdef __cplusplus
extern "C" { // So that the C++ compiler does not rename our function names
#endif
const int TOTAL_MEMORY = 65536; // We should always have at least 1 page of memory (1,024 bytes x 64 KiB).
const int MAXIMUM_ALLOCATED_CHUNKS = 10;
int current_allocated_count = 0;
//...
#ifdef __cplusplus
}
#endif
接着添加对应malloc和free的实现,定义内存分配的结构:
struct MemoryAllocated
{
int offset;
int length;
};
// The array that will hold details about each memory allocation
struct MemoryAllocated AllocatedMemoryChunks[MAXIMUM_ALLOCATED_CHUNKS];
定义函数InsertIntoAllocatedArray
,其功能是向AllocatedMemoryChunks
中插入已分配的内存块:
void InsertIntoAllocatedArray(int new_item_index, int offset_start, int size_needed)
{
// Shift everything to the right by one if it is to the right of where the new item will be inserted
for (int i = (MAXIMUM_ALLOCATED_CHUNKS - 1); i > new_item_index; i--)
{
AllocatedMemoryChunks[i] = AllocatedMemoryChunks[(i - 1)];
}
// Add the new item at the specified index
AllocatedMemoryChunks[new_item_index].offset = offset_start;
AllocatedMemoryChunks[new_item_index].length = size_needed;
// Increment the count of blocks allocated
current_allocated_count++;
}
定义对应malloc函数:
// Our version of malloc
EMSCRIPTEN_KEEPALIVE
int create_buffer(int size_needed)
{
// If we are already at our limit of allocated memory blocks then exit now
if (current_allocated_count == MAXIMUM_ALLOCATED_CHUNKS) { return 0; }
// Adjust the start position to give room for items that will be copied into memory when the module is instantiated
int offset_start = 1024;
int current_offset = 0;
int found_room = 0;
int memory_size = size_needed;
while (memory_size % 8 != 0) { memory_size++; }// Increase the size so that the next offset will be a multiple of 8
// Loop through the currently allocated memory...
for (int index = 0; index < current_allocated_count; index++)
{
// If there is space between the previous offset and the current one for the memory that is wanted then...
current_offset = AllocatedMemoryChunks[index].offset;
if ((current_offset - offset_start) >= memory_size)
{
// Add the current item to the current index of the array (bump the rest of the items to the right by one)
InsertIntoAllocatedArray(index, offset_start, memory_size);
found_room = 1;
break;
}
// OffsetStart for the next loop will be the end of the current array item's memory block
offset_start = (current_offset + AllocatedMemoryChunks[index].length);
}
// Room wasn't found in between the existing allocated memory blocks
if (found_room == 0)
{
// If there is room between the end of the last memory block and the end of memory then...
if (((TOTAL_MEMORY - 1) - offset_start) >= size_needed)
{
// Add the item to the array
AllocatedMemoryChunks[current_allocated_count].offset = offset_start;
AllocatedMemoryChunks[current_allocated_count].length = size_needed;
current_allocated_count++;
found_room = 1;
}
}
// If there was room for the memory needed then return the offset
if (found_room == 1) { return offset_start; }
// Otherwise, tell the caller there was no room (NULL pointer)
return 0;
}
定义free:
// Our version of free (for our version of malloc)
EMSCRIPTEN_KEEPALIVE
void free_buffer(int offset)
{
int shift_item_left = 0; // Don't shift left by default
// Loop through the currently allocated memory...
for (int index = 0; index < current_allocated_count; index++)
{
// If the current item's offset matches the one we wish to free then we want to start shifting all items to the left starting here
if (AllocatedMemoryChunks[index].offset == offset) { shift_item_left = 1; }
// If we are to shift from the left then...
if (shift_item_left == 1)
{
// If there is at least one more item in the array to the right then...
if (index < (current_allocated_count - 1))
{
AllocatedMemoryChunks[index] = AllocatedMemoryChunks[(index + 1)];
}
else // We're at the end of the array. Zero out the values.
{
AllocatedMemoryChunks[index].offset = 0;
AllocatedMemoryChunks[index].length = 0;
}
}
}
// If we shifted left that means we found the offset requested and removed it from the array. Adjust the count of items in the array
current_allocated_count--;
}
定义strlen等:
// Our version of strcpy
char* strcpy(char* destination, const char* source)
{
// Loop until we have copied all of the source characters to the destination
char* return_copy = destination;
while (*source) { *destination++ = *source++; }
*destination = 0;// Null terminate the string since we only looped until we hit the null terminator in the source (we didn't copy the null terminator)
return return_copy;
}
// Our version of strlen
size_t strlen(const char* value)
{
// Loop while the current character is not NULL
size_t length = 0;
while (value[length] != '\0') { length++; }
// Return the count
return length;
}
// our version of atoi
int atoi(const char* value)
{
// If a null pointer or an empty string was passed in then exit now
if ((value == NULL) || (value[0] == '\0')) { return 0; }
int result = 0;
int sign = 0;
// If we have a negative sign then set the flag and move to the next character.
if (*value == '-') { sign = -1; ++value; }
// Loop until we reach the null terminator of the string...
char current_value = *value;
while (current_value != '\0')
{
// If the current character is between 0 and 9 then...
if ((current_value >= '0') && (current_value <= '9'))
{
result = result * 10 + current_value - '0';// Convert the current character to an integer and add to to the result
++value;
current_value = *value;
}
else
{
return 0; // Invalid character found. exit now
}
}
// If the value is negative, flip the flag by multiplying the value by -1
if (sign == -1) { result *= -1; }
return result;
}
编译:
emcc side_module_system_functions.cpp validate.cpp -s SIDE_MODULE=2 -O1 -o validate.wasm
此时,由于没有validate.js
,我们需要编写JS代码来初始化wasm模块,利用Part1中的editproduct.js
和editproduct.html
,删除editproduct.html
中的这句:
<script src="validate.js"></script>