mirror of
https://github.com/extism/extism.git
synced 2026-01-11 14:58:01 -05:00
Compare commits
2 Commits
userdata
...
fix-dotnet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51a6aa313f | ||
|
|
c676cf1924 |
@@ -18,7 +18,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
|
||||
<!-- <PackageReference Include="Extism.runtime.win-x64" Version="0.7.0" /> -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,6 +4,8 @@ using Extism.Sdk.Native;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
Console.WriteLine($"Version: {Plugin.ExtismVersion()}");
|
||||
|
||||
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
|
||||
|
||||
using var helloWorld = new HostFunction(
|
||||
@@ -27,8 +29,17 @@ void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> ou
|
||||
outputs[0].v.i64 = plugin.WriteString(input);
|
||||
}
|
||||
|
||||
var wasm = File.ReadAllBytes("./code-functions.wasm");
|
||||
using var plugin = new Plugin(wasm, new[] { helloWorld }, withWasi: true);
|
||||
var manifest = new Manifest(new PathWasmSource("./code-functions.wasm"))
|
||||
{
|
||||
Config = new Dictionary<string, string>
|
||||
{
|
||||
{ "my-key", "some cool value" }
|
||||
},
|
||||
};
|
||||
|
||||
using var plugin = new Plugin(manifest, new[] { helloWorld }, withWasi: true);
|
||||
|
||||
Console.WriteLine("Plugin creatd!!!");
|
||||
|
||||
var output = Encoding.UTF8.GetString(
|
||||
plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World!"))
|
||||
|
||||
BIN
dotnet/samples/Extism.Sdk.Sample/code-functions.wasm
Normal file
BIN
dotnet/samples/Extism.Sdk.Sample/code-functions.wasm
Normal file
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<LangVersion>10</LangVersion>
|
||||
<LangVersion>11</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -24,5 +24,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="7.0.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -188,9 +188,17 @@ internal static class LibExtism
|
||||
/// <param name="functions">Array of host function pointers.</param>
|
||||
/// <param name="nFunctions">Number of host functions.</param>
|
||||
/// <param name="withWasi">Enables/disables WASI.</param>
|
||||
/// <param name="errmsg"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern ExtismPlugin* extism_plugin_new(byte* wasm, int wasmSize, IntPtr* functions, int nFunctions, bool withWasi, IntPtr* errmsg);
|
||||
unsafe internal static extern ExtismPlugin* extism_plugin_new(byte* wasm, ulong wasmSize, IntPtr* functions, ulong nFunctions, [MarshalAs(UnmanagedType.I1)] bool withWasi, out char** errmsg);
|
||||
|
||||
/// <summary>
|
||||
/// Frees a plugin error message.
|
||||
/// </summary>
|
||||
/// <param name="errorMessage"></param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_plugin_new_error_free(IntPtr errorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a plugin from the registry and free associated memory.
|
||||
@@ -263,11 +271,11 @@ internal static class LibExtism
|
||||
internal static extern bool extism_log_file(string filename, string logLevel);
|
||||
|
||||
/// <summary>
|
||||
/// Get the Extism Plugin ID, a 16-bit UUID in host order
|
||||
/// Get Extism Runtime version.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
// [DllImport("extism")]
|
||||
// unsafe internal static extern IntPtr extism_plugin_id(ExtismPlugin* plugin);
|
||||
[DllImport("extism")]
|
||||
internal static extern IntPtr extism_version();
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
|
||||
221
dotnet/src/Extism.Sdk/Manifest.cs
Normal file
221
dotnet/src/Extism.Sdk/Manifest.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Extism.Sdk
|
||||
{
|
||||
/// <summary>
|
||||
/// The manifest is a description of your plugin and some of the runtime constraints to apply to it.
|
||||
/// You can think of it as a blueprint to build your plugin.
|
||||
/// </summary>
|
||||
public class Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an empty manifest.
|
||||
/// </summary>
|
||||
public Manifest()
|
||||
{
|
||||
AllowedPaths = new Dictionary<string, string>
|
||||
{
|
||||
{ "/usr/plugins/1/data", "/data" }, // src, dest
|
||||
{ "d:/plugins/1/data", "/data" } // src, dest
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a manifest from one or more Wasm sources.
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
public Manifest(params WasmSource[] sources)
|
||||
{
|
||||
Sources.AddRange(sources);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of Wasm sources. See <see cref="PathWasmSource"/> and <see cref="ByteArrayWasmSource"/>.
|
||||
/// </summary>
|
||||
[JsonPropertyName("wasm")]
|
||||
public List<WasmSource> Sources { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Configures memory for the Wasm runtime.
|
||||
/// Memory is described in units of pages (64KB) and represent contiguous chunks of addressable memory.
|
||||
/// </summary>
|
||||
[JsonPropertyName("memory")]
|
||||
public MemoryOptions? MemoryOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of host names the plugins can access. Example:
|
||||
/// <code>
|
||||
/// AllowedHosts = new List<string> {
|
||||
/// "www.example.com",
|
||||
/// "api.*.com",
|
||||
/// "example.*",
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[JsonPropertyName("allowed_hosts")]
|
||||
public List<string> AllowedHosts { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of directories that can be accessed by the plugins. Examples:
|
||||
/// <code>
|
||||
/// AllowedPaths = new Dictionary<string, string>
|
||||
/// {
|
||||
/// { "/usr/plugins/1/data", "/data" }, // src, dest
|
||||
/// { "d:/plugins/1/data", "/data" } // src, dest
|
||||
/// };
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[JsonPropertyName("allowed_paths")]
|
||||
public Dictionary<string, string> AllowedPaths { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Configurations available to the plugins. Examples:
|
||||
/// <code>
|
||||
/// Config = new Dictionary<string, string>
|
||||
/// {
|
||||
/// { "userId", "55" }, // key, value
|
||||
/// { "mySecret", "super-secret-key" } // key, value
|
||||
/// };
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[JsonPropertyName("config")]
|
||||
public Dictionary<string, string> Config { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures memory for the Wasm runtime.
|
||||
/// Memory is described in units of pages (64KB) and represent contiguous chunks of addressable memory.
|
||||
/// </summary>
|
||||
public class MemoryOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Max number of pages. Each page is 64KB.
|
||||
/// </summary>
|
||||
[JsonPropertyName("max")]
|
||||
public int MaxPages { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A named Wasm source.
|
||||
/// </summary>
|
||||
public abstract class WasmSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Logical name of the Wasm source
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hash of the WASM source
|
||||
/// </summary>
|
||||
[JsonPropertyName("hash")]
|
||||
public string? Hash { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wasm Source represented by a file referenced by a path.
|
||||
/// </summary>
|
||||
public class PathWasmSource : WasmSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="path">path to wasm plugin.</param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="hash"></param>
|
||||
public PathWasmSource(string path, string? name = null, string? hash = null)
|
||||
{
|
||||
Path = System.IO.Path.GetFullPath(path);
|
||||
Name = name ?? System.IO.Path.GetFileNameWithoutExtension(path);
|
||||
Hash = hash;
|
||||
|
||||
if (Hash is null)
|
||||
{
|
||||
using var file = File.OpenRead(Path);
|
||||
Hash = Helpers.ComputeSha256Hash(file);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path to wasm plugin.
|
||||
/// </summary>
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wasm Source represented by raw bytes.
|
||||
/// </summary>
|
||||
public class ByteArrayWasmSource : WasmSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="data">the byte array representing the Wasm code</param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="hash"></param>
|
||||
public ByteArrayWasmSource(byte[] data, string? name, string? hash = null)
|
||||
{
|
||||
Data = data;
|
||||
Name = name;
|
||||
Hash = hash;
|
||||
|
||||
if (Hash is null)
|
||||
{
|
||||
using var memory = new MemoryStream(data);
|
||||
Hash = Helpers.ComputeSha256Hash(memory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The byte array representing the Wasm code
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
[JsonConverter(typeof(Base64EncodedStringConverter))]
|
||||
public byte[] Data { get; }
|
||||
}
|
||||
|
||||
static class Helpers
|
||||
{
|
||||
public static string ComputeSha256Hash(Stream stream)
|
||||
{
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] hashBytes = sha256.ComputeHash(stream);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Base64EncodedStringConverter : JsonConverter<string>
|
||||
{
|
||||
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
|
||||
Encoding.UTF8.GetString(reader.GetBytesFromBase64());
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
|
||||
writer.WriteBase64StringValue(Encoding.UTF8.GetBytes(value));
|
||||
}
|
||||
|
||||
class WasmSourceConverter : JsonConverter<WasmSource>
|
||||
{
|
||||
public override WasmSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, WasmSource value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is PathWasmSource path)
|
||||
JsonSerializer.Serialize(writer, path, typeof(PathWasmSource), options);
|
||||
else if (value is ByteArrayWasmSource bytes)
|
||||
JsonSerializer.Serialize(writer, bytes, typeof(ByteArrayWasmSource), options);
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Unknown Wasm Source");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
@@ -19,33 +21,72 @@ public unsafe class Plugin : IDisposable
|
||||
internal LibExtism.ExtismPlugin* NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a and load a plug-in
|
||||
/// Create a plugin from a Manifest.
|
||||
/// </summary>
|
||||
/// <param name="manifest"></param>
|
||||
/// <param name="functions"></param>
|
||||
/// <param name="withWasi"></param>
|
||||
public Plugin(Manifest manifest, HostFunction[] functions, bool withWasi)
|
||||
{
|
||||
_functions = functions;
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
options.Converters.Add(new WasmSourceConverter());
|
||||
var json = JsonSerializer.Serialize(manifest, options);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
|
||||
fixed (byte* wasmPtr = bytes)
|
||||
fixed (IntPtr* functionsPtr = functionHandles)
|
||||
{
|
||||
NativeHandle = Initialize(wasmPtr, bytes.Length, functions, withWasi, functionsPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and load a plugin from a byte array.
|
||||
/// </summary>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="functions">List of host functions expected by the plugin.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
|
||||
public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi)
|
||||
{
|
||||
_functions = functions;
|
||||
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
|
||||
|
||||
unsafe
|
||||
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
fixed (IntPtr* functionsPtr = functionHandles)
|
||||
{
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
fixed (IntPtr* functionsPtr = functionHandles)
|
||||
{
|
||||
NativeHandle = LibExtism.extism_plugin_new(wasmPtr, wasm.Length, functionsPtr, functions.Length, withWasi, null);
|
||||
if (NativeHandle == null)
|
||||
{
|
||||
throw new ExtismException("Unable to create plugin");
|
||||
// TODO: handle error
|
||||
// var s = Marshal.PtrToStringUTF8(result);
|
||||
// LibExtism.extism_plugin_new_error_free(errmsg);
|
||||
// throw new ExtismException(s);
|
||||
}
|
||||
}
|
||||
NativeHandle = Initialize(wasmPtr, wasm.Length, functions, withWasi, functionsPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe LibExtism.ExtismPlugin* Initialize(byte* wasmPtr, int wasmLength, HostFunction[] functions, bool withWasi, IntPtr* functionsPtr)
|
||||
{
|
||||
char** errorMsgPtr;
|
||||
|
||||
var handle = LibExtism.extism_plugin_new(wasmPtr, (ulong)wasmLength, functionsPtr, (ulong)functions.Length, withWasi, out errorMsgPtr);
|
||||
if (handle == null)
|
||||
{
|
||||
var msg = "Unable to create plugin";
|
||||
|
||||
if (errorMsgPtr is not null)
|
||||
{
|
||||
msg = Marshal.PtrToStringAnsi(new IntPtr(errorMsgPtr));
|
||||
}
|
||||
|
||||
throw new ExtismException(msg);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update plugin config values, this will merge with the existing values.
|
||||
/// </summary>
|
||||
@@ -199,4 +240,15 @@ public unsafe class Plugin : IDisposable
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Extism Runtime version.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ExtismVersion()
|
||||
{
|
||||
var version = LibExtism.extism_version();
|
||||
return Marshal.PtrToStringAnsi(version);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
|
||||
<PackageReference Include="Extism.runtime.win-x64" Version="0.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
/**
|
||||
* A list of all possible value types in WebAssembly.
|
||||
* An enumeration of all possible value types in WebAssembly.
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user