Add nethost for ps4

This commit is contained in:
Wojtek Figat
2023-03-11 21:30:56 +01:00
parent b7d4758dde
commit e83b8afdd3
13 changed files with 510 additions and 51 deletions

BIN
Source/Platforms/DotNet/System.Text.Encoding.CodePages.dll (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<doc>
<assembly>
<name>System.Text.Encoding.CodePages</name>
</assembly>
<members>
<member name="T:System.Text.CodePagesEncodingProvider">
<summary>Provides access to an encoding provider for code pages that otherwise are available only in the desktop .NET Framework.</summary>
</member>
<member name="M:System.Text.CodePagesEncodingProvider.GetEncoding(System.Int32)">
<summary>Returns the encoding associated with the specified code page identifier.</summary>
<param name="codepage">The code page identifier of the preferred encoding which the encoding provider may support.</param>
<returns>The encoding associated with the specified code page identifier, or <see langword="null" /> if the provider does not support the requested codepage encoding.</returns>
</member>
<member name="M:System.Text.CodePagesEncodingProvider.GetEncoding(System.String)">
<summary>Returns the encoding associated with the specified code page name.</summary>
<param name="name">The code page name of the preferred encoding which the encoding provider may support.</param>
<returns>The encoding associated with the specified code page, or <see langword="null" /> if the provider does not support the requested encoding.</returns>
</member>
<member name="M:System.Text.CodePagesEncodingProvider.GetEncodings">
<summary>Returns an array that contains all the encodings that are supported by the <see cref="T:System.Text.CodePagesEncodingProvider" />.</summary>
<returns>An array that contains all the supported encodings.</returns>
</member>
<member name="P:System.Text.CodePagesEncodingProvider.Instance">
<summary>Gets an encoding provider for code pages supported in the desktop .NET Framework but not in the current .NET Framework platform.</summary>
<returns>An encoding provider that allows access to encodings not supported on the current .NET Framework platform.</returns>
</member>
</members>
</doc>

View File

@@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View File

@@ -52,9 +52,6 @@ public class nethost : ThirdPartyModule
break;
case TargetPlatform.Linux:
case TargetPlatform.Android:
case TargetPlatform.Switch:
case TargetPlatform.PS4:
case TargetPlatform.PS5:
options.OutputFiles.Add(Path.Combine(hostRuntimePath, "libnethost.a"));
options.DependencyFiles.Add(Path.Combine(hostRuntimePath, "libnethost.so"));
break;
@@ -62,6 +59,12 @@ public class nethost : ThirdPartyModule
options.OutputFiles.Add(Path.Combine(hostRuntimePath, "libnethost.a"));
options.DependencyFiles.Add(Path.Combine(hostRuntimePath, "libnethost.dylib"));
break;
case TargetPlatform.Switch:
case TargetPlatform.PS4:
case TargetPlatform.PS5:
options.OutputFiles.Add(Path.Combine(hostRuntimePath, "libnethost.a"));
//options.OutputFiles.Add(Path.Combine(hostRuntimePath, "libhostfxr.a"));
break;
default:
throw new InvalidPlatformException(options.Platform.Target);
}

View File

@@ -77,8 +77,7 @@ namespace Flax.Build
case TargetArchitecture.ARM64:
arch = "arm64";
break;
default:
throw new InvalidArchitectureException(architecture);
default: throw new InvalidArchitectureException(architecture);
}
switch (platform)
{
@@ -120,8 +119,7 @@ namespace Flax.Build
dotnetPath = "/usr/local/share/dotnet/";
break;
}
default:
throw new InvalidPlatformException(platform);
default: throw new InvalidPlatformException(platform);
}
// Pick SDK version
@@ -171,7 +169,7 @@ namespace Flax.Build
TryAddHostRuntime(TargetPlatform.Windows, TargetArchitecture.ARM64, "win-arm64");
TryAddHostRuntime(TargetPlatform.Mac, TargetArchitecture.x64, "osx-x64");
TryAddHostRuntime(TargetPlatform.Mac, TargetArchitecture.ARM64, "osx-arm64");
// Found
IsValid = true;
Log.Verbose($"Found .NET SDK {VersionName} (runtime {RuntimeVersionName}) at {RootPath}");
@@ -180,14 +178,26 @@ namespace Flax.Build
}
/// <summary>
/// Gets the path to runtime host contents folder for a given target platform and architecure.
/// In format: <RootPath>/packs/Microsoft.NETCore.App.Host.<os>/<VersionName>/runtimes/<os>-<arch>/native
/// Gets the path to runtime host contents folder for a given target platform and architecture.
/// In format: &lt;RootPath&gt;/packs/Microsoft.NETCore.App.Host.&lt;os&gt;/&lt;VersionName&gt;/runtimes/&lt;os&gt;-&lt;arch&gt;/native
/// </summary>
public bool GetHostRuntime(TargetPlatform platform, TargetArchitecture arch, out string path)
{
return _hostRuntimes.TryGetValue(new KeyValuePair<TargetPlatform, TargetArchitecture>(platform, arch), out path);
}
/// <summary>
/// Adds an external hostfx location for a given platform.
/// </summary>
/// <param name="platform">Target platform.</param>
/// <param name="arch">Target architecture.</param>
/// <param name="path">Folder path with contents.</param>
public void AddHostRuntime(TargetPlatform platform, TargetArchitecture arch, string path)
{
if (Directory.Exists(path))
_hostRuntimes[new KeyValuePair<TargetPlatform, TargetArchitecture>(platform, arch)] = path;
}
private bool TryAddHostRuntime(TargetPlatform platform, TargetArchitecture arch, string rid)
{
if (string.IsNullOrEmpty(rid))

View File

@@ -215,7 +215,7 @@ namespace Flax.Deploy
throw new Exception(string.Format("Project {0} does not exist!", project));
}
string cmdLine = string.Format("\"{0}\" /m /t:Build /p:Configuration=\"{1}\" /p:Platform=\"{2}\" {3} /nologo", project, buildConfig, buildPlatform, Verbosity);
string cmdLine = string.Format("\"{0}\" /m /t:Restore,Build /p:Configuration=\"{1}\" /p:Platform=\"{2}\" {3} /nologo", project, buildConfig, buildPlatform, Verbosity);
int result = Utilities.Run(msBuild, cmdLine);
if (result != 0)
{
@@ -245,7 +245,7 @@ namespace Flax.Deploy
throw new Exception(string.Format("Unable to build solution {0}. Solution file not found.", solutionFile));
}
string cmdLine = string.Format("\"{0}\" /m /t:Build /p:Configuration=\"{1}\" /p:Platform=\"{2}\" {3} /nologo", solutionFile, buildConfig, buildPlatform, Verbosity);
string cmdLine = string.Format("\"{0}\" /m /t:Restore,Build /p:Configuration=\"{1}\" /p:Platform=\"{2}\" {3} /nologo", solutionFile, buildConfig, buildPlatform, Verbosity);
if (props != null)
{
foreach (var e in props)

View File

@@ -75,7 +75,7 @@ namespace Flax.Deps.Dependencies
}
// AOT build (disabled codegen)
Utilities.ReplaceInFile(Path.Combine(root, "Src", "Newtonsoft.Json", "Newtonsoft.Json.csproj"), "HAVE_RUNTIME_SERIALIZATION;", ";");
Utilities.ReplaceInFile(Path.Combine(root, "Src", "Newtonsoft.Json", "Newtonsoft.Json.csproj"), "HAVE_REFLECTION_EMIT;", ";");
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, buildPlatform);
foreach (var platform in options.Platforms)
{

View File

@@ -212,39 +212,8 @@ namespace Flax.Deps.Dependencies
{
case TargetPlatform.Windows:
{
msBuild = VCEnvironment.MSBuildPath;
// Some consoles don't support the latest Visual Studio 2022
var vsVersion = VisualStudioVersion.VisualStudio2022;
switch (targetPlatform)
{
case TargetPlatform.PS4:
vsVersion = VisualStudioVersion.VisualStudio2017;
break;
case TargetPlatform.PS5:
case TargetPlatform.Switch:
vsVersion = VisualStudioVersion.VisualStudio2019;
break;
}
if (vsVersion != VisualStudioVersion.VisualStudio2022)
{
// TODO: override VS version in cmake_generate_projects.py too
var visualStudioInstances = VisualStudioInstance.GetInstances();
foreach (var visualStudioInstance in visualStudioInstances)
{
if (visualStudioInstance.Version <= vsVersion)
{
var toolPath = Path.Combine(visualStudioInstance.Path, "MSBuild\\Current\\Bin\\MSBuild.exe");
if (!File.Exists(toolPath))
toolPath = Path.Combine(visualStudioInstance.Path, "MSBuild\\15.0\\Bin\\MSBuild.exe");
if (File.Exists(toolPath))
{
msBuild = toolPath;
break;
}
}
}
}
GetMsBuildForPlatform(targetPlatform, out var vsVersion, out msBuild);
// TODO: override VS version in cmake_generate_projects.py too
if (File.Exists(msBuild))
{
envVars.Add("PATH", Path.GetDirectoryName(msBuild));

View File

@@ -0,0 +1,244 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Flax.Build;
using Flax.Deploy;
using Ionic.Zip;
namespace Flax.Deps.Dependencies
{
/// <summary>
/// .NET runtime
/// </summary>
/// <seealso cref="Flax.Deps.Dependency" />
class nethost : Dependency
{
/// <inheritdoc />
public override TargetPlatform[] Platforms
{
get
{
switch (BuildPlatform)
{
case TargetPlatform.Windows:
return new[]
{
TargetPlatform.PS4,
};
default: return new TargetPlatform[0];
}
}
}
private string root;
private void Build(BuildOptions options, TargetPlatform targetPlatform, TargetArchitecture architecture)
{
// Build configuration (see build.cmd -help)
string configuration = "Release";
string framework = "net7.0";
// Clean output directory
var artifacts = Path.Combine(root, "artifacts");
SetupDirectory(artifacts, true);
// Peek options
string os, arch, runtimeFlavor, subset, hostRuntimeName, buildArgsBase = string.Empty;
bool setupVersion = false;
string[] hostRuntimeFiles = Array.Empty<string>();
var envVars = new Dictionary<string, string>();
switch (architecture)
{
case TargetArchitecture.x86:
arch = "x86";
break;
case TargetArchitecture.x64:
arch = "x64";
break;
case TargetArchitecture.ARM:
arch = "arm";
break;
case TargetArchitecture.ARM64:
arch = "arm64";
break;
default: throw new InvalidArchitectureException(architecture);
}
switch (targetPlatform)
{
case TargetPlatform.Windows:
os = "windows";
runtimeFlavor = "CoreCLR";
subset = "clr";
hostRuntimeName = $"win-{arch}";
break;
case TargetPlatform.Linux:
os = "Linux";
runtimeFlavor = "CoreCLR";
subset = "clr";
hostRuntimeName = $"linux-{arch}";
break;
case TargetPlatform.Mac:
os = "OSX";
runtimeFlavor = "CoreCLR";
subset = "clr";
hostRuntimeName = $"osx-{arch}";
break;
case TargetPlatform.Android:
os = "Android";
runtimeFlavor = "Mono";
subset = "mono+libs";
hostRuntimeName = $"android-{arch}";
break;
case TargetPlatform.PS4:
os = "PS4";
runtimeFlavor = "Mono";
subset = "mono+libs";
setupVersion = true;
buildArgsBase = " /p:RuntimeOS=ps4 -cmakeargs \"-DCLR_CROSS_COMPONENTS_BUILD=1\"";
hostRuntimeName = $"ps4-{arch}";
hostRuntimeFiles = new[]
{
"coreclr_delegates.h",
"hostfxr.h",
"nethost.h",
"libnethost.a",
};
break;
default: throw new InvalidPlatformException(targetPlatform);
}
// Setup build environment variables for build system
string buildScript = null;
switch (BuildPlatform)
{
case TargetPlatform.Windows:
{
buildScript = "build.cmd";
if (setupVersion)
{
// Setup _version.c file manually
SetupDirectory(Path.Combine(artifacts, "obj"), false);
foreach (var file in new[] { "_version.c", "_version.h" })
File.Copy(Path.Combine(root, "eng", "native", "version", file), Path.Combine(artifacts, "obj", file), true);
}
var msBuild = VCEnvironment.MSBuildPath;
if (File.Exists(msBuild))
{
envVars.Add("PATH", Path.GetDirectoryName(msBuild));
}
break;
}
case TargetPlatform.Linux:
{
buildScript = "build.sh";
break;
}
case TargetPlatform.Mac:
{
buildScript = "build.sh";
break;
}
default: throw new InvalidPlatformException(BuildPlatform);
}
// Print the runtime version
string version;
{
var doc = new XmlDocument();
doc.Load(Path.Combine(root, "eng", "Versions.props"));
version = doc["Project"]["PropertyGroup"]["ProductVersion"].InnerText;
Log.Info("Building dotnet/runtime version " + version);
}
// Build
buildArgsBase = $"-os {os} -a {arch} -f {framework} -c {configuration} -lc {configuration} -rc {configuration} -rf {runtimeFlavor}{buildArgsBase}";
foreach (var buildStep in new[] { subset, "host.pkg", "packs.product" })
{
var buildArgs = $"{buildArgsBase} {buildStep}";
if (BuildPlatform == TargetPlatform.Windows)
{
// For some reason running from Visual Studio fails the build so use command shell
//buildArgs = $"/C {buildScript} {buildArgs}";
//buildApp = "cmd.exe";
// TODO: maybe write command line into bat file and run it here?
WinAPI.SetClipboard($"{buildScript} {buildArgs}");
WinAPI.MessageBox.Show($"Open console command in folder '{root}' and run command from clipboard. Then close this dialog.", "Run command manually", WinAPI.MessageBox.Buttons.Ok);
}
else
{
Utilities.Run(Path.Combine(root, buildScript), buildArgs, null, root, Utilities.RunOptions.ThrowExceptionOnError, envVars);
}
}
// Deploy build products
var dstBinaries = GetThirdPartyFolder(options, targetPlatform, architecture);
var srcHostRuntime = Path.Combine(artifacts, "bin", $"{hostRuntimeName}.{configuration}", "corehost");
foreach (var file in hostRuntimeFiles)
{
Utilities.FileCopy(Path.Combine(srcHostRuntime, file), Path.Combine(dstBinaries, file));
}
var dstDotnet = Path.Combine(dstBinaries, "Dotnet");
var dstClassLibrary = Path.Combine(dstDotnet, "shared", "Microsoft.NETCore.App", version);
SetupDirectory(dstClassLibrary, true);
foreach (var file in new[]
{
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
})
{
Utilities.FileCopy(Path.Combine(root, file), Path.Combine(dstDotnet, file));
}
var srcDotnetLibsPkg = Path.Combine(artifacts, "packages", "Release", "Shipping", $"Microsoft.NETCore.App.Runtime.Mono.{hostRuntimeName}.{version}.nupkg");
if (!File.Exists(srcDotnetLibsPkg))
throw new Exception($"Missing .NET Core App class library package at '{srcDotnetLibsPkg}'");
var srcDotnetLibsPkgTemp = srcDotnetLibsPkg + "Temp";
using (var zip = new ZipFile(srcDotnetLibsPkg))
{
zip.ExtractAll(srcDotnetLibsPkgTemp, ExtractExistingFileAction.OverwriteSilently);
}
var privateCorelib = "System.Private.CoreLib.dll";
Utilities.FileCopy(Path.Combine(srcDotnetLibsPkgTemp, "runtimes", hostRuntimeName, "native", privateCorelib), Path.Combine(dstClassLibrary, privateCorelib));
Utilities.DirectoryCopy(Path.Combine(srcDotnetLibsPkgTemp, "runtimes", hostRuntimeName, "lib", "net7.0"), dstClassLibrary, false, true);
Utilities.DirectoriesDelete(srcDotnetLibsPkgTemp);
// TODO: host/fxr/<version>/hostfxr.dll
// TODO: shared/Microsoft.NETCore.App/<version>/hostpolicy.dl
// TODO: shared/Microsoft.NETCore.App/<version>/System.IO.Compression.Native.dll
}
/// <inheritdoc />
public override void Build(BuildOptions options)
{
root = options.IntermediateFolder;
// Ensure to have dependencies installed
Utilities.Run("ninja", "--version", null, null, Utilities.RunOptions.ThrowExceptionOnError);
Utilities.Run("cmake", "--version", null, null, Utilities.RunOptions.ThrowExceptionOnError);
// Get the source
CloneGitRepo(root, "https://github.com/FlaxEngine/dotnet-runtime.git", "flax-master", null, true);
SetupDirectory(Path.Combine(root, "src", "external"), false);
foreach (var platform in options.Platforms)
{
var platformData = Path.Combine(GetBinariesFolder(options, platform), "Data", "nethost");
if (Directory.Exists(platformData))
Utilities.DirectoryCopy(platformData, root, true, true);
switch (platform)
{
case TargetPlatform.PS4:
{
Build(options, platform, TargetArchitecture.x64);
break;
}
}
}
// Copy license
var dstIncludePath = Path.Combine(options.ThirdPartyFolder, "nethost");
Utilities.FileCopy(Path.Combine(root, "LICENSE.TXT"), Path.Combine(dstIncludePath, "LICENSE.TXT"));
}
}
}

View File

@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.IO;
using Flax.Build;
using Flax.Build.Platforms;
using Flax.Build.Projects.VisualStudio;
using Flax.Deploy;
namespace Flax.Deps
{
@@ -118,13 +120,16 @@ namespace Flax.Deps
/// <param name="url">The remote url.</param>
/// <param name="commit">The commit to checkout.</param>
/// <param name="args">The custom arguments to add to the clone command.</param>
public static void CloneGitRepo(string path, string url, string commit = null, string args = null)
/// <param name="submodules">True if initialize submodules of the repository (recursive).</param>
public static void CloneGitRepo(string path, string url, string commit = null, string args = null, bool submodules = false)
{
if (!Directory.Exists(Path.Combine(path, Path.GetFileNameWithoutExtension(url), ".git")))
{
string cmdLine = string.Format("clone \"{0}\" \"{1}\"", url, path);
if (args != null)
cmdLine += " " + args;
if (submodules)
cmdLine += " --recurse-submodules";
Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None);
}
@@ -141,13 +146,16 @@ namespace Flax.Deps
/// <param name="path">The local path for close.</param>
/// <param name="url">The remote url.</param>
/// <param name="args">The custom arguments to add to the clone command.</param>
public static void CloneGitRepoFast(string path, string url, string args = null)
/// <param name="submodules">True if initialize submodules of the repository (recursive).</param>
public static void CloneGitRepoFast(string path, string url, string args = null, bool submodules = false)
{
if (!Directory.Exists(Path.Combine(path, Path.GetFileNameWithoutExtension(url), ".git")))
{
string cmdLine = string.Format("clone \"{0}\" \"{1}\" --depth 1", url, path);
if (args != null)
cmdLine += " " + args;
if (submodules)
cmdLine += " --recurse-submodules";
Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None);
}
@@ -161,7 +169,8 @@ namespace Flax.Deps
/// <param name="branch">The name of the branch to checkout.</param>
/// <param name="commit">The commit to checkout.</param>
/// <param name="args">The custom arguments to add to the clone command.</param>
public static void CloneGitRepoSingleBranch(string path, string url, string branch, string commit = null, string args = null)
/// <param name="submodules">True if initialize submodules of the repository (recursive).</param>
public static void CloneGitRepoSingleBranch(string path, string url, string branch, string commit = null, string args = null, bool submodules = false)
{
if (!Directory.Exists(Path.Combine(path, ".git")))
{
@@ -170,6 +179,8 @@ namespace Flax.Deps
cmdLine += " --depth 1";
if (args != null)
cmdLine += " " + args;
if (submodules)
cmdLine += " --recurse-submodules";
Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None);
}
@@ -187,11 +198,14 @@ namespace Flax.Deps
/// <param name="branch">The name of the branch to checkout.</param>
/// <param name="commit">The commit to checkout.</param>
/// <param name="args">The custom arguments to add to the clone command.</param>
public static void GitCheckout(string path, string branch, string commit = null, string args = null)
/// <param name="submodules">True if initialize submodules of the repository (recursive).</param>
public static void GitCheckout(string path, string branch, string commit = null, string args = null, bool submodules = false)
{
string cmdLine = string.Format("checkout -B {0} origin/{0}", branch);
if (args != null)
cmdLine += " " + args;
if (submodules)
cmdLine += " --recurse-submodules";
Utilities.Run("git", cmdLine, null, path, Utilities.RunOptions.None);
@@ -384,5 +398,42 @@ namespace Flax.Deps
}
Utilities.Run(path, args, null, workspace, Utilities.RunOptions.ThrowExceptionOnError, envVars);
}
internal bool GetMsBuildForPlatform(TargetPlatform targetPlatform, out VisualStudioVersion vsVersion, out string msBuildPath)
{
// Some consoles don't support the latest Visual Studio 2022
vsVersion = VisualStudioVersion.VisualStudio2022;
switch (targetPlatform)
{
case TargetPlatform.PS4:
vsVersion = VisualStudioVersion.VisualStudio2017;
break;
case TargetPlatform.PS5:
case TargetPlatform.Switch:
vsVersion = VisualStudioVersion.VisualStudio2019;
break;
}
if (vsVersion != VisualStudioVersion.VisualStudio2022)
{
var visualStudioInstances = VisualStudioInstance.GetInstances();
foreach (var visualStudioInstance in visualStudioInstances)
{
if (visualStudioInstance.Version <= vsVersion)
{
var toolPath = Path.Combine(visualStudioInstance.Path, "MSBuild\\Current\\Bin\\MSBuild.exe");
if (!File.Exists(toolPath))
toolPath = Path.Combine(visualStudioInstance.Path, "MSBuild\\15.0\\Bin\\MSBuild.exe");
if (File.Exists(toolPath))
{
vsVersion = visualStudioInstance.Version;
msBuildPath = toolPath;
return true;
}
}
}
}
msBuildPath = VCEnvironment.MSBuildPath;
return false;
}
}
}

View File

@@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using Flax.Build;
namespace Flax.Deps
@@ -21,6 +22,9 @@ namespace Flax.Deps
public static bool Run()
{
// Fix error 'IBM437' is not a supported encoding name from Ionic.Zip.ZipFile
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// Setup
var buildPlatform = Platform.BuildPlatform;
var options = new Dependency.BuildOptions

View File

@@ -38,6 +38,9 @@
<Reference Include="Ionic.Zip.Reduced">
<HintPath>..\..\..\Source\Platforms\DotNet\Ionic.Zip.Reduced.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding.CodePages">
<HintPath>..\..\..\Source\Platforms\DotNet\System.Text.Encoding.CodePages.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\..\Source\Platforms\DotNet\Newtonsoft.Json.dll</HintPath>
</Reference>

View File

@@ -26,5 +26,148 @@ namespace Flax.Build
public delegate bool SymEnumerateSymbolsProc64(string SymbolName, ulong SymbolAddress, uint SymbolSize, IntPtr UserContext);
}
public static class kernel32
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GlobalUnlock(IntPtr hMem);
}
public static class user32
{
[DllImport("user32.dll")]
public static extern bool EmptyClipboard();
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseClipboard();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetClipboardData(uint uFormat, IntPtr data);
[DllImport("user32.dll")]
public static extern int MessageBoxA(IntPtr hWnd, string lpText, string lpCaption, uint uType);
}
// https://github.com/CopyText/TextCopy/blob/main/src/TextCopy/WindowsClipboard.cs
public static void SetClipboard(string text)
{
user32.OpenClipboard(IntPtr.Zero);
user32.EmptyClipboard();
IntPtr hGlobal = default;
try
{
var bytes = (text.Length + 1) * 2;
hGlobal = Marshal.AllocHGlobal(bytes);
var target = kernel32.GlobalLock(hGlobal);
try
{
Marshal.Copy(text.ToCharArray(), 0, target, text.Length);
}
finally
{
kernel32.GlobalUnlock(target);
}
user32.SetClipboardData(13, hGlobal);
hGlobal = default;
}
finally
{
if (hGlobal != default)
{
Marshal.FreeHGlobal(hGlobal);
}
user32.CloseClipboard();
}
}
// https://gist.github.com/nathan130200/53dec19cdcd895281fb568ec63e1275b
public static class MessageBox
{
public enum Buttons
{
AbortRetryIgnore = 0x00000002,
CancelTryIgnore = 0x00000006,
Help = 0x00004000,
Ok = 0x00000000,
OkCancel = 0x00000001,
RetryCancel = 0x00000005,
YesNo = 0x00000004,
YesNoCancel = 0x00000003,
}
public enum Result
{
Abort = 3,
Cancel = 2,
Continue = 11,
Ignore = 5,
No = 7,
Ok = 1,
Retry = 10,
Yes = 6,
}
public enum DefaultButton : uint
{
Button1 = 0x00000000,
Button2 = 0x00000100,
Button3 = 0x00000200,
Button4 = 0x00000300,
}
public enum Modal : uint
{
Application = 0x00000000,
System = 0x00001000,
Task = 0x00002000
}
public enum Icon : uint
{
Warning = 0x00000030,
Information = 0x00000040,
Question = 0x00000020,
Error = 0x00000010
}
public static Result Show(string text)
{
return (Result)user32.MessageBoxA(IntPtr.Zero, text, "\0", (uint)Buttons.Ok);
}
public static Result Show(string text, string caption)
{
return (Result)user32.MessageBoxA(IntPtr.Zero, text, caption, (uint)Buttons.Ok);
}
public static Result Show(string text, string caption, Buttons buttons)
{
return (Result)user32.MessageBoxA(IntPtr.Zero, text, caption, (uint)buttons);
}
public static Result Show(string text, string caption, Buttons buttons, Icon icon)
{
return (Result)user32.MessageBoxA(IntPtr.Zero, text, caption, ((uint)buttons) | ((uint)icon));
}
public static Result Show(string text, string caption, Buttons buttons, Icon icon, DefaultButton button)
{
return (Result)user32.MessageBoxA(IntPtr.Zero, text, caption, ((uint)buttons) | ((uint)icon) | ((uint)button));
}
public static Result Show(string text, string caption, Buttons buttons, Icon icon, DefaultButton button, Modal modal)
{
return (Result)user32.MessageBoxA(IntPtr.Zero, text, caption, ((uint)buttons) | ((uint)icon) | ((uint)button) | ((uint)modal));
}
}
}
}