From aea8c11dc46ed52cb0c99c020fc430937aeb6f20 Mon Sep 17 00:00:00 2001 From: Robert McRackan Date: Fri, 12 Aug 2022 13:49:51 -0400 Subject: [PATCH] Add OS-specific interop --- Source/AppScaffolding/LibationScaffolding.cs | 43 +++-- .../OSInterop/IInteropFunctions.cs | 15 ++ .../OSInterop/NullInteropFunctions.cs | 16 ++ .../AppScaffolding/OSInterop/OSConfigBase.cs | 29 ++++ .../OSInterop/OSInteropProxy.cs | 147 ++++++++++++++++++ Source/Libation.sln | 55 ++++++- .../LinuxConfigApp/LinuxConfigApp.csproj | 25 +++ .../LoadByOS/LinuxConfigApp/LinuxInterop.cs | 28 ++++ Source/LoadByOS/LinuxConfigApp/Program.cs | 11 ++ .../MacOSConfigApp/MacOSConfigApp.csproj | 25 +++ .../LoadByOS/MacOSConfigApp/MacOSInterop.cs | 28 ++++ Source/LoadByOS/MacOSConfigApp/Program.cs | 11 ++ Source/LoadByOS/README.txt | 1 + .../WindowsConfigApp/Form1.Designer.cs | 39 +++++ Source/LoadByOS/WindowsConfigApp/Form1.cs | 10 ++ Source/LoadByOS/WindowsConfigApp/Program.cs | 18 +++ .../LoadByOS/WindowsConfigApp/WinInterop.cs | 33 ++++ .../WindowsConfigApp/WindowsConfigApp.csproj | 26 ++++ .../CrossPlatformClientExe.csproj | 23 +++ .../IInteropFunctions.cs | 12 ++ .../NullInteropFunctions.cs | 14 ++ .../CrossPlatformClientExe/OSConfigBase.cs | 29 ++++ .../CrossPlatformClientExe/OSInteropProxy.cs | 138 ++++++++++++++++ .../CrossPlatformClientExe/Program.cs | 19 +++ .../LinuxConfigApp/LinuxConfigApp.csproj | 25 +++ .../LoadByOS/LinuxConfigApp/LinuxInterop.cs | 23 +++ .../_Demos/LoadByOS/LinuxConfigApp/Program.cs | 11 ++ .../WindowsConfigApp/Form1.Designer.cs | 39 +++++ .../_Demos/LoadByOS/WindowsConfigApp/Form1.cs | 10 ++ .../LoadByOS/WindowsConfigApp/Form1.resx | 120 ++++++++++++++ .../LoadByOS/WindowsConfigApp/Program.cs | 18 +++ .../LoadByOS/WindowsConfigApp/WinInterop.cs | 29 ++++ .../WindowsConfigApp/WindowsConfigApp.csproj | 26 ++++ 33 files changed, 1083 insertions(+), 13 deletions(-) create mode 100644 Source/AppScaffolding/OSInterop/IInteropFunctions.cs create mode 100644 Source/AppScaffolding/OSInterop/NullInteropFunctions.cs create mode 100644 Source/AppScaffolding/OSInterop/OSConfigBase.cs create mode 100644 Source/AppScaffolding/OSInterop/OSInteropProxy.cs create mode 100644 Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj create mode 100644 Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs create mode 100644 Source/LoadByOS/LinuxConfigApp/Program.cs create mode 100644 Source/LoadByOS/MacOSConfigApp/MacOSConfigApp.csproj create mode 100644 Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs create mode 100644 Source/LoadByOS/MacOSConfigApp/Program.cs create mode 100644 Source/LoadByOS/README.txt create mode 100644 Source/LoadByOS/WindowsConfigApp/Form1.Designer.cs create mode 100644 Source/LoadByOS/WindowsConfigApp/Form1.cs create mode 100644 Source/LoadByOS/WindowsConfigApp/Program.cs create mode 100644 Source/LoadByOS/WindowsConfigApp/WinInterop.cs create mode 100644 Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj create mode 100644 Source/_Demos/LoadByOS/CrossPlatformClientExe/CrossPlatformClientExe.csproj create mode 100644 Source/_Demos/LoadByOS/CrossPlatformClientExe/IInteropFunctions.cs create mode 100644 Source/_Demos/LoadByOS/CrossPlatformClientExe/NullInteropFunctions.cs create mode 100644 Source/_Demos/LoadByOS/CrossPlatformClientExe/OSConfigBase.cs create mode 100644 Source/_Demos/LoadByOS/CrossPlatformClientExe/OSInteropProxy.cs create mode 100644 Source/_Demos/LoadByOS/CrossPlatformClientExe/Program.cs create mode 100644 Source/_Demos/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj create mode 100644 Source/_Demos/LoadByOS/LinuxConfigApp/LinuxInterop.cs create mode 100644 Source/_Demos/LoadByOS/LinuxConfigApp/Program.cs create mode 100644 Source/_Demos/LoadByOS/WindowsConfigApp/Form1.Designer.cs create mode 100644 Source/_Demos/LoadByOS/WindowsConfigApp/Form1.cs create mode 100644 Source/_Demos/LoadByOS/WindowsConfigApp/Form1.resx create mode 100644 Source/_Demos/LoadByOS/WindowsConfigApp/Program.cs create mode 100644 Source/_Demos/LoadByOS/WindowsConfigApp/WinInterop.cs create mode 100644 Source/_Demos/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj diff --git a/Source/AppScaffolding/LibationScaffolding.cs b/Source/AppScaffolding/LibationScaffolding.cs index 1f08aedd..eb113cdd 100644 --- a/Source/AppScaffolding/LibationScaffolding.cs +++ b/Source/AppScaffolding/LibationScaffolding.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; using ApplicationServices; +using AppScaffolding.OSInterop; using AudibleUtilities; using Dinah.Core.Collections.Generic; using Dinah.Core.IO; @@ -59,8 +60,10 @@ namespace AppScaffolding ??= new[] { ExecutingAssembly.GetName(), EntryAssembly.GetName() } .Max(a => a.Version); - /// Run migrations before loading Configuration for the first time. Then load and return Configuration - public static Configuration RunPreConfigMigrations() + public static OSInteropProxy InteropInstance { get; private set; } + + /// Run migrations before loading Configuration for the first time. Then load and return Configuration + public static Configuration RunPreConfigMigrations() { // must occur before access to Configuration instance // // outdated. kept here as an example of what belongs in this area @@ -187,6 +190,9 @@ namespace AppScaffolding configureLogging(config); logStartupState(config); + // all else should occur after logging + + loadOSInterop(config); wireUpSystemEvents(config); } @@ -244,8 +250,8 @@ namespace AppScaffolding // However, empirical testing so far has shown no issues. Console.SetOut(new MultiTextWriter(origOut, new SerilogTextWriter())); - #region Console => Serilog tests - /* + #region Console => Serilog tests + /* // all below apply to "Console." and "Console.Out." // captured @@ -284,12 +290,12 @@ namespace AppScaffolding Console.Write("{0}{1}{2}", "zero|", "one|", "two"); Console.Write("{0}", new object[] { "arr" }); */ - #endregion + #endregion - // .Here() captures debug info via System.Runtime.CompilerServices attributes. Warning: expensive - //var withLineNumbers_outputTemplate = "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}in method {MemberName} at {FilePath}:{LineNumber}{NewLine}{Exception}{NewLine}"; - //Log.Logger.Here().Debug("Begin Libation. Debug with line numbers"); - } + // .Here() captures debug info via System.Runtime.CompilerServices attributes. Warning: expensive + //var withLineNumbers_outputTemplate = "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}in method {MemberName} at {FilePath}:{LineNumber}{NewLine}{Exception}{NewLine}"; + //Log.Logger.Here().Debug("Begin Libation. Debug with line numbers"); + } private static void logStartupState(Configuration config) { @@ -315,7 +321,7 @@ namespace AppScaffolding Version = BuildVersion.ToString(), ReleaseIdentifier, OS, - Mode = mode, + Mode = mode, LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(), LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(), LogLevel_Information_Enabled = Log.Logger.IsInformationEnabled(), @@ -335,9 +341,22 @@ namespace AppScaffolding AudibleFileStorage.DecryptInProgressDirectory, DecryptInProgressFiles = FileManager.FileUtility.SaferEnumerateFiles(AudibleFileStorage.DecryptInProgressDirectory).Count(), }); - } + } - private static void wireUpSystemEvents(Configuration configuration) + private static void loadOSInterop(Configuration configuration) + { + InteropInstance = new OSInteropProxy(); + Serilog.Log.Logger.Information("InteropInstance:{@DebugInfo}", new + { + type = OSInteropProxy.InteropFunctionsType, + instance = InteropInstance.InteropFunctions + }); + + if (OSInteropProxy.InteropFunctionsType is null) + Serilog.Log.Logger.Warning("WARNING: OSInteropProxy.InteropFunctionsType is null"); + } + + private static void wireUpSystemEvents(Configuration configuration) { LibraryCommands.LibrarySizeChanged += (_, __) => SearchEngineCommands.FullReIndex(); LibraryCommands.BookUserDefinedItemCommitted += (_, books) => SearchEngineCommands.UpdateBooks(books); diff --git a/Source/AppScaffolding/OSInterop/IInteropFunctions.cs b/Source/AppScaffolding/OSInterop/IInteropFunctions.cs new file mode 100644 index 00000000..80c6c41f --- /dev/null +++ b/Source/AppScaffolding/OSInterop/IInteropFunctions.cs @@ -0,0 +1,15 @@ +using System; + +namespace AppScaffolding.OSInterop +{ + public interface IInteropFunctions + { + // examples until the real interface is filled out + /* + public string TransformInit1(); + public int TransformInit2(); + public void CopyTextToClipboard(string text); + public void ShowForm(); + */ + } +} diff --git a/Source/AppScaffolding/OSInterop/NullInteropFunctions.cs b/Source/AppScaffolding/OSInterop/NullInteropFunctions.cs new file mode 100644 index 00000000..7b81b015 --- /dev/null +++ b/Source/AppScaffolding/OSInterop/NullInteropFunctions.cs @@ -0,0 +1,16 @@ +using System; + +namespace AppScaffolding.OSInterop +{ + internal class NullInteropFunctions : IInteropFunctions + { + public NullInteropFunctions() { } + public NullInteropFunctions(params object[] values) { } + + // examples until the real interface is filled out + public string TransformInit1() => throw new PlatformNotSupportedException(); + public int TransformInit2() => throw new PlatformNotSupportedException(); + public void CopyTextToClipboard(string text) => throw new PlatformNotSupportedException(); + public void ShowForm() => throw new PlatformNotSupportedException(); + } +} diff --git a/Source/AppScaffolding/OSInterop/OSConfigBase.cs b/Source/AppScaffolding/OSInterop/OSConfigBase.cs new file mode 100644 index 00000000..812d854a --- /dev/null +++ b/Source/AppScaffolding/OSInterop/OSConfigBase.cs @@ -0,0 +1,29 @@ +using System; + +namespace AppScaffolding.OSInterop +{ + public abstract class OSConfigBase + { + public abstract Type InteropFunctionsType { get; } + public virtual Type[] ReferencedTypes { get; } = new Type[0]; + + public void Run() + { + //Each of these types belongs to a different windows-only assembly that's needed by + //the WinInterop methods. By referencing these types in main we force the runtime to + //load their assemblies before execution reaches inside main. This allows the calling + //process to find these assemblies in its module list. + _ = ReferencedTypes; + _ = InteropFunctionsType; + + //Wait for the calling process to be ready to read the WriteLine() + Console.ReadLine(); + + // Signal the calling process that execution has reached inside main, and that all referenced assemblies have been loaded. + Console.WriteLine(); + + // Wait for the calling process to finish reading the process module list, then exit. + Console.ReadLine(); + } + } +} diff --git a/Source/AppScaffolding/OSInterop/OSInteropProxy.cs b/Source/AppScaffolding/OSInterop/OSInteropProxy.cs new file mode 100644 index 00000000..02a6bdc2 --- /dev/null +++ b/Source/AppScaffolding/OSInterop/OSInteropProxy.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using Dinah.Core; + +namespace AppScaffolding.OSInterop +{ + public class OSInteropProxy : IInteropFunctions + { + public static bool IsWindows { get; } = OperatingSystem.IsWindows(); + public static bool IsLinux { get; } = OperatingSystem.IsLinux(); + public static bool IsMacOs { get; } = OperatingSystem.IsMacOS(); + + public static Func MatchesOS { get; } + = IsWindows ? a => Path.GetFileName(a).StartsWithInsensitive("win") + : IsLinux ? a => Path.GetFileName(a).StartsWithInsensitive("linux") + : IsMacOs ? a => Path.GetFileName(a).StartsWithInsensitive("mac") || a.StartsWithInsensitive("osx") + : _ => false; + + public IInteropFunctions InteropFunctions { get; } = new NullInteropFunctions(); + + #region Singleton Stuff + + private const string CONFIG_APP_ENDING = "ConfigApp.exe"; + private static List ModuleList { get; } = new(); + public static Type InteropFunctionsType { get; } + static OSInteropProxy() + { + // searches file names for potential matches; doesn't run anything + var configApp = getOSConfigApp(); + + // nothing to load + if (configApp is null) + return; + + // runs the exe and gets the exe's loaded modules + ModuleList = LoadModuleList(Path.GetFileNameWithoutExtension(configApp)) + .OrderBy(x => x.ModuleName) + .ToList(); + + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + + var configAppAssembly = Assembly.LoadFrom(Path.ChangeExtension(configApp, "dll")); + var type = typeof(IInteropFunctions); + InteropFunctionsType = configAppAssembly + .GetTypes() + .FirstOrDefault(t => type.IsAssignableFrom(t)); + } + private static string getOSConfigApp() + { + var here = Path.GetDirectoryName(Environment.ProcessPath); + + // find '*ConfigApp.exe' files + var exes = + Directory.EnumerateFiles(here, $"*{CONFIG_APP_ENDING}", SearchOption.TopDirectoryOnly) + // sanity check. shouldn't ever be true + .Except(new[] { Environment.ProcessPath }) + .Where(exe => + // has a corresponding dll + File.Exists(Path.ChangeExtension(exe, "dll")) + && MatchesOS(exe) + ) + .ToList(); + var exeName = exes.FirstOrDefault(); + return exeName; + } + + private static List LoadModuleList(string exeName) + { + var proc = new Process + { + StartInfo = new() + { + FileName = exeName, + RedirectStandardInput = true, + RedirectStandardOutput = true, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = false + } + }; + + var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); + + proc.OutputDataReceived += (_, _) => waitHandle.Set(); + proc.Start(); + proc.BeginOutputReadLine(); + + //Let the win process know we're ready to receive its standard output + proc.StandardInput.WriteLine(); + + if (!waitHandle.WaitOne(2000)) + throw new Exception("Failed to start program"); + + //The win process has finished loading and is now waiting inside Main(). + //Copy it process module list. + var modules = proc.Modules.Cast().ToList(); + + //Let the win process know we're done reading its module list + proc.StandardInput.WriteLine(); + + return modules; + } + + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + // e.g. "System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" + var asmName = args.Name.Split(',')[0]; + + // `First` instead of `FirstOrDefault`. If it's not present we're going to fail anyway. May as well be here + var module = ModuleList.First(m => m.ModuleName.StartsWith(asmName)); + + return Assembly.LoadFrom(module.FileName); + } + + #endregion + + public OSInteropProxy() : this(new object[0]) { } + + //// example of the pattern which could be useful later + //public OSInteropProxy(string str, int i) : this(new object[] { str, i }) { } + + private OSInteropProxy(params object[] values) + { + if (InteropFunctionsType is null) + return; + + InteropFunctions = + values is null || values.Length == 0 + ? Activator.CreateInstance(InteropFunctionsType) as IInteropFunctions + : Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions; + } + + // Interface Members + /* + // examples until the real interface is filled out + public void CopyTextToClipboard(string text) => InteropFunctions.CopyTextToClipboard(text); + public void ShowForm() => InteropFunctions.ShowForm(); + public string TransformInit1() => InteropFunctions.TransformInit1(); + public int TransformInit2() => InteropFunctions.TransformInit2(); + */ + } +} diff --git a/Source/Libation.sln b/Source/Libation.sln index 50ce9990..08bf7cea 100644 --- a/Source/Libation.sln +++ b/Source/Libation.sln @@ -7,8 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solutio ProjectSection(SolutionItems) = preProject REFERENCE.txt = REFERENCE.txt _ARCHITECTURE NOTES.txt = _ARCHITECTURE NOTES.txt - _DB_NOTES.txt = _DB_NOTES.txt _AvaloniaUI Primer.txt = _AvaloniaUI Primer.txt + _DB_NOTES.txt = _DB_NOTES.txt __README - COLLABORATORS.txt = __README - COLLABORATORS.txt EndProjectSection EndProject @@ -72,6 +72,27 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HangoverAvalonia", "Hangove EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HangoverBase", "HangoverBase\HangoverBase.csproj", "{5C7005BA-7D83-4E99-8073-D970943A7D61}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Demos", "_Demos", "{185AC9FF-381E-4AA1-B649-9771F4917214}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LoadByOS", "LoadByOS", "{59DF46F3-ECD0-43CA-AD12-3FEE8FCF9E4F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CrossPlatformClientExe", "_Demos\LoadByOS\CrossPlatformClientExe\CrossPlatformClientExe.csproj", "{CC275937-DFE4-4383-B1BF-1D5D42B70C98}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxConfigApp", "_Demos\LoadByOS\LinuxConfigApp\LinuxConfigApp.csproj", "{47325742-5B38-48E7-95FB-CD94E6E07332}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsConfigApp", "_Demos\LoadByOS\WindowsConfigApp\WindowsConfigApp.csproj", "{0520760A-9CFB-48A8-BCE4-6E951AFD6BE9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LoadByOS", "LoadByOS", "{9B906374-1142-4D69-86FF-B384806CA5FE}" + ProjectSection(SolutionItems) = preProject + LoadByOS\README.txt = LoadByOS\README.txt + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxConfigApp", "LoadByOS\LinuxConfigApp\LinuxConfigApp.csproj", "{357DF797-4EC2-4DBD-A4BD-D045277F2666}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MacOSConfigApp", "LoadByOS\MacOSConfigApp\MacOSConfigApp.csproj", "{ECED4E13-B676-4277-8A8F-C8B2507B7D8C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsConfigApp", "LoadByOS\WindowsConfigApp\WindowsConfigApp.csproj", "{5F65A509-26E3-4B02-B403-EEB6F0EF391F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -162,6 +183,30 @@ Global {5C7005BA-7D83-4E99-8073-D970943A7D61}.Debug|Any CPU.Build.0 = Debug|Any CPU {5C7005BA-7D83-4E99-8073-D970943A7D61}.Release|Any CPU.ActiveCfg = Release|Any CPU {5C7005BA-7D83-4E99-8073-D970943A7D61}.Release|Any CPU.Build.0 = Release|Any CPU + {CC275937-DFE4-4383-B1BF-1D5D42B70C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC275937-DFE4-4383-B1BF-1D5D42B70C98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC275937-DFE4-4383-B1BF-1D5D42B70C98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC275937-DFE4-4383-B1BF-1D5D42B70C98}.Release|Any CPU.Build.0 = Release|Any CPU + {47325742-5B38-48E7-95FB-CD94E6E07332}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47325742-5B38-48E7-95FB-CD94E6E07332}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47325742-5B38-48E7-95FB-CD94E6E07332}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47325742-5B38-48E7-95FB-CD94E6E07332}.Release|Any CPU.Build.0 = Release|Any CPU + {0520760A-9CFB-48A8-BCE4-6E951AFD6BE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0520760A-9CFB-48A8-BCE4-6E951AFD6BE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0520760A-9CFB-48A8-BCE4-6E951AFD6BE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0520760A-9CFB-48A8-BCE4-6E951AFD6BE9}.Release|Any CPU.Build.0 = Release|Any CPU + {357DF797-4EC2-4DBD-A4BD-D045277F2666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {357DF797-4EC2-4DBD-A4BD-D045277F2666}.Debug|Any CPU.Build.0 = Debug|Any CPU + {357DF797-4EC2-4DBD-A4BD-D045277F2666}.Release|Any CPU.ActiveCfg = Release|Any CPU + {357DF797-4EC2-4DBD-A4BD-D045277F2666}.Release|Any CPU.Build.0 = Release|Any CPU + {ECED4E13-B676-4277-8A8F-C8B2507B7D8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECED4E13-B676-4277-8A8F-C8B2507B7D8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECED4E13-B676-4277-8A8F-C8B2507B7D8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECED4E13-B676-4277-8A8F-C8B2507B7D8C}.Release|Any CPU.Build.0 = Release|Any CPU + {5F65A509-26E3-4B02-B403-EEB6F0EF391F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F65A509-26E3-4B02-B403-EEB6F0EF391F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F65A509-26E3-4B02-B403-EEB6F0EF391F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F65A509-26E3-4B02-B403-EEB6F0EF391F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -188,6 +233,14 @@ Global {F612D06F-3134-4B9B-95CD-EB3FC798AE60} = {8679CAC8-9164-4007-BDD2-F004810EDA14} {8A7B01D3-9830-44FD-91A1-D8D010996BEB} = {8679CAC8-9164-4007-BDD2-F004810EDA14} {5C7005BA-7D83-4E99-8073-D970943A7D61} = {8679CAC8-9164-4007-BDD2-F004810EDA14} + {59DF46F3-ECD0-43CA-AD12-3FEE8FCF9E4F} = {185AC9FF-381E-4AA1-B649-9771F4917214} + {CC275937-DFE4-4383-B1BF-1D5D42B70C98} = {59DF46F3-ECD0-43CA-AD12-3FEE8FCF9E4F} + {47325742-5B38-48E7-95FB-CD94E6E07332} = {59DF46F3-ECD0-43CA-AD12-3FEE8FCF9E4F} + {0520760A-9CFB-48A8-BCE4-6E951AFD6BE9} = {59DF46F3-ECD0-43CA-AD12-3FEE8FCF9E4F} + {9B906374-1142-4D69-86FF-B384806CA5FE} = {8679CAC8-9164-4007-BDD2-F004810EDA14} + {357DF797-4EC2-4DBD-A4BD-D045277F2666} = {9B906374-1142-4D69-86FF-B384806CA5FE} + {ECED4E13-B676-4277-8A8F-C8B2507B7D8C} = {9B906374-1142-4D69-86FF-B384806CA5FE} + {5F65A509-26E3-4B02-B403-EEB6F0EF391F} = {9B906374-1142-4D69-86FF-B384806CA5FE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9} diff --git a/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj b/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj new file mode 100644 index 00000000..8735328c --- /dev/null +++ b/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj @@ -0,0 +1,25 @@ + + + + Exe + net6.0 + enable + false + false + + + + ..\..\bin\Avalonia\Debug + embedded + + + + ..\..\bin\Avalonia\Release + embedded + + + + + + + \ No newline at end of file diff --git a/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs b/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs new file mode 100644 index 00000000..74533de5 --- /dev/null +++ b/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs @@ -0,0 +1,28 @@ +using AppScaffolding.OSInterop; + +namespace LinuxConfigApp +{ + internal class LinuxInterop : IInteropFunctions + { + public LinuxInterop() { } + public LinuxInterop(params object[] values) { } + + + // examples until the real interface is filled out + private string InitValue1 { get; } + private int InitValue2 { get; } + + public LinuxInterop(string initValue1, int initValue2) + { + InitValue1 = initValue1; + InitValue2 = initValue2; + } + + public string TransformInit1() => InitValue1.ToLower(); + + public int TransformInit2() => InitValue2 + InitValue2; + + public void CopyTextToClipboard(string text) => throw new PlatformNotSupportedException(); + public void ShowForm() => throw new PlatformNotSupportedException(); + } +} diff --git a/Source/LoadByOS/LinuxConfigApp/Program.cs b/Source/LoadByOS/LinuxConfigApp/Program.cs new file mode 100644 index 00000000..be568de8 --- /dev/null +++ b/Source/LoadByOS/LinuxConfigApp/Program.cs @@ -0,0 +1,11 @@ +using AppScaffolding.OSInterop; + +namespace LinuxConfigApp +{ + class Program : OSConfigBase + { + public override Type InteropFunctionsType => typeof(LinuxInterop); + + static void Main() => new Program().Run(); + } +} diff --git a/Source/LoadByOS/MacOSConfigApp/MacOSConfigApp.csproj b/Source/LoadByOS/MacOSConfigApp/MacOSConfigApp.csproj new file mode 100644 index 00000000..8735328c --- /dev/null +++ b/Source/LoadByOS/MacOSConfigApp/MacOSConfigApp.csproj @@ -0,0 +1,25 @@ + + + + Exe + net6.0 + enable + false + false + + + + ..\..\bin\Avalonia\Debug + embedded + + + + ..\..\bin\Avalonia\Release + embedded + + + + + + + \ No newline at end of file diff --git a/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs b/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs new file mode 100644 index 00000000..6e92064d --- /dev/null +++ b/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs @@ -0,0 +1,28 @@ +using AppScaffolding.OSInterop; + +namespace MacOSConfigApp +{ + internal class MacOSInterop : IInteropFunctions + { + public MacOSInterop() { } + public MacOSInterop(params object[] values) { } + + + // examples until the real interface is filled out + private string InitValue1 { get; } + private int InitValue2 { get; } + + public MacOSInterop(string initValue1, int initValue2) + { + InitValue1 = initValue1; + InitValue2 = initValue2; + } + + public string TransformInit1() => InitValue1.ToLower(); + + public int TransformInit2() => InitValue2 + InitValue2; + + public void CopyTextToClipboard(string text) => throw new PlatformNotSupportedException(); + public void ShowForm() => throw new PlatformNotSupportedException(); + } +} diff --git a/Source/LoadByOS/MacOSConfigApp/Program.cs b/Source/LoadByOS/MacOSConfigApp/Program.cs new file mode 100644 index 00000000..84aae9dd --- /dev/null +++ b/Source/LoadByOS/MacOSConfigApp/Program.cs @@ -0,0 +1,11 @@ +using AppScaffolding.OSInterop; + +namespace MacOSConfigApp +{ + class Program : OSConfigBase + { + public override Type InteropFunctionsType => typeof(MacOSInterop); + + static void Main() => new Program().Run(); + } +} diff --git a/Source/LoadByOS/README.txt b/Source/LoadByOS/README.txt new file mode 100644 index 00000000..9dc6c2a3 --- /dev/null +++ b/Source/LoadByOS/README.txt @@ -0,0 +1 @@ +Streamlined example is in \Source\_Demos\LoadByOS \ No newline at end of file diff --git a/Source/LoadByOS/WindowsConfigApp/Form1.Designer.cs b/Source/LoadByOS/WindowsConfigApp/Form1.Designer.cs new file mode 100644 index 00000000..30118c1b --- /dev/null +++ b/Source/LoadByOS/WindowsConfigApp/Form1.Designer.cs @@ -0,0 +1,39 @@ +namespace WindowsConfigApp +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form1"; + } + + #endregion + } +} \ No newline at end of file diff --git a/Source/LoadByOS/WindowsConfigApp/Form1.cs b/Source/LoadByOS/WindowsConfigApp/Form1.cs new file mode 100644 index 00000000..d3265e4e --- /dev/null +++ b/Source/LoadByOS/WindowsConfigApp/Form1.cs @@ -0,0 +1,10 @@ +namespace WindowsConfigApp +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Source/LoadByOS/WindowsConfigApp/Program.cs b/Source/LoadByOS/WindowsConfigApp/Program.cs new file mode 100644 index 00000000..2d793c2c --- /dev/null +++ b/Source/LoadByOS/WindowsConfigApp/Program.cs @@ -0,0 +1,18 @@ +using AppScaffolding.OSInterop; + +namespace WindowsConfigApp +{ + class Program : OSConfigBase + { + public override Type InteropFunctionsType => typeof(WinInterop); + public override Type[] ReferencedTypes => new Type[] + { + typeof(Form1), + typeof(Bitmap), + typeof(Accessibility.IAccIdentity), + typeof(Microsoft.Win32.SystemEvents) + }; + + static void Main() => new Program().Run(); + } +} \ No newline at end of file diff --git a/Source/LoadByOS/WindowsConfigApp/WinInterop.cs b/Source/LoadByOS/WindowsConfigApp/WinInterop.cs new file mode 100644 index 00000000..4c76aac4 --- /dev/null +++ b/Source/LoadByOS/WindowsConfigApp/WinInterop.cs @@ -0,0 +1,33 @@ +using AppScaffolding.OSInterop; + +namespace WindowsConfigApp +{ + internal class WinInterop : IInteropFunctions + { + public WinInterop() { } + public WinInterop(params object[] values) { } + + + // examples until the real interface is filled out + private string InitValue1 { get; } + private int InitValue2 { get; } + + public WinInterop(string initValue1, int initValue2) + { + InitValue1 = initValue1; + InitValue2 = initValue2; + } + + public void CopyTextToClipboard(string text) => Clipboard.SetDataObject(text, true, 5, 150); + + public void ShowForm() + { + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + } + + public string TransformInit1() => InitValue1.ToUpper(); + + public int TransformInit2() => InitValue2 * InitValue2; + } +} diff --git a/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj b/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj new file mode 100644 index 00000000..29c5a9a4 --- /dev/null +++ b/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj @@ -0,0 +1,26 @@ + + + + WinExe + net6.0-windows + true + enable + false + false + + + + ..\..\bin\Debug + embedded + + + + ..\..\bin\Release + embedded + + + + + + + \ No newline at end of file diff --git a/Source/_Demos/LoadByOS/CrossPlatformClientExe/CrossPlatformClientExe.csproj b/Source/_Demos/LoadByOS/CrossPlatformClientExe/CrossPlatformClientExe.csproj new file mode 100644 index 00000000..8155d0de --- /dev/null +++ b/Source/_Demos/LoadByOS/CrossPlatformClientExe/CrossPlatformClientExe.csproj @@ -0,0 +1,23 @@ + + + + Exe + net6.0 + false + false + + + ..\bin\Debug + embedded + + + + ..\bin\Release + embedded + + + + + + + diff --git a/Source/_Demos/LoadByOS/CrossPlatformClientExe/IInteropFunctions.cs b/Source/_Demos/LoadByOS/CrossPlatformClientExe/IInteropFunctions.cs new file mode 100644 index 00000000..548deffc --- /dev/null +++ b/Source/_Demos/LoadByOS/CrossPlatformClientExe/IInteropFunctions.cs @@ -0,0 +1,12 @@ +using System; + +namespace CrossPlatformClientExe +{ + public interface IInteropFunctions + { + public string TransformInit1(); + public int TransformInit2(); + public void CopyTextToClipboard(string text); + public void ShowForm(); + } +} diff --git a/Source/_Demos/LoadByOS/CrossPlatformClientExe/NullInteropFunctions.cs b/Source/_Demos/LoadByOS/CrossPlatformClientExe/NullInteropFunctions.cs new file mode 100644 index 00000000..1adb86e7 --- /dev/null +++ b/Source/_Demos/LoadByOS/CrossPlatformClientExe/NullInteropFunctions.cs @@ -0,0 +1,14 @@ +using System; + +namespace CrossPlatformClientExe +{ + internal class NullInteropFunctions : IInteropFunctions + { + public NullInteropFunctions(params object[] values) { } + + public string TransformInit1() => throw new PlatformNotSupportedException(); + public int TransformInit2() => throw new PlatformNotSupportedException(); + public void CopyTextToClipboard(string text) => throw new PlatformNotSupportedException(); + public void ShowForm() => throw new PlatformNotSupportedException(); + } +} diff --git a/Source/_Demos/LoadByOS/CrossPlatformClientExe/OSConfigBase.cs b/Source/_Demos/LoadByOS/CrossPlatformClientExe/OSConfigBase.cs new file mode 100644 index 00000000..c1b1415d --- /dev/null +++ b/Source/_Demos/LoadByOS/CrossPlatformClientExe/OSConfigBase.cs @@ -0,0 +1,29 @@ +using System; + +namespace CrossPlatformClientExe +{ + public abstract class OSConfigBase + { + public abstract Type InteropFunctionsType { get; } + public virtual Type[] ReferencedTypes { get; } = new Type[0]; + + public void Run() + { + //Each of these types belongs to a different windows-only assembly that's needed by + //the WinInterop methods. By referencing these types in main we force the runtime to + //load their assemblies before execution reaches inside main. This allows the calling + //process to find these assemblies in its module list. + _ = ReferencedTypes; + _ = InteropFunctionsType; + + //Wait for the calling process to be ready to read the WriteLine() + Console.ReadLine(); + + // Signal the calling process that execution has reached inside main, and that all referenced assemblies have been loaded. + Console.WriteLine(); + + // Wait for the calling process to finish reading the process module list, then exit. + Console.ReadLine(); + } + } +} diff --git a/Source/_Demos/LoadByOS/CrossPlatformClientExe/OSInteropProxy.cs b/Source/_Demos/LoadByOS/CrossPlatformClientExe/OSInteropProxy.cs new file mode 100644 index 00000000..9dbaf318 --- /dev/null +++ b/Source/_Demos/LoadByOS/CrossPlatformClientExe/OSInteropProxy.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using Dinah.Core; + +namespace CrossPlatformClientExe +{ + public class OSInteropProxy : IInteropFunctions + { + public static bool IsWindows { get; } = OperatingSystem.IsWindows(); + public static bool IsLinux { get; } = OperatingSystem.IsLinux(); + public static bool IsMacOs { get; } = OperatingSystem.IsMacOS(); + + public static Func MatchesOS { get; } + = IsWindows ? a => Path.GetFileName(a).StartsWithInsensitive("win") + : IsLinux ? a => Path.GetFileName(a).StartsWithInsensitive("linux") + : IsMacOs ? a => Path.GetFileName(a).StartsWithInsensitive("mac") || a.StartsWithInsensitive("osx") + : _ => false; + + private IInteropFunctions InteropFunctions { get; } = new NullInteropFunctions(); + + #region Singleton Stuff + + private const string CONFIG_APP_ENDING = "ConfigApp.exe"; + private static List ModuleList { get; } = new(); + private static Type InteropFunctionsType { get; } + static OSInteropProxy() + { + // searches file names for potential matches; doesn't run anything + var configApp = getOSConfigApp(); + + // nothing to load + if (configApp is null) + return; + + // runs the exe and gets the exe's loaded modules + ModuleList = LoadModuleList(Path.GetFileNameWithoutExtension(configApp)) + .OrderBy(x => x.ModuleName) + .ToList(); + + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + + var configAppAssembly = Assembly.LoadFrom(Path.ChangeExtension(configApp, "dll")); + var type = typeof(IInteropFunctions); + InteropFunctionsType = configAppAssembly + .GetTypes() + .FirstOrDefault(t => type.IsAssignableFrom(t)); + } + private static string getOSConfigApp() + { + var here = Path.GetDirectoryName(Environment.ProcessPath); + + // find '*ConfigApp.exe' files + var exes = + Directory.EnumerateFiles(here, $"*{CONFIG_APP_ENDING}", SearchOption.TopDirectoryOnly) + // sanity check. shouldn't ever be true + .Except(new[] { Environment.ProcessPath }) + .Where(exe => + // has a corresponding dll + File.Exists(Path.ChangeExtension(exe, "dll")) + && MatchesOS(exe) + ) + .ToList(); + var exeName = exes.FirstOrDefault(); + return exeName; + } + + private static List LoadModuleList(string exeName) + { + var proc = new Process + { + StartInfo = new() + { + FileName = exeName, + RedirectStandardInput = true, + RedirectStandardOutput = true, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = false + } + }; + + var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); + + proc.OutputDataReceived += (_, _) => waitHandle.Set(); + proc.Start(); + proc.BeginOutputReadLine(); + + //Let the win process know we're ready to receive its standard output + proc.StandardInput.WriteLine(); + + if (!waitHandle.WaitOne(2000)) + throw new Exception("Failed to start program"); + + //The win process has finished loading and is now waiting inside Main(). + //Copy it process module list. + var modules = proc.Modules.Cast().ToList(); + + //Let the win process know we're done reading its module list + proc.StandardInput.WriteLine(); + + return modules; + } + + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + // e.g. "System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" + var asmName = args.Name.Split(',')[0]; + + // `First` instead of `FirstOrDefault`. If it's not present we're going to fail anyway. May as well be here + var module = ModuleList.First(m => m.ModuleName.StartsWith(asmName)); + + return Assembly.LoadFrom(module.FileName); + } + + #endregion + + public OSInteropProxy(string str, int i) : this(new object[] { str, i }) { } + private OSInteropProxy(params object[] values) + { + InteropFunctions = + values is null || values.Length == 0 + ? Activator.CreateInstance(InteropFunctionsType) as IInteropFunctions + : Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions; + } + + #region Interface Members + public void CopyTextToClipboard(string text) => InteropFunctions.CopyTextToClipboard(text); + public void ShowForm() => InteropFunctions.ShowForm(); + public string TransformInit1() => InteropFunctions.TransformInit1(); + public int TransformInit2() => InteropFunctions.TransformInit2(); + #endregion + } +} diff --git a/Source/_Demos/LoadByOS/CrossPlatformClientExe/Program.cs b/Source/_Demos/LoadByOS/CrossPlatformClientExe/Program.cs new file mode 100644 index 00000000..0221a12c --- /dev/null +++ b/Source/_Demos/LoadByOS/CrossPlatformClientExe/Program.cs @@ -0,0 +1,19 @@ +using System; + +namespace CrossPlatformClientExe +{ + class Program + { + [STAThread] + public static void Main() + { + var interopInstance = new OSInteropProxy("this IS SOME text", 42); + + Console.WriteLine("X-Formed Value 1: {0}", interopInstance.TransformInit1()); + Console.WriteLine("X-Formed Value 2: {0}", interopInstance.TransformInit2()); + + interopInstance.ShowForm(); + interopInstance.CopyTextToClipboard("This is copied text!"); + } + } +} \ No newline at end of file diff --git a/Source/_Demos/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj b/Source/_Demos/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj new file mode 100644 index 00000000..1c150006 --- /dev/null +++ b/Source/_Demos/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj @@ -0,0 +1,25 @@ + + + + Exe + net6.0 + enable + false + false + + + + ..\bin\Debug + embedded + + + + ..\bin\Release + embedded + + + + + + + \ No newline at end of file diff --git a/Source/_Demos/LoadByOS/LinuxConfigApp/LinuxInterop.cs b/Source/_Demos/LoadByOS/LinuxConfigApp/LinuxInterop.cs new file mode 100644 index 00000000..efcad11a --- /dev/null +++ b/Source/_Demos/LoadByOS/LinuxConfigApp/LinuxInterop.cs @@ -0,0 +1,23 @@ +using CrossPlatformClientExe; + +namespace LinuxConfigApp +{ + internal class LinuxInterop : IInteropFunctions + { + private string InitValue1 { get; } + private int InitValue2 { get; } + + public LinuxInterop(string initValue1, int initValue2) + { + InitValue1 = initValue1; + InitValue2 = initValue2; + } + + public string TransformInit1() => InitValue1.ToLower(); + + public int TransformInit2() => InitValue2 + InitValue2; + + public void CopyTextToClipboard(string text) => throw new PlatformNotSupportedException(); + public void ShowForm() => throw new PlatformNotSupportedException(); + } +} diff --git a/Source/_Demos/LoadByOS/LinuxConfigApp/Program.cs b/Source/_Demos/LoadByOS/LinuxConfigApp/Program.cs new file mode 100644 index 00000000..10dbdd5a --- /dev/null +++ b/Source/_Demos/LoadByOS/LinuxConfigApp/Program.cs @@ -0,0 +1,11 @@ +using CrossPlatformClientExe; + +namespace LinuxConfigApp +{ + class Program : OSConfigBase + { + public override Type InteropFunctionsType => typeof(LinuxInterop); + + static void Main() => new Program().Run(); + } +} diff --git a/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.Designer.cs b/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.Designer.cs new file mode 100644 index 00000000..65f198eb --- /dev/null +++ b/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.Designer.cs @@ -0,0 +1,39 @@ +namespace WindowsConfigApp +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form1"; + } + + #endregion + } +} \ No newline at end of file diff --git a/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.cs b/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.cs new file mode 100644 index 00000000..2639908c --- /dev/null +++ b/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.cs @@ -0,0 +1,10 @@ +namespace WindowsConfigApp +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.resx b/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/Source/_Demos/LoadByOS/WindowsConfigApp/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Source/_Demos/LoadByOS/WindowsConfigApp/Program.cs b/Source/_Demos/LoadByOS/WindowsConfigApp/Program.cs new file mode 100644 index 00000000..26e43e55 --- /dev/null +++ b/Source/_Demos/LoadByOS/WindowsConfigApp/Program.cs @@ -0,0 +1,18 @@ +using CrossPlatformClientExe; + +namespace WindowsConfigApp +{ + class Program : OSConfigBase + { + public override Type InteropFunctionsType => typeof(WinInterop); + public override Type[] ReferencedTypes => new Type[] + { + typeof(Form1), + typeof(Bitmap), + typeof(Accessibility.IAccIdentity), + typeof(Microsoft.Win32.SystemEvents) + }; + + static void Main() => new Program().Run(); + } +} \ No newline at end of file diff --git a/Source/_Demos/LoadByOS/WindowsConfigApp/WinInterop.cs b/Source/_Demos/LoadByOS/WindowsConfigApp/WinInterop.cs new file mode 100644 index 00000000..f1838b8f --- /dev/null +++ b/Source/_Demos/LoadByOS/WindowsConfigApp/WinInterop.cs @@ -0,0 +1,29 @@ +using CrossPlatformClientExe; + +namespace WindowsConfigApp +{ + internal class WinInterop : IInteropFunctions + { + private string InitValue1 { get; } + private int InitValue2 { get; } + + public WinInterop() { } + public WinInterop(string initValue1, int initValue2) + { + InitValue1 = initValue1; + InitValue2 = initValue2; + } + + public void CopyTextToClipboard(string text) => Clipboard.SetDataObject(text, true, 5, 150); + + public void ShowForm() + { + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + } + + public string TransformInit1() => InitValue1.ToUpper(); + + public int TransformInit2() => InitValue2 * InitValue2; + } +} diff --git a/Source/_Demos/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj b/Source/_Demos/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj new file mode 100644 index 00000000..de6f17b7 --- /dev/null +++ b/Source/_Demos/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj @@ -0,0 +1,26 @@ + + + + WinExe + net6.0-windows + true + enable + false + false + + + + ..\bin\Debug + embedded + + + + ..\bin\Release + embedded + + + + + + + \ No newline at end of file