Skip to content

Commit d434bf0

Browse files
committed
WASM-to-DLL is now minimally working.
1 parent f49020d commit d434bf0

File tree

6 files changed

+186
-79
lines changed

6 files changed

+186
-79
lines changed

README.md

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
> Only WebAssembly 1.0 is supported!
66
> Most WASM files target a higher version and will encounter errors if you try to load them with WebAssembly for .NET.
77
8-
A library able to create, read, modify, write and execute WebAssembly (WASM) files from .NET-based applications.
8+
A library able to create, read, modify, write, execute WebAssembly (WASM) files from .NET-based applications.
9+
It can also convert WASM files to .NET DLLs.
910
*Execution does not use an interpreter or a 3rd party library:*
1011
WASM instructions are mapped to their .NET equivalents and converted to native machine language by the .NET JIT compiler.
1112

@@ -17,10 +18,11 @@ Available on NuGet at https://www.nuget.org/packages/WebAssembly .
1718
- `Module.ReadFromBinary` reads a stream into an instance, which can then be inspected and modified through its properties.
1819
- Most WASM files use post-1.0 features and will experience errors when you try to load them.
1920
- `WriteToBinary` on a module instance writes binary WASM to the provided stream.
20-
- Use the `WebAssembly.Runtime.Compile` class to execute WebAssembly (WASM) binary files using the .NET JIT compiler.
21+
- Use the `WebAssembly.Runtime.Compile` class to execute WebAssembly (WASM) binary files using the .NET JIT compiler or convert it to a .NET DLL.
2122
- Most WASM files have many imports and exports--you'll need to cover these yourself.
2223
- This should work for most WASM 1.0 files, but spec compliance is not perfect.
2324
- This will not work for any newer-than-1.0 files
25+
- Saving to a DLL requires .NET 9 or higher and has several additional steps.
2426

2527
You're welcome to report a bug if you can share a WASM file that has a problem, but no one is actively working on this project so a fix may not come.
2628

@@ -102,7 +104,68 @@ public abstract class Sample
102104
// Sometimes you can use C# dynamic instead of building an abstract class like this.
103105
public abstract int Demo(int value);
104106
}
107+
```
108+
## Sample: Convert a WASM file to a .NET DLL
109+
110+
> [!NOTE]
111+
> This feature is experimental.
112+
113+
The saving process uses the [PersistedAssemblyBuilder](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.persistedassemblybuilder.-ctor) feature introduced in .NET 9.
114+
Aided by [MetadataLoadContext](https://www.nuget.org/packages/System.Reflection.MetadataLoadContext), this example produces a DLL for .NET Standard 2.0.
115+
116+
```C#
117+
var resolver = new PathAssemblyResolver([
118+
// A core DLL containing System.String and other basic features:
119+
"C:\\Program Files\\dotnet\\sdk\\9.0.300-preview.0.25177.5\\ref\\netstandard.dll",
120+
// One way or another you'll need a reference to the matching WebAssembly.dll built against the core DLL.
121+
"C:\\dotnet-webassembly\\WebAssembly\\bin\\Release\\netstandard2.0\\WebAssembly.dll"
122+
]);
123+
using var context = new MetadataLoadContext(resolver);
124+
125+
const string name = "HelloWorld"; // Name components should match.
126+
127+
var assembly = Compile.CreatePersistedAssembly(
128+
File.OpenRead("HelloWorld.wasm"), // This is part of the "RunExisting" sample.
129+
new(
130+
context.CoreAssembly,
131+
resolver.Resolve(context, new("WebAssembly")),
132+
new(name),
133+
$"{name}.dll"
134+
)
135+
{
136+
// The type name includes the namespace.
137+
// If not set, defaults to WebAssembly.CompiledFromWasm.
138+
TypeName = "Converted.HelloWorld"
139+
}
140+
);
141+
142+
assembly.Save($"{name}.dll");
143+
```
144+
145+
To use the new DLL, you directly reference it in your .csproj.
146+
147+
```XML
148+
<ItemGroup>
149+
<Reference Include="HelloWorld">
150+
<HintPath>bin\HelloWorld.dll</HintPath> <!-- Relative path to the URL. -->
151+
</Reference>
152+
</ItemGroup>
153+
```
154+
155+
Once the DLL reference is in place, you can access it just like any other .NET library.
156+
157+
```C#
158+
var helloWorld = new Converted.HelloWorld((module, field) =>
159+
{
160+
// Imports are defined by the original WASM and must be supplied by you.
161+
if (module == "env" && field == "sayc")
162+
return new FunctionImport(new Action<int>(raw => Console.Write((char)raw)));
163+
164+
throw new Exception($"Unknown import: {module} {field}");
165+
});
105166

167+
// You can directly access anything exported by the WASM.
168+
var result = helloWorld.main();
106169
```
107170

108171
## Other Information

WebAssembly.Tests/ApiQualityTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ static IEnumerable<string> GatherViolations()
105105
[TestMethod]
106106
public void NoTypeMatchingCompilerConfigurationDefaultNameExists()
107107
{
108-
var defaultTypeName = new Runtime.PersistedCompilerConfiguration(typeof(object).Assembly, typeof(Module).Assembly).TypeName;
108+
var defaultTypeName = new Runtime.PersistedCompilerConfiguration(typeof(object).Assembly, typeof(Module).Assembly, new("Test"), "Test").TypeName;
109109
Assert.IsNull(typeof(Module).Assembly.GetType(defaultTypeName));
110110
}
111111
#endif

WebAssembly/Runtime/Compilation/Signature.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ private Signature()
2222
this.RawReturnTypes = this.RawParameterTypes = [];
2323
}
2424

25-
public Signature(WebAssemblyValueType returnType)
25+
public Signature(WebAssemblyValueType returnType, CompilerConfiguration configuration)
2626
: this()
2727
{
28-
this.ReturnTypes = [returnType.ToSystemType()];
28+
this.ReturnTypes = [configuration.NeutralizeType(returnType.ToSystemType())];
2929
this.RawReturnTypes = [returnType];
3030
}
3131

32-
public Signature(Reader reader, uint typeIndex)
32+
public Signature(Reader reader, uint typeIndex, CompilerConfiguration configuration)
3333
{
3434
this.TypeIndex = typeIndex;
3535

@@ -39,7 +39,7 @@ public Signature(Reader reader, uint typeIndex)
3939
var rawParameters = this.RawParameterTypes = new WebAssemblyValueType[parameters.Length];
4040

4141
for (var i = 0; i < parameters.Length; i++)
42-
parameters[i] = (rawParameters[i] = (WebAssemblyValueType)reader.ReadVarInt7()).ToSystemType();
42+
parameters[i] = configuration.NeutralizeType((rawParameters[i] = (WebAssemblyValueType)reader.ReadVarInt7()).ToSystemType());
4343

4444
var returns = this.ReturnTypes = new Type[reader.ReadVarUInt1()];
4545
var rawReturns = this.RawReturnTypes = new WebAssemblyValueType[returns.Length];
@@ -48,7 +48,7 @@ public Signature(Reader reader, uint typeIndex)
4848
throw new ModuleLoadException("Multiple returns are not supported.", reader.Offset - 1);
4949

5050
for (var i = 0; i < returns.Length; i++)
51-
returns[i] = (rawReturns[i] = (WebAssemblyValueType)reader.ReadVarInt7()).ToSystemType();
51+
returns[i] = configuration.NeutralizeType((rawReturns[i] = (WebAssemblyValueType)reader.ReadVarInt7()).ToSystemType());
5252
}
5353

5454
public bool Equals(WebAssemblyType? other)

0 commit comments

Comments
 (0)