Skip to content

Commit

Permalink
feat: Support for regasm.exe functionalities (#313)
Browse files Browse the repository at this point in the history
* feat: Added support for regasm functionalities

---------

Co-authored-by: Mark Lechtermann <[email protected]>
  • Loading branch information
mavToday and marklechtermann authored Jan 3, 2025
1 parent 2b4369a commit becca63
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 68 deletions.
19 changes: 6 additions & 13 deletions src/dscom.client/AssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,20 @@ namespace dSPACE.Runtime.InteropServices;
/// </summary>
internal sealed class AssemblyResolver : AssemblyLoadContext, IDisposable
{
private readonly string[] _paths;

private bool _disposedValue;

internal AssemblyResolver(TypeLibConverterOptions options) : base("dscom", isCollectible: options.ShouldEmbed())
internal AssemblyResolver(string[] paths, bool isCollectible)
: base("dscom", isCollectible)
{
Options = options;
_paths = paths;
Resolving += Context_Resolving;
}

private Assembly? Context_Resolving(AssemblyLoadContext context, AssemblyName name)
{
var dir = Path.GetDirectoryName(Options.Assembly);

var asmPaths = Options.ASMPath;
if (Directory.Exists(dir))
{
asmPaths = asmPaths.Prepend(dir).ToArray();
}

foreach (var path in asmPaths)
foreach (var path in _paths)
{
var dllToLoad = Path.Combine(path, $"{name.Name}.dll");
if (File.Exists(dllToLoad))
Expand All @@ -63,8 +58,6 @@ public Assembly LoadAssembly(string path)
return LoadFromAssemblyPath(path);
}

public TypeLibConverterOptions Options { get; }

private void Dispose(bool disposing)
{
if (!_disposedValue)
Expand Down
169 changes: 135 additions & 34 deletions src/dscom.client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

Expand All @@ -27,48 +28,58 @@ public static class ConsoleApp
public static int Main(string[] args)
{
var tlbexportCommand = new Command("tlbexport", "Export the assembly to the specified type library") {
new Argument<string>("Assembly", "File name of assembly to parse"),
new Option<string>(new[] {"--out", "/out"}, description: "File name of type library to be produced"),
new Option<string[]>(new[] {"--tlbreference", "/tlbreference"}, description: "Type library used to resolve references", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] {"--tlbrefpath", "/tlbrefpath"}, description: "Path used to resolve referenced type libraries", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] {"--asmpath", "/asmpath"}, description: "Look for assembly references here", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<bool>(new[] {"--silent", "/silent"}, description: "Suppresses all output except for errors"),
new Option<string[]>(new[] {"--silence", "/silence"}, description: "Suppresses output for the given warning (Can not be used with /silent)", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<bool>(new[] {"--verbose", "/verbose"}, description: "Detailed log output"),
new Option<string[]>(new[] {"--names", "/names"}, description: "A file in which each line specifies the capitalization of a name in the type library.", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string>(new[] { "--overridename", "/overridename"}, description: "Overwrites the library name"),
new Option<Guid>(new[] {"--overridetlbid", "/overridetlbid"}, description: "Overwrites the library id"),
new Option<bool?>(new[] {"--createmissingdependenttlbs", "/createmissingdependenttlbs"}, description: "Generate missing type libraries for referenced assemblies. (default true)"),
new Option<string?>(new[] { "--embed", "/embed"}, () => TypeLibConverterOptions.NotSpecifiedViaCommandLineArgumentsDefault, description: "Embeds type library into the assembly. (default: false)") { Arity = ArgumentArity.ZeroOrOne },
new Option<ushort>(new[] {"--index", "/index"}, () => 1, description: "If the switch --embed is specified, the index indicates the resource ID to be used for the embedded type library. Must be a number between 1 and 65535. Ignored if --embed not present. (default 1)")
};
new Argument<string>("Assembly", "File name of assembly to parse"),
new Option<string>(new[] {"--out", "/out"}, description: "File name of type library to be produced"),
new Option<string[]>(new[] {"--tlbreference", "/tlbreference"}, description: "Type library used to resolve references", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] {"--tlbrefpath", "/tlbrefpath"}, description: "Path used to resolve referenced type libraries", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] {"--asmpath", "/asmpath"}, description: "Look for assembly references here", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<bool>(new[] {"--silent", "/silent"}, description: "Suppresses all output except for errors"),
new Option<string[]>(new[] {"--silence", "/silence"}, description: "Suppresses output for the given warning (Can not be used with /silent)", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<bool>(new[] {"--verbose", "/verbose"}, description: "Detailed log output"),
new Option<string[]>(new[] {"--names", "/names"}, description: "A file in which each line specifies the capitalization of a name in the type library.", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string>(new[] { "--overridename", "/overridename"}, description: "Overwrites the library name"),
new Option<Guid>(new[] {"--overridetlbid", "/overridetlbid"}, description: "Overwrites the library id"),
new Option<bool?>(new[] {"--createmissingdependenttlbs", "/createmissingdependenttlbs"}, description: "Generate missing type libraries for referenced assemblies. (default true)"),
new Option<string?>(new[] { "--embed", "/embed"}, () => TypeLibConverterOptions.NotSpecifiedViaCommandLineArgumentsDefault, description: "Embeds type library into the assembly. (default: false)") { Arity = ArgumentArity.ZeroOrOne },
new Option<ushort>(new[] {"--index", "/index"}, () => 1, description: "If the switch --embed is specified, the index indicates the resource ID to be used for the embedded type library. Must be a number between 1 and 65535. Ignored if --embed not present. (default 1)")
};

var tlbdumpCommand = new Command("tlbdump", "Dump a type library")
{
new Argument<string>("TypeLibrary", "File name of type library"),
new Option<string>(new[] {"--out", "/out"}, description: "File name of the output"),
new Option<string[]>(new[] {"--tlbreference", "/tlbreference"}, description: "Type library used to resolve references", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] {"--tlbrefpath", "/tlbrefpath"}, description: "Path used to resolve referenced type libraries", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] {"--filterregex", "/filterregex"}, description: "Regex to filter the output", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
};
{
new Argument<string>("TypeLibrary", "File name of type library"),
new Option<string>(new[] {"--out", "/out"}, description: "File name of the output"),
new Option<string[]>(new[] {"--tlbreference", "/tlbreference"}, description: "Type library used to resolve references", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] {"--tlbrefpath", "/tlbrefpath"}, description: "Path used to resolve referenced type libraries", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] {"--filterregex", "/filterregex"}, description: "Regex to filter the output", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
};

var tlbregisterCommand = new Command("tlbregister", "Register a type library")
{
new Argument<string>("TypeLibrary", "File name of type library"),
new Option<bool>(new[] {"--foruser", "/foruser"}, description: "Registered for use only by the calling user identity."),
};
{
new Argument<string>("TypeLibrary", "File name of type library"),
new Option<bool>(new[] {"--foruser", "/foruser"}, description: "Registered for use only by the calling user identity."),
};

var tlbunregisterCommand = new Command("tlbunregister", "Unregister a type library")
{
new Argument<string>("TypeLibrary", "File name of type library"),
new Option<bool>(new[] {"--foruser", "/foruser"}, description: "Registered for use only by the calling user identity."),
};
{
new Argument<string>("TypeLibrary", "File name of type library"),
new Option<bool>(new[] {"--foruser", "/foruser"}, description: "Registered for use only by the calling user identity."),
};

var tlbembedCommand = new Command("tlbembed", "Embeds a source type library into a target file")
{
new Argument<string>("SourceTypeLibrary","File name of type library"),
new Argument<string>("TargetAssembly", "File name of target assembly to receive the type library as a resource"),
new Option<ushort>(new[] {"--index", "/index"}, () => 1, description:"Index to use for resource ID for the type library. If omitted, defaults to 1. Must be a positive integer from 1 to 65535.")
};

var registerAssemblyCommand = new Command("regasm", "register assembly")
{
new Argument<string>("SourceTypeLibrary","File name of type library"),
new Argument<string>("TargetAssembly", "File name of target assembly to receive the type library as a resource"),
new Option<ushort>(new[] {"--index", "/index"}, () => 1, description:"Index to use for resource ID for the type library. If omitted, defaults to 1. Must be a positive integer from 1 to 65535.")
new Option<string[]>(new[] {"--asmpath", "/asmpath"}, description: "Look for assembly references here", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] { "--tlbrefpath", "/tblrefpath"}, description: "Look for type library references here", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<bool>(new[] {"--tlb", "/tlb"}, description: "Will create and register a typelibrary for the given assembly"),
new Option<bool>(new[] {"--codebase", "/codebase"}, description: "Will register the assembly with codebase"),
new Option<bool>(new[] {"--unregister", "/unregister"}, description: "Will unregister the assembly"),
};

var rootCommand = new RootCommand
Expand All @@ -77,7 +88,8 @@ public static int Main(string[] args)
tlbdumpCommand,
tlbregisterCommand,
tlbunregisterCommand,
tlbembedCommand
tlbembedCommand,
registerAssemblyCommand
};

rootCommand.Description = $"dSPACE COM tools ({(Environment.Is64BitProcess ? "64Bit" : "32Bit")})";
Expand All @@ -87,6 +99,7 @@ public static int Main(string[] args)
ConfigureTLBRegisterHandler(tlbregisterCommand);
ConfigureTLBUnRegisterHandler(tlbunregisterCommand);
ConfigureTLBEmbedHandler(tlbembedCommand);
ConfigureRegisterAssemblyHandler(registerAssemblyCommand);

return rootCommand.Invoke(args);
}
Expand Down Expand Up @@ -153,6 +166,78 @@ private static void ConfigureTLBEmbedHandler(Command tlbembedCommand)
tlbembedCommand.Handler = CommandHandler.Create<TypeLibEmbedderSettings>((settings) => TypeLibEmbedder.EmbedTypeLib(settings));
}

/// <summary>
/// Will register an assembly like regasm.exe
/// </summary>
/// <param name="registerCommand"></param>
/// <exception cref="FileNotFoundException"></exception>
private static void ConfigureRegisterAssemblyHandler(Command registerCommand)
{
registerCommand.Handler = CommandHandler.Create<RegisterAssemblySettings>(
(options) =>
{
try
{
var paths = options.ASMPath.Append(Path.GetDirectoryName(options.TargetAssembly)).ToArray();

using var assemblyResolver = new AssemblyResolver(paths!, false);

if (!File.Exists(options.TargetAssembly))
{
throw new FileNotFoundException($"File {options.TargetAssembly} not found.");
}

var assembly = assemblyResolver.LoadAssembly(options.TargetAssembly);

var registrationService = new RegistrationServices();

if (options.Unregister)
{
// Unregister the assembly
if (!registrationService.UnregisterAssembly(assembly))
{
return -1;
}
}
else
{
var lypeLibConvertOptions = new TypeLibConverterOptions()
{
ASMPath = paths!,

TLBRefpath = options.TLBRefpath,

Assembly = options.TargetAssembly,

Out = Path.ChangeExtension(options.TargetAssembly, ".tlb"),

CreateMissingDependentTLBs = false
};

if (options.TLB)
{
// Export TLB
ExportTypeLibraryImpl(assembly, lypeLibConvertOptions);

// Register TLB
RegisterTypeLib(lypeLibConvertOptions.Out);
}

if (!registrationService.RegisterAssembly(assembly, options.Codebase))
{
return -1;
}
}

return 0;
}
catch (Exception e)
{
return HandleException(e, "Failed to register assembly.");
}
});
}

private static void ConfigureTLBExportHandler(Command tlbexportCommand)
{
tlbexportCommand.Handler = CommandHandler.Create<TypeLibConverterOptions>((options) =>
Expand Down Expand Up @@ -191,14 +276,30 @@ private static void ConfigureTLBExportHandler(Command tlbexportCommand)
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ExportTypeLibraryImpl(TypeLibConverterOptions options, out WeakReference weakRef)
{
using var assemblyResolver = new AssemblyResolver(options);
var dir = Path.GetDirectoryName(options.Assembly);

var asmPaths = options.ASMPath;
if (Directory.Exists(dir))
{
asmPaths = asmPaths.Prepend(dir).ToArray();
}

using var assemblyResolver = new AssemblyResolver(asmPaths, options.ShouldEmbed());
weakRef = new WeakReference(assemblyResolver, trackResurrection: true);
if (!File.Exists(options.Assembly))
{
throw new FileNotFoundException($"File {options.Assembly} not found.");
}

var assembly = assemblyResolver.LoadAssembly(options.Assembly);

ExportTypeLibraryImpl(assembly, options);
}

private static void ExportTypeLibraryImpl(
Assembly assembly,
TypeLibConverterOptions options)
{
var typeLibConverter = new TypeLibConverter();
var nameResolver = options.Names.Length > 0 ? NameResolver.Create(options.Names) : NameResolver.Create(assembly);
var typeLib = typeLibConverter.ConvertAssemblyToTypeLib(assembly, options, new TypeLibExporterNotifySink(options, nameResolver));
Expand Down
52 changes: 52 additions & 0 deletions src/dscom/RegisterAssemblySettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2022 dSPACE GmbH, Mark Lechtermann, Matthias Nissen and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace dSPACE.Runtime.InteropServices;

/// <summary>
/// Represents the settings used for registration of an assembly (like RegAsm.exe)
/// </summary>
public class RegisterAssemblySettings
{
/// <summary>
/// Gets or sets target assembly path.
/// </summary>
public string TargetAssembly { get; set; } = string.Empty;

/// <summary>
/// Specifies a directory containing assembly references.
/// </summary>
public string[] ASMPath { get; set; } = Array.Empty<string>();

/// <summary>
/// Gets or sets a path to a directory with type libraries.
/// </summary>
public string[] TLBRefpath { get; set; } = Array.Empty<string>();

/// <summary>
/// Get or sets the flag for creating a type library
/// </summary>
public bool TLB { get; set; }

/// <summary>
/// Gets or sets the flag for creating the codebase entry in the registry.
/// </summary>
public bool Codebase { get; set; }

/// <summary>
/// Gets or sets the flag for deregistration of the assembly
/// </summary>
public bool Unregister { get; set; }

}
Loading

0 comments on commit becca63

Please sign in to comment.