From cb356d9b6c9cdf20d7d401bfc29afb6d918fc983 Mon Sep 17 00:00:00 2001 From: Roman Vanicek Date: Mon, 21 Oct 2024 14:32:56 +0200 Subject: [PATCH] Command-line interface --- .drone.yml | 4 + DllExports.Cli/DllExports.Cli.csproj | 59 ++++++++ DllExports.Cli/Program.cs | 209 +++++++++++++++++++++++++++ DllExports.sln | 6 + DllExports/Exporter.cs | 1 + 5 files changed, 279 insertions(+) create mode 100644 DllExports.Cli/DllExports.Cli.csproj create mode 100644 DllExports.Cli/Program.cs diff --git a/.drone.yml b/.drone.yml index 5a7a3e0..a96b5a4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,6 +26,9 @@ steps: # Re-pack - rm DllExports.MSBuild/bin/Release/IvDllExports.0.1.1.nupkg - (cd DllExports.MSBuild/bin/Release/nupkg; zip -r ../IvDllExports.0.1.1.nupkg ./) + # Cli + - dotnet build --configuration Release DllExports.Cli/DllExports.Cli.csproj + - dotnet pack --configuration Release DllExports.Cli/DllExports.Cli.csproj - name: release image: mcr.microsoft.com/dotnet/sdk:8.0 @@ -35,5 +38,6 @@ steps: nuget_uri: https://git.ivasoft.cz/api/packages/Ivasoft/nuget/index.json commands: - dotnet nuget push DllExports.MSBuild/bin/Release/IvDllExports.0.1.1.nupkg --api-key $$PLUGIN_NUGET_APIKEY --source $$PLUGIN_NUGET_URI --skip-duplicate + - dotnet nuget push DllExports.Cli/bin/Release/IvDllExports-Cli.0.1.1.nupkg --api-key $$PLUGIN_NUGET_APIKEY --source $$PLUGIN_NUGET_URI --skip-duplicate when: event: tag \ No newline at end of file diff --git a/DllExports.Cli/DllExports.Cli.csproj b/DllExports.Cli/DllExports.Cli.csproj new file mode 100644 index 0000000..212eeb2 --- /dev/null +++ b/DllExports.Cli/DllExports.Cli.csproj @@ -0,0 +1,59 @@ + + + + + + net472;net5.0 + true + + IvDllExports-Cli + IvDllExports-Cli + lordmilko + Unmanaged Exports for legacy/SDK style projects + false + (c) 2023 lordmilko. All rights reserved. + + DllExports provides unmanaged exports for both legacy and SDK style projects. + +Unlike other libraries that rely on a wacky series of external dependencies, DllExports has everything it needs to do its job built in. + +DllExports is entirely driven by its MSBuild task, and provides a number of knobs you can adjust to customize the resulting assemblies, including converting AnyCPU assemblies into both x86 and x64 outputs. + +In order to be able to debug your exports in Visual Studio you must be targeting .NET Framework and be using Visual Studio 2019 or newer. .NET Standard exports work but you can't debug them. .NET Core applications can't truly have unmanaged exports as you can't use mscoree to load their runtime. Consider using a library such as DNNE for proper .NET Core support. + EXE + Major + + + + + + + + + + + + + + + + + + + + + + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage + + + + + <_ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" /> + + + + + + + + \ No newline at end of file diff --git a/DllExports.Cli/Program.cs b/DllExports.Cli/Program.cs new file mode 100644 index 0000000..026d913 --- /dev/null +++ b/DllExports.Cli/Program.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +using DllExports; +using DllExports.MSBuild; + +// Based on GenerateDllExports.cs + +class A +{ + private static ExportOptions options = new ExportOptions(); + + enum MessageImportance { + High, + Normal + } + static class Log { + internal static void LogMessage(MessageImportance level, string msg) { + LogMessage(msg); + } + internal static void LogMessage(string msg) { + Console.Out.WriteLine(msg); + } + internal static void LogError(string msg) { + Console.Error.WriteLine(msg); + } + internal static void LogErrorFromException(Exception ex) { + LogError(ex.GetType().Name + ": " + ex.Message); + } + } + + public static int Main(string[] args) + { + options.Enabled = true; + List architectures = new List(); + + for (int i = 0; i < args.Length; i++) + { + string arg = args[i]; + if (i + 1 < args.Length && (arg.StartsWith("-") || arg.StartsWith("/"))) + { + switch (arg.ToLowerInvariant().Substring(1)) + { + case "inputfile": + options.InputFile = args[++i]; + break; + case "outputfile": + options.OutputFile = args[++i]; + break; + case "architecture": + architectures.Add(args[++i]); + break; + case "architecturenameformat": + options.ArchitectureNameFormat = args[++i]; + break; + case "removeinputfile": + if (!bool.TryParse(args[++i], out bool a)) + { + Console.Error.WriteLine($"Expected boolean 'true' or 'false' for option 'RemoveInputFile'."); + return 3; + } + else + options.RemoveInputFile = a; + break; + + default: + Console.Error.WriteLine($"Unknown option '{arg}'."); + return 2; + } + } + else + { + Console.Error.WriteLine($"Unexpected command-line argument '{arg}'."); + return 1; + } + } + + options.Architectures = architectures.ToArray(); + + bool isOk = Execute(options); + return isOk ? 0 : 1; + } + + public static bool Enabled + { + get => options.Enabled; + set => options.Enabled = value; + } + + public static string InputFile + { + get => options.InputFile; + set => options.InputFile = value; + } + + public static string OutputFile + { + get => options.OutputFile; + set => options.OutputFile = value; + } + + public static string[] Architectures + { + get => options.Architectures; + set => options.Architectures = value; + } + + public static string ArchitectureNameFormat + { + get => options.ArchitectureNameFormat; + set => options.ArchitectureNameFormat = value; + } + + public static bool RemoveInputFile + { + get => options.RemoveInputFile; + set => options.RemoveInputFile = value; + } + + public static bool Execute(ExportOptions options) + { + if (!Enabled) + { + Log.LogMessage($"DllExports{nameof(Enabled)} was false. Nothing to do"); + return true; + } + + if (string.IsNullOrWhiteSpace(InputFile)) + { + Log.LogError($"DllExports{nameof(InputFile)} must be specified"); + return false; + } + + Log.LogMessage(MessageImportance.Normal, $"Processing file '{InputFile}'"); + + if (string.IsNullOrWhiteSpace(OutputFile)) + { + Log.LogError($"DllExports{nameof(OutputFile)} must be specified"); + return false; + } + + if (options.Architectures != null) + { + if (string.IsNullOrWhiteSpace(options.ArchitectureNameFormat)) + { + Log.LogError($"DllExports{nameof(ArchitectureNameFormat)} must be specified when DllExports{nameof(Architectures)} is specified"); + return false; + } + + var validArchs = new[] + { + "I386", + "AMD64" + }; + + foreach (var arch in options.Architectures) + { + if (!validArchs.Contains(arch, StringComparer.OrdinalIgnoreCase)) + { + Log.LogError($"Invalid architecture '{arch}' specified. Valid architectures: {string.Join(", ", validArchs)}"); + return false; + } + } + } + + var outputs = options.CalculateOutputFiles(); + + if (options.RemoveInputFile && outputs.Any(o => StringComparer.OrdinalIgnoreCase.Equals(o.Path, options.InputFile))) + { + Log.LogError($"Cannot set option DllExports{nameof(RemoveInputFile)} to true when inputs and outputs are the same file"); + return false; + } + + var taskRunner = new IsolatedTaskRunner(); + try + { + taskRunner.Execute(options); + } + catch (TargetInvocationException ex) + { + Log.LogErrorFromException(ex.InnerException); + return false; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex); + return false; + } + + if (options.RemoveInputFile) + File.Delete(options.InputFile); + + var directory = Path.GetDirectoryName(options.InputFile); + var dllExportsDll = Path.Combine(directory, "DllExports.dll"); + + if (File.Exists(dllExportsDll)) + File.Delete(dllExportsDll); + + Log.LogMessage(MessageImportance.High, string.Empty); + + foreach (var output in outputs) + Log.LogMessage(MessageImportance.High, $"DllExports ({output.Name}) -> {output.Path}"); + + return true; + } +} \ No newline at end of file diff --git a/DllExports.sln b/DllExports.sln index 5ae296f..25199f5 100644 --- a/DllExports.sln +++ b/DllExports.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DllExports", "DllExports\Dl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DllExports.MSBuild", "DllExports.MSBuild\DllExports.MSBuild.csproj", "{0F2800DF-65E6-47F8-949F-E401881D5970}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DllExports.Cli", "DllExports.Cli\DllExports.Cli.csproj", "{574001C6-8F9D-11EF-9F40-77C5A17AB591}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DllExports.Tests", "DllExports.Tests\DllExports.Tests.csproj", "{D8920F8A-90C4-4D1E-9750-8ED1D79086C9}" EndProject Global @@ -27,6 +29,10 @@ Global {D8920F8A-90C4-4D1E-9750-8ED1D79086C9}.Debug|Any CPU.Build.0 = Debug|Any CPU {D8920F8A-90C4-4D1E-9750-8ED1D79086C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8920F8A-90C4-4D1E-9750-8ED1D79086C9}.Release|Any CPU.Build.0 = Release|Any CPU + {574001C6-8F9D-11EF-9F40-77C5A17AB591}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {574001C6-8F9D-11EF-9F40-77C5A17AB591}.Debug|Any CPU.Build.0 = Debug|Any CPU + {574001C6-8F9D-11EF-9F40-77C5A17AB591}.Release|Any CPU.ActiveCfg = Release|Any CPU + {574001C6-8F9D-11EF-9F40-77C5A17AB591}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DllExports/Exporter.cs b/DllExports/Exporter.cs index 91a3270..e7a2e18 100644 --- a/DllExports/Exporter.cs +++ b/DllExports/Exporter.cs @@ -10,6 +10,7 @@ using dnlib.PE; using CallingConvention = System.Runtime.InteropServices.CallingConvention; [assembly: InternalsVisibleTo("DllExports.MSBuild")] +[assembly: InternalsVisibleTo("DllExports.Cli")] [assembly: InternalsVisibleTo("DllExports.Tests")] namespace DllExports