1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
namespace MetaOxide
{
/// <summary>
/// Handles dynamic loading of the native MetaOxide library.
/// </summary>
/// <remarks>
/// This class automatically detects the platform and loads the appropriate
/// native library (DLL on Windows, SO on Linux, DYLIB on macOS).
/// It supports both NuGet package deployment and local development scenarios.
/// </remarks>
internal static class NativeLibraryLoader
{
private static bool _isLoaded;
private static readonly object _lock = new object();
/// <summary>
/// Ensures the native library is loaded. This method is idempotent.
/// </summary>
/// <exception cref="DllNotFoundException">Thrown when the native library cannot be found or loaded</exception>
/// <exception cref="PlatformNotSupportedException">Thrown when the current platform is not supported</exception>
public static void EnsureLoaded()
{
if (_isLoaded)
return;
lock (_lock)
{
if (_isLoaded)
return;
try
{
LoadNativeLibrary();
_isLoaded = true;
}
catch (Exception ex)
{
throw new DllNotFoundException(
$"Failed to load native MetaOxide library. {ex.Message}\n" +
$"Platform: {GetRuntimeIdentifier()}\n" +
$"Library name: {GetLibraryName()}\n" +
"Please ensure the native library is present in the appropriate runtime directory.",
ex);
}
}
}
private static void LoadNativeLibrary()
{
string rid = GetRuntimeIdentifier();
string libraryName = GetLibraryName();
// Try to load from multiple locations in order of preference
string? libraryPath = null;
// 1. Try NuGet package runtime directory (most common for deployed apps)
string nugetPath = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "",
"runtimes",
rid,
"native",
libraryName);
if (File.Exists(nugetPath))
{
libraryPath = nugetPath;
}
// 2. Try local native directory (for development)
if (libraryPath == null)
{
string localPath = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "",
"native",
rid,
libraryName);
if (File.Exists(localPath))
{
libraryPath = localPath;
}
}
// 3. Try assembly directory directly (for simple deployments)
if (libraryPath == null)
{
string directPath = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "",
libraryName);
if (File.Exists(directPath))
{
libraryPath = directPath;
}
}
// 4. Try system library paths (Linux/macOS)
if (libraryPath == null && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Let the system loader find it in standard locations
libraryPath = libraryName;
}
if (libraryPath == null)
{
throw new FileNotFoundException(
$"Native library '{libraryName}' not found in any expected location.\n" +
$"Searched paths:\n" +
$" - {nugetPath}\n" +
$" - {Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", "native", rid, libraryName)}\n" +
$" - {Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", libraryName)}");
}
// Load the library
if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework"))
{
// .NET Framework: Use LoadLibrary
LoadLibraryWindows(libraryPath);
}
else
{
// .NET Core/.NET 5+: Use NativeLibrary.Load
LoadLibraryModern(libraryPath);
}
}
private static void LoadLibraryModern(string libraryPath)
{
#if NET5_0_OR_GREATER
IntPtr handle = NativeLibrary.Load(libraryPath);
if (handle == IntPtr.Zero)
{
throw new DllNotFoundException($"Failed to load library from: {libraryPath}");
}
#else
// For .NET Standard 2.0, we rely on DllImport's default loading mechanism
// The library must be in the same directory as the assembly or in system paths
if (!File.Exists(libraryPath))
{
throw new FileNotFoundException($"Library not found: {libraryPath}");
}
#endif
}
private static void LoadLibraryWindows(string libraryPath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
IntPtr handle = WindowsLoadLibrary(libraryPath);
if (handle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
throw new DllNotFoundException(
$"Failed to load library from: {libraryPath}. " +
$"Windows error code: {errorCode}");
}
}
}
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpFileName);
private static IntPtr WindowsLoadLibrary(string libraryPath)
{
return LoadLibrary(libraryPath);
}
/// <summary>
/// Gets the runtime identifier (RID) for the current platform.
/// </summary>
private static string GetRuntimeIdentifier()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.X64 => "win-x64",
Architecture.X86 => "win-x86",
Architecture.Arm64 => "win-arm64",
_ => throw new PlatformNotSupportedException(
$"Unsupported Windows architecture: {RuntimeInformation.ProcessArchitecture}")
};
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.X64 => "linux-x64",
Architecture.Arm64 => "linux-arm64",
Architecture.Arm => "linux-arm",
_ => throw new PlatformNotSupportedException(
$"Unsupported Linux architecture: {RuntimeInformation.ProcessArchitecture}")
};
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.X64 => "osx-x64",
Architecture.Arm64 => "osx-arm64",
_ => throw new PlatformNotSupportedException(
$"Unsupported macOS architecture: {RuntimeInformation.ProcessArchitecture}")
};
}
throw new PlatformNotSupportedException(
$"Unsupported operating system: {RuntimeInformation.OSDescription}");
}
/// <summary>
/// Gets the platform-specific library file name.
/// </summary>
private static string GetLibraryName()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return "meta_oxide.dll";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "libmeta_oxide.so";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return "libmeta_oxide.dylib";
}
throw new PlatformNotSupportedException(
$"Unsupported operating system: {RuntimeInformation.OSDescription}");
}
/// <summary>
/// Gets diagnostic information about the current platform and library loading.
/// </summary>
public static string GetDiagnosticInfo()
{
return $"Platform: {RuntimeInformation.OSDescription}\n" +
$"Architecture: {RuntimeInformation.ProcessArchitecture}\n" +
$"Framework: {RuntimeInformation.FrameworkDescription}\n" +
$"Runtime Identifier: {GetRuntimeIdentifier()}\n" +
$"Library Name: {GetLibraryName()}\n" +
$"Assembly Location: {Assembly.GetExecutingAssembly().Location}\n" +
$"Library Loaded: {_isLoaded}";
}
}
}