diff --git a/src/Caller.cs b/src/Caller.cs
index 202c066..4d49c34 100644
--- a/src/Caller.cs
+++ b/src/Caller.cs
@@ -9,6 +9,8 @@ namespace Wasmtime
///
public readonly ref struct Caller
{
+ private const int StackallocThreshold = 256;
+
internal Caller(IntPtr handle)
{
if (handle == IntPtr.Zero)
@@ -21,6 +23,61 @@ internal Caller(IntPtr handle)
this.store = context.Store;
}
+ ///
+ /// Gets a Span from an exported memory of the caller by the given name.
+ ///
+ /// The name of the exported memory.
+ /// The zero-based address of the start of the span.
+ /// The length of the span.
+ /// The Span of memory (if the function returns true)
+ /// Returns true if the exported memory is found or false if a memory of the requested name is not exported.
+ ///
+ ///
+ /// The span may become invalid if the memory grows.
+ ///
+ /// This may happen if the memory is explicitly requested to grow or
+ /// grows as a result of WebAssembly execution.
+ ///
+ ///
+ /// Therefore, the returned span should not be used after calling the grow method or
+ /// after calling into WebAssembly code.
+ ///
+ ///
+ /// Note that WebAssembly always uses little endian as byte order. On platforms
+ /// that use big endian, you will need to convert numeric values accordingly.
+ ///
+ ///
+ public bool TryGetMemorySpan(string name, long address, int length, out Span result)
+ where T : unmanaged
+ {
+ var nameLength = Encoding.UTF8.GetMaxByteCount(name.Length);
+ var nameBytes = nameLength < StackallocThreshold ? stackalloc byte[nameLength] : new byte[nameLength];
+ nameLength = Encoding.UTF8.GetBytes(name, nameBytes);
+ nameBytes = nameBytes[..nameLength];
+
+ unsafe
+ {
+ fixed (byte* ptr = nameBytes)
+ {
+ if (!Native.wasmtime_caller_export_get(handle, ptr, (UIntPtr)nameBytes.Length, out var item))
+ {
+ result = default;
+ return false;
+ }
+
+ if (item.kind != ExternKind.Memory)
+ {
+ item.Dispose();
+ result = default;
+ return false;
+ }
+
+ result = Memory.GetSpan(context, item.of.memory, address, length);
+ return true;
+ }
+ }
+ }
+
///
/// Gets an exported memory of the caller by the given name.
///
diff --git a/src/Memory.cs b/src/Memory.cs
index c23816c..9a85fb3 100644
--- a/src/Memory.cs
+++ b/src/Memory.cs
@@ -231,6 +231,14 @@ public unsafe Span GetSpan(int address)
///
public unsafe Span GetSpan(long address, int length)
where T : unmanaged
+ {
+ var span = GetSpan(store.Context, memory, address, length);
+ GC.KeepAlive(store);
+ return span;
+ }
+
+ internal static unsafe Span GetSpan(StoreContext context, ExternMemory memory, long address, int length)
+ where T : unmanaged
{
if (address < 0)
{
@@ -242,11 +250,9 @@ public unsafe Span GetSpan(long address, int length)
throw new ArgumentOutOfRangeException(nameof(length));
}
- var context = store.Context;
- var data = Native.wasmtime_memory_data(context.handle, this.memory);
- GC.KeepAlive(store);
+ var data = Native.wasmtime_memory_data(context.handle, memory);
- var memoryLength = this.GetLength();
+ var memoryLength = checked((long)Native.wasmtime_memory_data_size(context.handle, memory));
// Note: A Span can span more than 2 GiB bytes if sizeof(T) > 1.
long byteLength = (long)length * sizeof(T);
diff --git a/tests/CallerTests.cs b/tests/CallerTests.cs
index 2d2465e..f1a8648 100644
--- a/tests/CallerTests.cs
+++ b/tests/CallerTests.cs
@@ -90,7 +90,33 @@ public void ItCanReadAndWriteMemoryFromCaller()
var callback = instance.GetFunction("call_callback")!;
// Write a value into memory
- var memory = instance.GetMemory("memory");
+ var memory = instance.GetMemory("memory")!;
+ memory.WriteByte(10, 20);
+
+ // Callback checks that value and writes another
+ callback.Invoke();
+
+ // Read value back from memory, checking it has been modified
+ memory.ReadByte(10).Should().Be(21);
+ }
+
+ [Fact]
+ public void ItCanReadAndWriteMemorySpanFromCaller()
+ {
+ Linker.DefineFunction("env", "callback", (Caller c) =>
+ {
+ c.TryGetMemorySpan("memory", 10, 1, out var span).Should().BeTrue();
+
+ span.Length.Should().Be(1);
+ span[0].Should().Be(20);
+ span[0] = 21;
+ });
+
+ var instance = Linker.Instantiate(Store, Fixture.Module);
+ var callback = instance.GetFunction("call_callback")!;
+
+ // Write a value into memory
+ var memory = instance.GetMemory("memory")!;
memory.WriteByte(10, 20);
// Callback checks that value and writes another