Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Lazy loading of type libraries in case of type is class #330

Merged
merged 11 commits into from
Jan 10, 2025
39 changes: 25 additions & 14 deletions src/dscom.test/BaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,47 +20,58 @@ public class BaseTest
{
static BaseTest()
{
Dir = Path.Combine(Directory.GetCurrentDirectory(), "dynamic");
if (!Directory.Exists(Dir))
var dynamic_output = Path.Combine(Directory.GetCurrentDirectory(), "dynamic");

var dynamic_output_dir = new DirectoryInfo(dynamic_output);

// remove existing files, for a fresh restart
if (dynamic_output_dir.Exists)
{
Directory.CreateDirectory(Dir);
dynamic_output_dir.Delete(true);
}

dynamic_output_dir.Create();

Dir = dynamic_output_dir.FullName;
}

private static string Dir { get; }

protected static Regex ValidChars { get; } = new("[^a-zA-Z0-9_]");

internal DynamicAssemblyBuilder CreateAssembly([CallerMemberName] string callerName = "",
[CallerFilePath] string filepath = "")
[CallerFilePath] string filepath = "")
{
return CreateAssembly(CreateAssemblyName(callerName, string.Empty, 0, 0), callerName, filepath);
return CreateAssembly(CreateAssemblyName(callerName, string.Empty, 0, 0), false, callerName, filepath);
}

[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Compatibility")]
internal DynamicAssemblyBuilder CreateAssembly(AssemblyName assemblyName,
CustomAttributeBuilder[] customAttributeBuilders,
[CallerMemberName] string callerName = "",
[CallerFilePath] string filepath = "")
CustomAttributeBuilder[] customAttributeBuilders,
bool assemblyNameAsFilename = false,
[CallerMemberName] string callerName = "",
[CallerFilePath] string filepath = "")
{


#if NETFRAMEWORK
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, Dir, true, customAttributeBuilders);
#else
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run, customAttributeBuilders);
#endif

var name = GenerateAssemblyName(assemblyName, filepath, callerName);
var name = assemblyNameAsFilename
? assemblyName.Name!
: GenerateAssemblyName(assemblyName, filepath, callerName);

var typeLibPath = Path.Combine(Dir, $"{name}.tlb");
return new DynamicAssemblyBuilder(name, assemblyBuilder, typeLibPath);
}

internal DynamicAssemblyBuilder CreateAssembly(AssemblyName name,
[CallerMemberName] string callerName = "",
[CallerFilePath] string filepath = "")
bool assemblyNameAsFilename = false,
[CallerMemberName] string callerName = "",
[CallerFilePath] string filepath = "")
{
return CreateAssembly(name, Array.Empty<CustomAttributeBuilder>(), callerName, filepath);
return CreateAssembly(name, Array.Empty<CustomAttributeBuilder>(), assemblyNameAsFilename, callerName, filepath);
}

protected static AssemblyName CreateAssemblyName([CallerMemberName] string assemblyName = "", string assemblyNameSuffix = "", int major = 0, int minor = 0)
Expand Down
70 changes: 70 additions & 0 deletions src/dscom.test/tests/TypeLibTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,74 @@ public void AssemblyWithAssemblyDescriptionAttribute_DocStringIsAvailable()

docString.Should().Be("Test");
}

[Fact]
public void TypeLib_ShouldBeLoaded_By_Class()
{
CreateAssembly(new AssemblyName("FooBarA"), true)
.WithClass("TestSourceClass")
.WithCustomAttribute(typeof(ClassInterfaceAttribute), ClassInterfaceType.AutoDispatch)
.Build(out var classType)
.WithInterface("ITestSourceInterface")
.Build(out var interfaceType)
.Build();

var assemblyB = CreateAssembly(new AssemblyName("FooBarB"), true)
.WithInterface("TestClassB")
.WithProperty("Some", classType!).Build()
.WithProperty("Other", interfaceType!).Build()
.Build()
.Build();

// check for class
var typeInfo = assemblyB.TypeLib.GetTypeInfoByName("TestClassB");
typeInfo.Should().NotBeNull("TestClassB not found");

// check for 'some' property
using var some_property_funcdesc = typeInfo!.GetFuncDescByName("Some");
some_property_funcdesc.Should().NotBeNull();

some_property_funcdesc!.Value!.elemdescFunc.tdesc.vt.Should().Be((short)VarEnum.VT_PTR, "type is known");

// check for 'other' property
using var other_property_funcdesc = typeInfo!.GetFuncDescByName("Other");
other_property_funcdesc.Should().NotBeNull();

other_property_funcdesc!.Value!.elemdescFunc.tdesc.vt.Should().Be((short)VarEnum.VT_PTR, "type is known");
}

[Fact]
public void TypeLib_ShouldBeLoaded_By_Interface()
{
CreateAssembly(new AssemblyName("FooBarA"), true)
.WithClass("TestSourceClass")
.WithCustomAttribute(typeof(ClassInterfaceAttribute), ClassInterfaceType.AutoDispatch)
.Build(out var classType)
.WithInterface("ITestSourceInterface")
.Build(out var interfaceType)
.Build();

var assemblyB = CreateAssembly(new AssemblyName("FooBarB"), true)
.WithInterface("TestClassB")
.WithProperty("Other", interfaceType!).Build()
.WithProperty("Some", classType!).Build()
.Build()
.Build();

// check for class
var typeInfo = assemblyB.TypeLib.GetTypeInfoByName("TestClassB");
typeInfo.Should().NotBeNull("TestClassB not found");

// check for 'other' property
using var other_property_funcdesc = typeInfo!.GetFuncDescByName("Other");
other_property_funcdesc.Should().NotBeNull();

other_property_funcdesc!.Value!.elemdescFunc.tdesc.vt.Should().Be((short)VarEnum.VT_PTR, "type is known");

// check for 'some' property
using var some_property_funcdesc = typeInfo!.GetFuncDescByName("Some");
some_property_funcdesc.Should().NotBeNull();

some_property_funcdesc!.Value!.elemdescFunc.tdesc.vt.Should().Be((short)VarEnum.VT_PTR, "type is known");
}
}
101 changes: 50 additions & 51 deletions src/dscom/TypeInfoResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,54 @@ public TypeInfoResolver(WriterContext writerContext)
return typeInfo;
}

/// <summary>
/// Resolve the <see cref="ITypeInfo"/> by a <see cref="Guid"/>. If the <see cref="ITypeInfo"/> is not
/// present yet, it will try to add the type library for the corresponding assembly.
/// </summary>
private ITypeInfo? ResolveTypeInfo(Type type, Guid guid)
{
// If the given type is not present in _types
// this means, that the typelib is not loaded yet
if (_types.TryGetValue(guid, out var typeInfo))
{
return typeInfo;
}

var assembly = type.Assembly;

// check if the type library is already present
var identifier = GetTypeLibFromIdentifier(assembly.GetLibIdentifier(WriterContext.Options.OverrideTlbId));
if (identifier is null)
{
foreach (var additionalLib in _additionalLibs)
{
var name = assembly.GetName().Name ?? string.Empty;
marklechtermann marked this conversation as resolved.
Show resolved Hide resolved
if (additionalLib.Contains(name))
{
AddTypeLib(additionalLib);
break;
}
}

var notifySink = WriterContext.NotifySink;
if (notifySink != null)
{
if (notifySink.ResolveRef(assembly) is ITypeLib refTypeLib)
{
AddTypeLib(refTypeLib);
}
}
}

// The dictionary will be updated in 'AddTypeLib'.
// Therefore it should be contain the typeinfo now.
_types.TryGetValue(guid, out typeInfo);
return typeInfo;
}

public ITypeInfo? ResolveTypeInfo(Type type)
{
#pragma warning disable IDE0045 // In bedingten Ausdruck konvertieren
marklechtermann marked this conversation as resolved.
Show resolved Hide resolved
if (_resolvedTypeInfos.TryGetValue(type, out var typeInfo))
{
return typeInfo;
Expand Down Expand Up @@ -102,61 +148,14 @@ public TypeInfoResolver(WriterContext writerContext)
}
else if (type.IsClass)
{
retval = ResolveTypeInfo(MarshalExtension.GetClassInterfaceGuidForType(type));
retval = ResolveTypeInfo(type, MarshalExtension.GetClassInterfaceGuidForType(type));
}
else
{
retval = ResolveTypeInfo(type.GUID);

if (retval == null)
{
var assembly = type.Assembly;
var identifier = assembly.GetLibIdentifier(WriterContext.Options.OverrideTlbId);

var typeLib = GetTypeLibFromIdentifier(identifier);
if (typeLib == null)
{
var name = assembly.GetName().Name ?? string.Empty;
var additionalLibsWithMatchingName = _additionalLibs
.Where(additionalLib => Path.GetFileNameWithoutExtension(additionalLib).Equals(name, StringComparison.OrdinalIgnoreCase));

// At first we try to find a type library that matches the assembly name.
// We do this to limit the number of type libraries to load.
// See https://github.com/dspace-group/dscom/issues/310
foreach (var additionalLib in additionalLibsWithMatchingName)
{
AddTypeLib(additionalLib);
retval = ResolveTypeInfo(type.GUID);
break;
}

// If no type was found in a type library with matching name we search in the remaining type libraries.
if (retval == null)
{
var additionalLibsWithoutMatchingName = _additionalLibs.Except(additionalLibsWithMatchingName);
foreach (var additionalLib in additionalLibsWithoutMatchingName)
{
AddTypeLib(additionalLib);
retval = ResolveTypeInfo(type.GUID);
if (retval != null)
{
break;
}
}
}

var notifySink = WriterContext.NotifySink;
if (notifySink != null)
{
if (notifySink.ResolveRef(assembly) is ITypeLib refTypeLib)
{
AddTypeLib(refTypeLib);
retval = ResolveTypeInfo(type.GUID);
}
}
}
}
retval = ResolveTypeInfo(type, type.GUID);
}
#pragma warning restore IDE0045 // In bedingten Ausdruck konvertieren

_resolvedTypeInfos[type] = retval;
return retval;
}
Expand Down
5 changes: 5 additions & 0 deletions src/dscom/TypeLibConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public class TypeLibConverter
Out = tlbFilePath,
};

if (!string.IsNullOrEmpty(tlbFilePath))
{
options.TLBRefpath = new[] { Path.GetDirectoryName(tlbFilePath)! };
}

return ConvertAssemblyToTypeLib(assembly, options, notifySink);
}

Expand Down