未验证 提交 e22ebdfa 编写于 作者: J Jeremy Koritzinsky 提交者: GitHub

Implement NativeLibrary.GetEntryPointModuleHandle (#57610)

上级 a1bf79e0
......@@ -8,6 +8,9 @@ else(CLR_CMAKE_HOST_WIN32)
include(configure.cmake)
endif(CLR_CMAKE_HOST_WIN32)
#Required to expose symbols for global symbol discovery.
set(CLR_CMAKE_KEEP_NATIVE_SYMBOLS TRUE)
add_executable_clr(corerun
corerun.cpp
dotenv.cpp
......@@ -25,6 +28,8 @@ if(CLR_CMAKE_HOST_WIN32)
)
else(CLR_CMAKE_HOST_WIN32)
target_link_libraries(corerun ${CMAKE_DL_LIBS})
# Required to expose symbols for global symbol discovery
target_link_libraries(corerun -rdynamic)
# Android implements pthread natively
if(NOT CLR_CMAKE_TARGET_ANDROID)
......
......@@ -564,16 +564,13 @@ int MAIN(const int argc, const char_t* argv[])
return exit_code;
}
#ifdef TARGET_WINDOWS
// Used by CoreShim to determine running CoreCLR details.
extern "C" __declspec(dllexport) HRESULT __cdecl GetCurrentClrDetails(void** clrInstance, unsigned int* appDomainId)
extern "C" DLL_EXPORT HRESULT CDECL GetCurrentClrDetails(void** clrInstance, unsigned int* appDomainId)
{
assert(clrInstance != nullptr && appDomainId != nullptr);
*clrInstance = CurrentClrInstance;
*appDomainId = CurrentAppDomainId;
return S_OK;
}
#endif // TARGET_WINDOWS
//
// Self testing for corerun.
......
......@@ -47,8 +47,10 @@ namespace pal
}
#ifdef TARGET_WINDOWS
#define CDECL __cdecl
#include <Windows.h>
#define DLL_EXPORT __declspec(dllexport)
#define MAIN __cdecl wmain
#define W(str) L ## str
......@@ -316,10 +318,16 @@ public:
#include <config.h>
#include <minipal/getexepath.h>
#if __GNUC__ >= 4
#define DLL_EXPORT __attribute__ ((visibility ("default")))
#else
#define DLL_EXPORT
#endif
#define CDECL
#define MAIN main
#define W(str) str
#define FAILED(result) (result < 0)
#define S_OK 0
#define FAILED(result) (result < S_OK)
#if !HAVE_DIRENT_D_TYPE
#define DT_UNKNOWN 0
#define DT_DIR 4
......@@ -327,6 +335,8 @@ public:
#define DT_LNK 10
#endif
typedef int HRESULT;
namespace pal
{
using char_t = char;
......
......@@ -323,9 +323,6 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.SchedGetCpu.cs">
<Link>Interop\Unix\System.Native\Interop.SchedGetCpu.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.DynamicLoad.cs">
<Link>Interop\Unix\System.Native\Interop.DynamicLoad.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Threading.cs">
<Link>Interop\Unix\System.Native\Interop.Threading.cs</Link>
</Compile>
......
......@@ -70,4 +70,3 @@ extern "C" INT_PTR QCALLTYPE NativeLibrary_GetSymbol(INT_PTR handle, LPCWSTR sym
return address;
}
......@@ -19,5 +19,8 @@ internal unsafe partial class Sys
[LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_FreeLibrary")]
internal static partial void FreeLibrary(IntPtr handle);
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetDefaultSearchOrderPseudoHandle", SetLastError = true)]
internal static partial IntPtr GetDefaultSearchOrderPseudoHandle();
}
}
......@@ -1827,6 +1827,9 @@
<Compile Include="$(CommonPath)Interop\Windows\User32\Interop.USEROBJECTFLAGS.cs">
<Link>Common\Interop\Windows\User32\Interop.USEROBJECTFLAGS.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetModuleHandle.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.GetModuleHandle.cs</Link>
</Compile>
<Compile Include="$(CommonPath)System\IO\FileSystem.Attributes.Windows.cs">
<Link>Common\System\IO\FileSystem.Attributes.Windows.cs</Link>
</Compile>
......@@ -1969,6 +1972,9 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.CopyFile.cs">
<Link>Common\Interop\Unix\System.Native\Interop.CopyFile.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.DynamicLoad.cs">
<Link>Common\Interop\Unix\System.Native\Interop.DynamicLoad.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ErrNo.cs">
<Link>Common\Interop\Unix\System.Native\Interop.ErrNo.cs</Link>
</Compile>
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
......@@ -231,5 +232,26 @@ public static void SetDllImportResolver(Assembly assembly!!, DllImportResolver r
return resolver(libraryName, assembly, hasDllImportSearchPathFlags ? (DllImportSearchPath?)dllImportSearchPathFlags : null);
}
/// <summary>
/// Get a handle that can be used with <see cref="GetExport" /> or <see cref="TryGetExport" /> to resolve exports from the entry point module.
/// </summary>
/// <returns> The handle that can be used to resolve exports from the entry point module.</returns>
public static IntPtr GetMainProgramHandle()
{
IntPtr result = IntPtr.Zero;
#if TARGET_WINDOWS
result = Interop.Kernel32.GetModuleHandle(null);
#else
result = Interop.Sys.GetDefaultSearchOrderPseudoHandle();
#endif
// I don't know when a failure case can occur here, but checking for it and throwing an exception
// if we encounter it.
if (result == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastPInvokeError());
}
return result;
}
}
}
......@@ -784,6 +784,7 @@ public partial class MarshalDirectiveException : System.SystemException
public static partial class NativeLibrary
{
public static void Free(System.IntPtr handle) { }
public static System.IntPtr GetMainProgramHandle() { throw null; }
public static System.IntPtr GetExport(System.IntPtr handle, string name) { throw null; }
public static System.IntPtr Load(string libraryPath) { throw null; }
public static System.IntPtr Load(string libraryName, System.Reflection.Assembly assembly, System.Runtime.InteropServices.DllImportSearchPath? searchPath) { throw null; }
......
......@@ -757,13 +757,27 @@ netcore_check_alc_cache (MonoAssemblyLoadContext *alc, const char *scope)
return result;
}
static MonoDl*
netcore_lookup_self_native_handle()
{
char *error_msg = NULL;
if (!internal_module)
internal_module = mono_dl_open_self (&error_msg);
if (!internal_module) {
mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, "DllImport error loading library '__Internal': '%s'.", error_msg);
g_free (error_msg);
}
mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via __Internal.");
return internal_module;
}
static MonoDl *
netcore_lookup_native_library (MonoAssemblyLoadContext *alc, MonoImage *image, const char *scope, guint32 flags)
{
MonoDl *module = NULL;
MonoDl *cached;
MonoAssembly *assembly = mono_image_get_assembly (image);
char *error_msg = NULL;
MONO_REQ_GC_UNSAFE_MODE;
......@@ -771,18 +785,7 @@ netcore_lookup_native_library (MonoAssemblyLoadContext *alc, MonoImage *image, c
// We allow a special name to dlopen from the running process namespace, which is not present in CoreCLR
if (strcmp (scope, "__Internal") == 0) {
if (!internal_module)
internal_module = mono_dl_open_self (&error_msg);
module = internal_module;
if (!module) {
mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_DLLIMPORT, "DllImport error loading library '__Internal': '%s'.", error_msg);
g_free (error_msg);
}
mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_DLLIMPORT, "Native library found via __Internal: '%s'.", scope);
return module;
return netcore_lookup_self_native_handle();
}
/*
......@@ -1219,9 +1222,9 @@ ves_icall_System_Runtime_InteropServices_NativeLibrary_FreeLib (gpointer lib, Mo
g_hash_table_add (native_library_module_blocklist, module);
mono_dl_close (module);
} else {
MonoDl raw_module = { { 0 } };
raw_module.handle = lib;
mono_dl_close (&raw_module);
MonoDl* raw_module = g_new0(MonoDl, 1);
raw_module->handle = lib;
mono_dl_close (raw_module);
}
leave:
......
......@@ -234,6 +234,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_LoadLibrary)
DllImportEntry(SystemNative_GetProcAddress)
DllImportEntry(SystemNative_FreeLibrary)
DllImportEntry(SystemNative_GetDefaultSearchOrderPseudoHandle)
DllImportEntry(SystemNative_SchedGetCpu)
DllImportEntry(SystemNative_Exit)
DllImportEntry(SystemNative_Abort)
......
......@@ -50,3 +50,20 @@ void SystemNative_FreeLibrary(void* handle)
{
dlclose(handle);
}
#ifdef TARGET_ANDROID
void* SystemNative_GetDefaultSearchOrderPseudoHandle(void)
{
return (void*)RTLD_DEFAULT;
}
#else
static void* g_defaultSearchOrderPseudoHandle = NULL;
void* SystemNative_GetDefaultSearchOrderPseudoHandle(void)
{
if (g_defaultSearchOrderPseudoHandle == NULL)
{
g_defaultSearchOrderPseudoHandle = dlopen(NULL, RTLD_LAZY);
}
return g_defaultSearchOrderPseudoHandle;
}
#endif
......@@ -11,3 +11,5 @@ PALEXPORT void* SystemNative_LoadLibrary(const char* filename);
PALEXPORT void* SystemNative_GetProcAddress(void* handle, const char* symbol);
PALEXPORT void SystemNative_FreeLibrary(void* handle);
PALEXPORT void* SystemNative_GetDefaultSearchOrderPseudoHandle(void);
......@@ -59,6 +59,7 @@ public static bool Verbose
}
public static bool IsX86 => (RuntimeInformation.ProcessArchitecture == Architecture.X86);
public static bool IsNotX86 => !IsX86;
public static bool IsX64 => (RuntimeInformation.ProcessArchitecture == Architecture.X64);
public static bool IsArm => (RuntimeInformation.ProcessArchitecture == Architecture.Arm);
public static bool IsArm64 => (RuntimeInformation.ProcessArchitecture == Architecture.Arm64);
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Xunit;
using static TestHelpers;
class GetLibraryExportTests : IDisposable
{
private readonly IntPtr handle;
public GetLibraryExportTests()
{
handle = NativeLibrary.Load(NativeLibraryToLoad.GetFullPath());
}
[ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsX86))]
public void GetValidExport_ManualMangling()
{
EXPECT(GetLibraryExport(handle, "_NativeSum@8"));
EXPECT(TryGetLibraryExport(handle, "_NativeSum@8"));
}
[ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotX86))]
public void GetValidExport()
{
EXPECT(GetLibraryExport(handle, "NativeSum"));
EXPECT(TryGetLibraryExport(handle, "NativeSum"));
}
[Fact]
public void NullHandle()
{
EXPECT(GetLibraryExport(IntPtr.Zero, "NativeSum"), TestResult.ArgumentNull);
EXPECT(TryGetLibraryExport(IntPtr.Zero, "NativeSum"), TestResult.ArgumentNull);
}
[Fact]
public void NullExport()
{
EXPECT(GetLibraryExport(handle, null), TestResult.ArgumentNull);
EXPECT(TryGetLibraryExport(handle, null), TestResult.ArgumentNull);
}
[Fact]
public void ExportDoesNotExist()
{
EXPECT(GetLibraryExport(handle, "NonNativeSum"), TestResult.EntryPointNotFound);
EXPECT(TryGetLibraryExport(handle, "NonNativeSum"), TestResult.ReturnFailure);
}
public void Dispose() => NativeLibrary.Free(handle);
static TestResult GetLibraryExport(IntPtr handle, string name)
{
return Run(() => {
IntPtr address = NativeLibrary.GetExport(handle, name);
if (address == IntPtr.Zero)
return TestResult.ReturnNull;
if (RunExportedFunction(address, 1, 1) != 2)
return TestResult.IncorrectEvaluation;
return TestResult.Success;
});
}
static TestResult TryGetLibraryExport(IntPtr handle, string name)
{
return Run(() => {
IntPtr address = IntPtr.Zero;
bool success = NativeLibrary.TryGetExport(handle, name, out address);
if (!success)
return TestResult.ReturnFailure;
if (address == IntPtr.Zero)
return TestResult.ReturnNull;
if (RunExportedFunction(address, 1, 1) != 2)
return TestResult.IncorrectEvaluation;
return TestResult.Success;
});
}
private static unsafe int RunExportedFunction(IntPtr address, int arg1, int arg2)
{
// We use a delegate here instead of a function pointer to avoid hitting issues
// where Mono AOT doesn't generate the managed->native wrapper and then fails
// when in AOT-only mode.
NativeFunctionWrapper wrapper = Marshal.GetDelegateForFunctionPointer<NativeFunctionWrapper>(address);
return wrapper(arg1, arg2);
}
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int NativeFunctionWrapper(int arg1, int arg2);
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Xunit;
using static TestHelpers;
class GetMainProgramHandleTests
{
// Mobile test runs aren't hosted by corerun, so we don't have a well-known export to test here
[ConditionalFact(nameof(IsHostedByCoreRun))]
public static void CanAccessCoreRunExportFromMainProgramHandle()
{
EXPECT(GetSymbolFromMainProgramHandle("HostExecutable", "GetCurrentClrDetails"));
EXPECT(GetSymbolFromMainProgramHandle("HostExecutable", "NonExistentCoreRunExport"), TestResult.ReturnFailure);
}
[Fact]
[SkipOnPlatform(TestPlatforms.OSX | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "Apple platforms load library symbols globally by default.")]
public static void NativeLibraryLoadDoesNotLoadSymbolsGlobally()
{
IntPtr handle = NativeLibrary.Load(NativeLibraryToLoad.GetFullPath());
try
{
// NativeLibrary does not load symbols globally, so we shouldn't be able to discover symbols from libaries loaded
// with NativeLibary.Load.
EXPECT(GetSymbolFromMainProgramHandle("LocallyLoadedNativeLib", TestLibrary.Utilities.IsX86 ? "_NativeSum@8" : "NativeSum"), TestResult.ReturnFailure);
EXPECT(GetSymbolFromMainProgramHandle("LocallyLoadedNativeLib", "NonNativeSum"), TestResult.ReturnFailure);
}
finally
{
NativeLibrary.Free(handle);
}
}
[ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotX86))]
[SkipOnPlatform(TestPlatforms.Windows, "Windows does not have a concept of globally loaded symbols")]
public static void GloballyLoadedLibrarySymbolsVisibleFromMainProgramHandle()
{
// On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable.
// Globally loading symbols is not the .NET default, so we use a call to dlopen in native code
// with the right flags to test the scenario.
IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));
try
{
EXPECT(GetSymbolFromMainProgramHandle("GloballyLoadedNativeLib", "NativeMultiply"));
}
finally
{
NativeLibrary.Free(handle);
}
}
[ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotX86))]
[SkipOnPlatform(TestPlatforms.Windows, "Windows does not have a concept of globally loaded symbols")]
public static void InvalidSymbolName_Fails()
{
// On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable.
// Globally loading symbols is not the .NET default, so we use a call to dlopen in native code
// with the right flags to test the scenario.
IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));
try
{
EXPECT(GetSymbolFromMainProgramHandle("GloballyLoadedNativeLib", "NonNativeMultiply"), TestResult.ReturnFailure);
}
finally
{
NativeLibrary.Free(handle);
}
}
[ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsX86))]
[SkipOnPlatform(TestPlatforms.Windows, "Windows does not have a concept of globally loaded symbols")]
public static void GloballyLoadedLibrarySymbolsVisibleFromMainProgramHandle_Mangling()
{
// On non-Windows platforms, symbols from globally loaded shared libraries will also be discoverable.
// Globally loading symbols is not the .NET default, so we use a call to dlopen in native code
// with the right flags to test the scenario.
IntPtr handle = LoadLibraryGlobally(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), NativeLibraryToLoad.GetLibraryFileName("GloballyLoadedNativeLibrary")));
try
{
EXPECT(GetSymbolFromMainProgramHandle("GloballyLoadedNativeLib", "_NativeMultiply@8"));
}
finally
{
NativeLibrary.Free(handle);
}
}
[Fact]
public static void FreeMainProgramHandle()
{
NativeLibrary.Free(NativeLibrary.GetMainProgramHandle());
Assert.True(true);
}
public static bool IsHostedByCoreRun { get; } = Process.GetCurrentProcess().MainModule.ModuleName is "corerun" or "corerun.exe";
static TestResult GetSymbolFromMainProgramHandle(string scenarioName, string symbolToLoadFromHandle)
{
return Run(() => {
IntPtr moduleHandle = NativeLibrary.GetMainProgramHandle();
bool success = NativeLibrary.TryGetExport(moduleHandle, symbolToLoadFromHandle, out IntPtr address);
if (!success)
return TestResult.ReturnFailure;
if (address == IntPtr.Zero)
return TestResult.ReturnNull;
return TestResult.Success;
});
}
static IntPtr LoadLibraryGlobally(string name)
{
IntPtr handle = LoadLibraryGloballyNative(name);
if (handle == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastPInvokeError());
}
return handle;
[DllImport("GlobalLoadHelper", EntryPoint = "LoadLibraryGlobally", SetLastError = true)]
static extern IntPtr LoadLibraryGloballyNative(string name);
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Xunit;
enum TestResult
{
Success,
ReturnFailure,
ReturnNull,
IncorrectEvaluation,
ArgumentNull,
ArgumentBad,
DllNotFound,
BadImage,
InvalidOperation,
EntryPointNotFound,
GenericException
};
static class TestHelpers
{
public static void EXPECT(TestResult actualValue, TestResult expectedValue = TestResult.Success)
{
Assert.Equal(expectedValue, actualValue);
}
public static TestResult Run (Func<TestResult> test)
{
TestResult result;
try
{
result = test();
}
catch (ArgumentNullException)
{
return TestResult.ArgumentNull;
}
catch (ArgumentException)
{
return TestResult.ArgumentBad;
}
catch (DllNotFoundException)
{
return TestResult.DllNotFound;
}
catch (BadImageFormatException)
{
return TestResult.BadImage;
}
catch (InvalidOperationException)
{
return TestResult.InvalidOperation;
}
catch (EntryPointNotFoundException)
{
return TestResult.EntryPointNotFound;
}
catch (Exception)
{
return TestResult.GenericException;
}
return result;
}
}
project (NativeLibrary)
include ("${CLR_INTEROP_TEST_ROOT}/Interop.cmake")
set(SOURCES NativeLibrary.cpp)
# add the shared library
add_library (NativeLibrary SHARED ${SOURCES})
target_link_libraries(NativeLibrary ${LINK_LIBRARIES_ADDITIONAL})
add_library (NativeLibrary SHARED NativeLibrary.cpp)
# add the install targets
install (TARGETS NativeLibrary DESTINATION bin)
if (CLR_CMAKE_TARGET_UNIX)
target_link_libraries(NativeLibrary ${LINK_LIBRARIES_ADDITIONAL})
endif()
# add the shared library
add_library (GlobalLoadHelper SHARED GlobalLoadHelper.cpp)
target_link_libraries(GlobalLoadHelper ${LINK_LIBRARIES_ADDITIONAL})
add_library (GloballyLoadedNativeLibrary SHARED GloballyLoadedNativeLibrary.cpp)
target_link_libraries(GloballyLoadedNativeLibrary ${LINK_LIBRARIES_ADDITIONAL})
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#include <stdio.h>
#include <platformdefines.h>
#ifndef TARGET_WINDOWS
#include <dlfcn.h>
#endif
extern "C" DLL_EXPORT void* LoadLibraryGlobally(const char* name)
{
#ifdef TARGET_WINDOWS
// Windows doesn't support global symbol loading.
return NULL;
#else
return dlopen(name, RTLD_GLOBAL | RTLD_LAZY);
#endif
}
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#include <stdio.h>
#include <platformdefines.h>
extern "C" DLL_EXPORT int NativeMultiply(int a, int b)
{
return a * b;
}
......@@ -2,16 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
#include <stdio.h>
#include <platformdefines.h>
#ifndef TARGET_WINDOWS
#include <dlfcn.h>
#endif
extern "C" DLL_EXPORT int NativeSum(int a, int b)
{
return a + b;
}
extern "C" DLL_EXPORT int RunExportedFunction(void *function, int arg1, int arg2)
{
int(*f)(int, int) = reinterpret_cast<int(*)(int,int)>(function);
return f(arg1, arg2);
}
......@@ -12,15 +12,20 @@ public class NativeLibraryToLoad
public const string InvalidName = "DoesNotExist";
public static string GetFileName()
{
return GetLibraryFileName(Name);
}
public static string GetLibraryFileName(string name)
{
if (OperatingSystem.IsWindows())
return $"{Name}.dll";
return $"{name}.dll";
if (OperatingSystem.IsLinux())
return $"lib{Name}.so";
return $"lib{name}.so";
if (OperatingSystem.IsMacOS())
return $"lib{Name}.dylib";
return $"lib{name}.dylib";
throw new PlatformNotSupportedException();
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册