RyanLamansky / dotnet-webassembly

Create, read, modify, write and execute WebAssembly (WASM) files from .NET-based applications.
Apache License 2.0
792 stars 74 forks source link

How to read and write strings to memory with a compiled WASM? #58

Closed 0xOmarA closed 1 year ago

0xOmarA commented 2 years ago

Hi. I have a compiled WASM and I'm loading it through the following code.

using System;
using System.Runtime.InteropServices;
using WebAssembly.Runtime;

ImportDictionary imports = new ImportDictionary { };
WebAssembly.Instance<InternalService> instance = Compile.FromBinary<InternalService>("../../target/wasm32-unknown-unknown/release/service.wasm")(imports);

String str = "Hello World!";
int pointer = instance.Exports.alloc(Encoding.ASCII.GetByteCount(str) + 1);
// How to write the the string to the memory?

I would like to write the string str to the linear memory of the WASM instance. However, I have not been able to find any resources on how I can do that. Any help would be highly appreciated.

munik commented 2 years ago

I was wanting to do this too, and I have some test code for it that I could put in a gist for you. Briefly, you simply need to choose an encoding and then write it into memory with Marshal.Copy.

However, the very next thing you're going to need to think about is what you intend to do with the string once it is in the linear memory. Do you want to compare it or perform some other operation in wasm? Or do you want to get it out at a later point?

What you want to do with it will determine what other primitive operations, such as a string comparison algorithm, which needs to take into account the string encoding, that you will need.

RyanLamansky commented 2 years ago

One thing that makes this easier on recent versions of .NET is the Span<T>) type (and the related ReadOnlySpan). One of the constructors takes a Void* parameter, which can be obtained via the Start property of exported memory. From there, Slice it to the appropriate range for the string's bytes (either predefined statically or exported some other way) and use it with Encoding.ASCII/UTF8/etc to read or write from it without having to dabble in pointers or other forms of raw memory access.

munik commented 2 years ago

One thing that makes this easier on recent versions of .NET is the Span<T>) type (and the related ReadOnlySpan). One of the constructors takes a Void* parameter, which can be obtained via the Start property of exported memory. From there, Slice it to the appropriate range for the string's bytes (either predefined statically or exported some other way) and use it with Encoding.ASCII/UTF8/etc to read or write from it without having to dabble in pointers or other forms of raw memory access.

Oh, that's awesome!! A very useful technique

RyanLamansky commented 2 years ago

Yeah, it's extremely useful and didn't exist when I first started development (this project's first commit is April 2017, Span came with .NET Core/Standard 2.1 in May 2018).

I've considered replacing most of the internal raw memory usage with Span-based mechanisms, but I'd have to give up .NET Standard 2.0 compatibility or use a lot of conditional compilation. Doesn't seem worth the trouble right now.

In the nearer term, I could potentially add an example on how handle strings with the help of Span.

Terricide commented 1 year ago

You might be able to use Span and still keep .net standard 2.0. It sounds like the full benefit isn't there but that it could help.

https://github.com/dotnet/corefxlab/issues/2581