Skip to content

Commit 4b833ea

Browse files
committed
refactor: NativeBindingContext
NativeBindingInfo -> NativeBindingContext
1 parent 25efb4b commit 4b833ea

10 files changed

Lines changed: 182 additions & 87 deletions

File tree

src/MaaFramework.Binding.Native/Interop/NativeLibrary.cs

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,23 @@ internal static partial class NativeLibrary
88
{
99
private static readonly Assembly s_assembly = typeof(NativeLibrary).Assembly;
1010

11-
private static bool s_isAgentServer;
12-
private static readonly List<string> s_searchPath = [];
13-
private static readonly Dictionary<string, nint> s_libraryHandles = [];
14-
1511
#pragma warning disable CA2255 // 不应在库中使用 “ModuleInitializer” 属性
16-
[ModuleInitializer]
17-
internal static void SetNativeAssemblyResolver() => SetDllImportResolver(s_assembly, NativeAssemblyResolver);
18-
#pragma warning restore CA2255 // 不应在库中使用 “ModuleInitializer” 属性
12+
#pragma warning disable S2223 // Non-constant static fields should not be visible
1913

20-
public static void Init(bool isAgentServer, params string[] paths)
21-
{
22-
if (s_libraryHandles.Count != 0)
23-
throw new InvalidOperationException("NativeLibrary is already loaded.");
14+
internal static bool IsLoaded;
15+
internal static string LoadedDirectory = string.Empty;
16+
internal static readonly Dictionary<string, nint> LoadedLibraryHandles = [];
2417

25-
s_isAgentServer = isAgentServer;
26-
s_searchPath.AddRange(paths);
27-
}
18+
internal static ApiInfoFlags ApiInfo;
19+
internal static readonly List<string> SearchPath = [];
20+
21+
[ModuleInitializer]
22+
internal static void SetNativeLibraryResolver() => SetDllImportResolver(s_assembly, NativeLibraryResolver);
23+
24+
#pragma warning restore S2223 // Non-constant static fields should not be visible
25+
#pragma warning restore CA2255 // 不应在库中使用 “ModuleInitializer” 属性
2826

29-
public static IntPtr NativeAssemblyResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) => libraryName switch
27+
public static nint NativeLibraryResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) => libraryName switch
3028
{
3129
"MaaFramework" or "MaaToolkit"
3230
or "MaaAgentServer" or "MaaAgentClient" => GetLibraryHandle(libraryName),
@@ -36,32 +34,36 @@ public static void Init(bool isAgentServer, params string[] paths)
3634

3735
private static nint GetLibraryHandle(string libraryName)
3836
{
39-
if (s_libraryHandles.TryGetValue(libraryName, out var libraryHandle))
37+
if (LoadedLibraryHandles.TryGetValue(libraryName, out var libraryHandle))
4038
return libraryHandle;
4139

42-
if (!TryGetRuntimesPath(libraryName, out var dllPath) || !TryLoad(dllPath, out libraryHandle))
43-
{
44-
s_libraryHandles.Add(libraryName, nint.Zero);
45-
NativeBindingInfo.NativeAssemblyDirectory = null;
46-
NativeBindingInfo.IsStatelessMode = false;
47-
NativeBindingInfo.ApiInfo = "Using default dll resolver.";
48-
return nint.Zero;
49-
}
40+
IsLoaded = true;
41+
var resolver = TryGetRuntimesPath(libraryName, out var dllPath) && TryLoad(dllPath, out libraryHandle)
42+
? ApiInfoFlags.UseBindingResolver
43+
: ApiInfoFlags.UseDefaultResolver;
44+
SetBindingContext(
45+
resolver,
46+
dllDir: Path.GetDirectoryName(dllPath) ?? string.Empty,
47+
isFirst: LoadedLibraryHandles.Count == 0);
48+
49+
LoadedLibraryHandles.Add(libraryName, libraryHandle);
50+
return libraryHandle;
51+
}
5052

51-
s_libraryHandles.Add(libraryName, libraryHandle);
53+
private static void SetBindingContext(ApiInfoFlags resolver, string dllDir, bool isFirst)
54+
{
55+
if (ApiInfo.HasFlag_ResolverExcept(resolver))
56+
throw new InvalidOperationException($"The resolver '{ApiInfo}' was attempted to switch to '{resolver}'.");
5257

53-
var dllDir = Path.GetDirectoryName(dllPath);
54-
if (s_libraryHandles.Count == 1)
55-
{
56-
NativeBindingInfo.NativeAssemblyDirectory ??= dllDir;
57-
NativeBindingInfo.IsStatelessMode = s_isAgentServer;
58-
NativeBindingInfo.ApiInfo = s_isAgentServer ? "In MaaAgentServer context." : "In MaaFramework context.";
59-
}
58+
if (isFirst)
59+
LoadedDirectory = dllDir;
6060

61-
if (NativeBindingInfo.NativeAssemblyDirectory != dllDir)
62-
throw new InvalidOperationException($"The native assembly directory '{NativeBindingInfo.NativeAssemblyDirectory}' was switched to '{dllDir}'.");
61+
if (LoadedDirectory != dllDir)
62+
throw new InvalidOperationException($"The native library directory '{LoadedDirectory}' was attempted to switch to '{dllDir}'.");
6363

64-
return libraryHandle;
64+
ApiInfo |= resolver;
65+
if (!ApiInfo.HasFlag_Context())
66+
ApiInfo |= ApiInfoFlags.InFrameworkContext;
6567
}
6668

6769
private static bool TryGetRuntimesPath(string libraryName, out string dllPath)
@@ -73,7 +75,7 @@ private static bool TryGetRuntimesPath(string libraryName, out string dllPath)
7375

7476
private static IEnumerable<string> GetRuntimesPaths(string libraryFullName)
7577
{
76-
var searchPaths = s_searchPath.Concat(
78+
var searchPaths = SearchPath.Concat(
7779
[
7880
Environment.GetEnvironmentVariable("MAAFW_BINARY_PATH"),
7981
Path.GetDirectoryName(s_assembly.Location),
@@ -82,8 +84,8 @@ private static IEnumerable<string> GetRuntimesPaths(string libraryFullName)
8284

8385
var runtimePaths = new[]
8486
{
87+
"./",
8588
$"./runtimes/{GetArchitectureName()}/native/",
86-
"./"
8789
};
8890

8991
return from searchPath in searchPaths
@@ -109,7 +111,7 @@ select Path.GetFullPath(
109111

110112
private static string GetFullLibraryName(string libraryName)
111113
{
112-
if (s_isAgentServer && libraryName == "MaaFramework")
114+
if (libraryName == "MaaFramework" && ApiInfo.HasFlag(ApiInfoFlags.InAgentServerContext))
113115
libraryName = "MaaAgentServer";
114116

115117
if (IsWindows)

src/MaaFramework.Binding.Native/MaaAgentClient.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,8 @@ public bool LinkStart(IMaaAgentClient.AgentServerStartupMethod method, Cancellat
157157
{
158158
if (_agentServerProcess is null or { HasExited: true })
159159
{
160-
if (string.IsNullOrEmpty(Id) || string.IsNullOrEmpty(NativeBindingInfo.NativeAssemblyDirectory))
161-
{
162-
throw new InvalidOperationException(
163-
$"The {nameof(Id)}({Id ?? "<null>"})" +
164-
$" or {nameof(NativeBindingInfo.NativeAssemblyDirectory)}({NativeBindingInfo.NativeAssemblyDirectory ?? "<null>"})" +
165-
$" is invalid.");
166-
}
167-
168160
_agentServerProcess?.Dispose();
169-
_agentServerProcess = method.Invoke(Id, NativeBindingInfo.NativeAssemblyDirectory);
161+
_agentServerProcess = method.Invoke(Id, NativeBindingContext.LoadedNativeLibraryDirectory);
170162

171163
if (_agentServerProcess is null or { HasExited: true })
172164
return false;

src/MaaFramework.Binding.Native/MaaAgentServer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public sealed class MaaAgentServer : IMaaAgentServer
2828
private MaaAgentServer() { }
2929
static MaaAgentServer()
3030
{
31-
NativeLibrary.Init(isAgentServer: true);
31+
NativeBindingContext.SwitchToAgentServerContext();
3232
CurrentId = string.Empty;
3333
Current = new();
3434
}
@@ -146,7 +146,7 @@ public static MaaAgentServer WithToolkitConfig_InitOption(this MaaAgentServer se
146146
/// <param name="paths">The directory paths to search for native libraries.</param>
147147
public static MaaAgentServer WithNativeLibrary(this MaaAgentServer server, params string[] paths)
148148
{
149-
NativeLibrary.Init(true, paths);
149+
NativeBindingContext.AppendNativeLibrarySearchPaths(paths);
150150
return server;
151151
}
152152
}

src/MaaFramework.Binding.Native/MaaContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public MaaTasker Tasker
102102
get
103103
{
104104
var taskerHandle = MaaContextGetTasker(Handle);
105-
if (NativeBindingInfo.IsStatelessMode)
105+
if (NativeBindingContext.IsStatelessMode)
106106
return new MaaTasker(taskerHandle);
107107
else
108108
return MaaTasker.Instances[taskerHandle];
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using MaaFramework.Binding.Interop.Native;
2+
using System.Diagnostics;
3+
4+
namespace MaaFramework.Binding;
5+
6+
/// <summary>
7+
/// Provides information and configuration for native bindings in the MaaFramework.
8+
/// </summary>
9+
public static class NativeBindingContext
10+
{
11+
/// <summary>
12+
/// Gets a value indicating whether NativeLibrary is already loaded.
13+
/// </summary>
14+
public static bool IsLoaded => NativeLibrary.IsLoaded;
15+
16+
/// <summary>
17+
/// Gets the loaded directory path where native libraries are located.
18+
/// </summary>
19+
public static string LoadedNativeLibraryDirectory
20+
{
21+
get
22+
{
23+
if (!NativeLibrary.ApiInfo.HasFlag(ApiInfoFlags.UseDefaultResolver))
24+
return NativeLibrary.LoadedDirectory;
25+
26+
var name = NativeLibrary.LoadedLibraryHandles.First().Key;
27+
foreach (var obj in Process.GetCurrentProcess().Modules)
28+
{
29+
if (obj is not ProcessModule module || Path.GetFileNameWithoutExtension(module.ModuleName) != name)
30+
continue;
31+
return Path.GetDirectoryName(module.FileName) ?? string.Empty;
32+
}
33+
return string.Empty;
34+
}
35+
}
36+
37+
/// <summary>
38+
/// Gets the API information, which provides details about the current API context.
39+
/// </summary>
40+
public static ApiInfoFlags ApiInfo => NativeLibrary.ApiInfo;
41+
42+
/// <summary>
43+
/// Gets a value indicating whether the current API is interoperating in the stateless mode.
44+
/// <para>Stateless mode is typically used in server contexts.</para>
45+
/// </summary>
46+
public static bool IsStatelessMode => ApiInfo.HasFlag(ApiInfoFlags.InAgentServerContext);
47+
48+
/// <summary>
49+
/// Switches the context to the MaaAgentServer context.
50+
/// </summary>
51+
/// <exception cref="InvalidOperationException">NativeLibrary is already loaded.</exception>
52+
public static void SwitchToAgentServerContext()
53+
{
54+
ThrowIfLoaded();
55+
NativeLibrary.ApiInfo = ApiInfoFlags.InAgentServerContext;
56+
}
57+
58+
/// <summary>
59+
/// Switches the context to the MaaFramework context.
60+
/// </summary>
61+
/// <exception cref="InvalidOperationException">NativeLibrary is already loaded.</exception>
62+
public static void SwitchToFrameworkContext()
63+
{
64+
ThrowIfLoaded();
65+
NativeLibrary.ApiInfo = ApiInfoFlags.InFrameworkContext;
66+
}
67+
68+
/// <summary>
69+
/// Appends the specified paths to the native library search paths.
70+
/// </summary>
71+
/// <param name="paths"></param>
72+
/// <exception cref="InvalidOperationException">NativeLibrary is already loaded.</exception>
73+
public static void AppendNativeLibrarySearchPaths(params IEnumerable<string> paths)
74+
{
75+
ThrowIfLoaded();
76+
NativeLibrary.SearchPath.AddRange(paths);
77+
}
78+
79+
/// <exception cref="InvalidOperationException">NativeLibrary is already loaded.</exception>
80+
internal static void ThrowIfLoaded()
81+
{
82+
if (NativeLibrary.IsLoaded)
83+
throw new InvalidOperationException("NativeLibrary is already loaded.");
84+
}
85+
}

src/MaaFramework.Binding.Native/NativeBindingInfo.cs

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/MaaFramework.Binding.UnitTests/MaaFramework.Binding.UnitTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<PackageReference Include="MSTest.TestFramework" />
77
<PackageReference Include="SixLabors.ImageSharp" />
88

9-
<ProjectReference Include="..\MaaFramework\MaaFramework.csproj" />
9+
<ProjectReference Include="..\MaaFramework\MaaFramework.csproj" Private="false" />
1010
<ProjectReference Include="..\MaaFramework.Binding.Extensions\MaaFramework.Binding.Extensions.csproj" />
1111

1212
<None Include="..\Common\TestParam.runsettings" />
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace MaaFramework.Binding;
2+
3+
#pragma warning disable S2344 // Enumeration type names should not have "Flags" or "Enum" suffixes
4+
5+
/// <summary>
6+
/// Represents information about binding interoperable API.
7+
/// </summary>
8+
[Flags]
9+
public enum ApiInfoFlags
10+
{
11+
/// <summary>
12+
/// No flags.
13+
/// </summary>
14+
None = 0,
15+
16+
/// <summary>
17+
/// Indicates that the API is in MaaFramework context.
18+
/// </summary>
19+
InFrameworkContext = 1,
20+
21+
/// <summary>
22+
/// Indicates that the API is in MaaAgentServer context.
23+
/// </summary>
24+
InAgentServerContext = 2,
25+
26+
/// <summary>
27+
/// Indicates that the API uses the default resolver.
28+
/// </summary>
29+
UseDefaultResolver = 1 << 8,
30+
31+
/// <summary>
32+
/// Indicates that the API uses the resolver from binding.
33+
/// </summary>
34+
UseBindingResolver = 2 << 8,
35+
}
36+
37+
internal static class ApiInfoFlagsExtensions
38+
{
39+
internal const ApiInfoFlags ContextMask = ApiInfoFlags.InFrameworkContext | ApiInfoFlags.InAgentServerContext;
40+
internal const ApiInfoFlags ResolverMask = ApiInfoFlags.UseDefaultResolver | ApiInfoFlags.UseBindingResolver;
41+
42+
internal static bool HasFlag_Context(this ApiInfoFlags flags) => (flags & ContextMask) != ApiInfoFlags.None;
43+
internal static bool HasFlag_ResolverExcept(this ApiInfoFlags flags, ApiInfoFlags other) => (flags & ~other & ResolverMask) != ApiInfoFlags.None;
44+
}

src/MaaFramework.Binding/IMaaAgentClient.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ public interface IMaaAgentClient : IMaaDisposable
4949
/// <param name="method">The delegate method that defines how to start the agent server process.</param>
5050
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation waiting for the connection.</param>
5151
/// <returns><see langword="true"/> if the connection was started successfully; otherwise, <see langword="false"/>.</returns>
52-
/// <exception cref="InvalidOperationException">One or more parameters required by the <paramref name="method"/> are invalid.</exception>
5352
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> has had cancellation requested.</exception>
5453
bool LinkStart(AgentServerStartupMethod method, CancellationToken cancellationToken = default);
5554

@@ -79,6 +78,10 @@ public interface IMaaAgentClient : IMaaDisposable
7978
/// <returns>
8079
/// A new <see cref="Process"/> that is associated with the process resource, or <see langword="null"/> if no process resource is started.
8180
/// </returns>
81+
/// <remarks>
82+
/// <para>The implementation of this delegate is responsible for validating the provided parameters.</para>
83+
/// <para>Ensure that <paramref name="identifier"/> and <paramref name="nativeAssemblyDirectory"/> are valid and meet the requirements of the agent server process.</para>
84+
/// </remarks>
8285
delegate Process? AgentServerStartupMethod(string identifier, string nativeAssemblyDirectory);
8386

8487
/// <summary>

src/MaaFramework.Binding/MaaFramework.Binding.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@
1212
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
1313
</PropertyGroup>
1414

15+
<ItemGroup>
16+
<InternalsVisibleTo Include="MaaFramework.Binding.Native" />
17+
</ItemGroup>
18+
1519
</Project>

0 commit comments

Comments
 (0)