Skip to content

Commit

Permalink
Proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
mavToday committed Nov 26, 2024
1 parent 6736eee commit 2ef482f
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 56 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: 126 additions & 43 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,58 +28,68 @@ 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.")
};
{
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 registerCommand = new Command("asmregister", "register assembly")
{
new Argument<string>("TargetAssembly", "File name of target assembly to receive the type library as a resource"),
new Option<string[]>(new[] {"--asmpath", "/asmpath"}, description: "Look for assembly references here", getDefaultValue: () => Array.Empty<string>()) { Arity = ArgumentArity.ZeroOrMore},
new Option<string[]>(new[] { "--tblrefpath", "/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"),
};

var rootCommand = new RootCommand
{
tlbexportCommand,
tlbdumpCommand,
tlbregisterCommand,
tlbunregisterCommand,
tlbembedCommand
};
{
tlbexportCommand,
tlbdumpCommand,
tlbregisterCommand,
tlbunregisterCommand,
tlbembedCommand,
registerCommand
};

rootCommand.Description = $"dSPACE COM tools ({(Environment.Is64BitProcess ? "64Bit" : "32Bit")})";

Expand All @@ -87,6 +98,7 @@ public static int Main(string[] args)
ConfigureTLBRegisterHandler(tlbregisterCommand);
ConfigureTLBUnRegisterHandler(tlbunregisterCommand);
ConfigureTLBEmbedHandler(tlbembedCommand);
ConfigureAsmRegisterHandler(registerCommand);

return rootCommand.Invoke(args);
}
Expand Down Expand Up @@ -153,6 +165,61 @@ 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 ConfigureAsmRegisterHandler(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 lypeLibConvertOptions = new TypeLibConverterOptions()
{
ASMPath = paths!,
TLBRefpath = options.TLBRefpath,
Assembly = options.TargetAssembly,
Out = Path.ChangeExtension(options.TargetAssembly, ".tlb")
};
if (options.TLB)
{
// Export TLB
ExportTypeLibraryImpl(assembly, lypeLibConvertOptions);
// Register TLB
RegisterTypeLib(lypeLibConvertOptions.Out);
}
var registrationService = new RegistrationServices();
registrationService.RegisterAssembly(assembly, options.Codebase);
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 +258,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
46 changes: 46 additions & 0 deletions src/dscom/RegisterAssemblySettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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 by <see cref="TypeLibEmbedder"/>.
/// </summary>
public class RegisterAssemblySettings
{
/// <summary>
/// Gets or sets target assembly path.
/// </summary>
public string TargetAssembly { get; set; } = string.Empty;

/// <summary>
/// Gets or sets a path to a directory with assemblies.
/// </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 registration with codebase
/// </summary>
public bool Codebase { get; set; }
}
12 changes: 12 additions & 0 deletions src/dscom/RegistrationServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,18 @@ private static IReadOnlyCollection<Type> GetComRegistratableTypes(Assembly assem
{
static bool TypeMustBeRegistered(Type type)
{
// Only public types are com visible
if (!type.IsPublic && !type.IsNestedPublic && !type.IsByRef)
{
return false;
}

// Type could be defined as not com visible
if (!type.IsComVisible())
{
return false;
}

if (type.IsGenericType)
{
return false;
Expand Down

0 comments on commit 2ef482f

Please sign in to comment.