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

Add support in comhost tooling to embed type libraries. #50986

Merged
4 commits merged into from
Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/installer/managed/Microsoft.NET.HostModel/ComHost/ComHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Microsoft.NET.HostModel.ComHost
Expand All @@ -19,10 +21,12 @@ public class ComHost
/// <param name="comHostSourceFilePath">The path of Apphost template, which has the place holder</param>
/// <param name="comHostDestinationFilePath">The destination path for desired location to place, including the file name</param>
/// <param name="clsidmapFilePath">The path to the *.clsidmap file.</param>
/// <param name="typeLibraries">Resource ids for tlbs and paths to the tlb files to be embedded.</param>
public static void Create(
string comHostSourceFilePath,
string comHostDestinationFilePath,
string clsidmapFilePath)
string clsidmapFilePath,
IReadOnlyDictionary<int, string> typeLibraries = null)
{
var destinationDirectory = new FileInfo(comHostDestinationFilePath).Directory.FullName;
if (!Directory.Exists(destinationDirectory))
Expand All @@ -44,6 +48,26 @@ public static void Create(
using (ResourceUpdater updater = new ResourceUpdater(comHostDestinationFilePath))
{
updater.AddResource(clsidMapBytes, (IntPtr)ClsidmapResourceType, (IntPtr)ClsidmapResourceId);
if (typeLibraries is not null)
{
foreach (var typeLibrary in typeLibraries)
{
if (!ResourceUpdater.IsIntResource((IntPtr)typeLibrary.Key))
{
throw new InvalidTypeLibraryIdException(typeLibrary.Value, typeLibrary.Key);
}

try
{
byte[] tlbFileBytes = File.ReadAllBytes(typeLibrary.Value);
updater.AddResource(tlbFileBytes, "typelib", (IntPtr)typeLibrary.Key);
}
catch (FileNotFoundException ex)
{
throw new TypeLibraryDoesNotExistException(typeLibrary.Value, ex);
}
}
}
updater.Update();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.NET.HostModel.ComHost
{
/// <summary>
/// The provided resource id for the type library is unsupported.
/// </summary>
public class InvalidTypeLibraryIdException : Exception
{
public InvalidTypeLibraryIdException(string path, int id)
{
Path = path;
Id = id;
}

public string Path { get; }

public int Id { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.NET.HostModel.ComHost
{
/// <summary>
/// The specified type library path does not exist.
/// </summary>
public class TypeLibraryDoesNotExistException : Exception
{
public TypeLibraryDoesNotExistException(string path, Exception innerException)
:base($"Type library '{path}' does not exist.", innerException)
{
Path = path;
}

public string Path { get; }
}
}
38 changes: 37 additions & 1 deletion src/installer/managed/Microsoft.NET.HostModel/ResourceUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ public static extern bool UpdateResource(SafeUpdateHandle hUpdate,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=5)] byte[] lpData,
uint cbData);

// Update a resource with data from a managed byte[]
[DllImport(nameof(Kernel32), SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UpdateResource(SafeUpdateHandle hUpdate,
string lpType,
IntPtr lpName,
ushort wLanguage,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=5)] byte[] lpData,
uint cbData);

[DllImport(nameof(Kernel32), SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EndUpdateResource(SafeUpdateHandle hUpdate,
Expand Down Expand Up @@ -277,7 +287,7 @@ public ResourceUpdater AddResourcesFromPEImage(string peFile)
return this;
}

private static bool IsIntResource(IntPtr lpType)
internal static bool IsIntResource(IntPtr lpType)
{
return ((uint)lpType >> 16) == 0;
}
Expand Down Expand Up @@ -308,6 +318,32 @@ public ResourceUpdater AddResource(byte[] data, IntPtr lpType, IntPtr lpName)
return this;
}

/// <summary>
/// Add a language-neutral integer resource from a byte[] with
/// a particular type and name. This will not modify the
/// target until Update() is called.
/// Throws an InvalidOperationException if Update() was already called.
/// </summary>
public ResourceUpdater AddResource(byte[] data, string lpType, IntPtr lpName)
{
if (hUpdate.IsInvalid)
{
ThrowExceptionForInvalidUpdate();
}

if (!IsIntResource(lpName))
{
throw new ArgumentException("AddResource can only be used with integer resource names");
}

if (!Kernel32.UpdateResource(hUpdate, lpType, lpName, Kernel32.LangID_LangNeutral_SublangNeutral, data, (uint)data.Length))
{
ThrowExceptionForLastWin32Error();
}

return this;
}

/// <summary>
/// Write the pending resource updates to the target PE
/// file. After this, the ResourceUpdater no longer maintains
Expand Down
18 changes: 15 additions & 3 deletions src/installer/tests/Assets/TestProjects/ComLibrary/ComLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@ public class UserDefinedAttribute : Attribute
{
}

[ComVisible(true)]
[Guid("27293cc8-7933-4fdf-9fde-653cbf9b55df")]
public interface IServer
{
}

[UserDefined]
[ComVisible(true)]
[Guid("438968CE-5950-4FBC-90B0-E64691350DF5")]
public class Server
public class Server : IServer
{
public Server()
{
Expand All @@ -28,14 +34,20 @@ public class NotComVisible
{
}

[ComVisible(true)]
[Guid("f7199267-9821-4f5b-924b-ab5246b455cd")]
public interface INested
{
}

[ComVisible(true)]
[Guid("36e75747-aecd-43bf-9082-1a605889c762")]
public class ComVisible
{
[UserDefined]
[ComVisible(true)]
[Guid("c82e4585-58bd-46e0-a76d-c0b6975e5984")]
public class Nested
public class Nested : INested
{
}
}
Expand All @@ -46,7 +58,7 @@ internal class ComVisibleNonPublic
{
[ComVisible(true)]
[Guid("8a0a7085-aca4-4651-9878-ca42747e2206")]
public class Nested
public class Nested : INested
{
}
}
Expand Down
46 changes: 44 additions & 2 deletions src/installer/tests/HostActivation.Tests/NativeHosting/Comhost.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.IO;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -119,6 +120,38 @@ public void ActivateClass_ValidateIErrorInfoResult()
}
}

[Fact]
public void LoadTypeLibraries()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// COM activation is only supported on Windows
return;
}

using (var fixture = sharedState.ComLibraryFixture.Copy())
{
var comHost = Path.Combine(
fixture.TestProject.BuiltApp.Location,
$"{ fixture.TestProject.AssemblyName }.comhost.dll");

string[] args = {
"comhost",
"typelib",
"2",
comHost,
sharedState.ClsidString
};
CommandResult result = sharedState.CreateNativeHostCommand(args, fixture.BuiltDotnet.BinPath)
.Execute();

result.Should().Pass()
.And.HaveStdOutContaining("Loading default type library succeeded.")
.And.HaveStdOutContaining("Loading type library 1 succeeded.")
.And.HaveStdOutContaining("Loading type library 2 succeeded.");
}
}

public class SharedTestState : SharedTestStateBase
{
public string ComHostPath { get; }
Expand Down Expand Up @@ -150,14 +183,23 @@ public SharedTestState()
}
}

// Use the locally built comhost to create a comhost with the embedded .clsidmap
// Use the locally built comhost to create a comhost with the embedded .clsidmap
ComHostPath = Path.Combine(
ComLibraryFixture.TestProject.BuiltApp.Location,
$"{ ComLibraryFixture.TestProject.AssemblyName }.comhost.dll");

// Include the test type libraries in the ComHost tests.
var typeLibraries = new Dictionary<int, string>
{
{ 1, Path.Combine(RepoDirectories.Artifacts, "corehost_test", "Server.tlb") },
{ 2, Path.Combine(RepoDirectories.Artifacts, "corehost_test", "Nested.tlb") }
};

ComHost.Create(
Path.Combine(RepoDirectories.HostArtifacts, "comhost.dll"),
ComHostPath,
clsidMapPath);
clsidMapPath,
typeLibraries);
}

protected override void Dispose(bool disposing)
Expand Down
3 changes: 3 additions & 0 deletions src/native/corehost/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ add_subdirectory(mockcoreclr)
add_subdirectory(mockhostfxr)
add_subdirectory(mockhostpolicy)
add_subdirectory(nativehost)
if (CLR_CMAKE_TARGET_WIN32)
add_subdirectory(typelibs)
endif()
56 changes: 55 additions & 1 deletion src/native/corehost/test/nativehost/comhost_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <iostream>
#include <future>
#include <pal.h>
#include <oleauto.h>

namespace
{
Expand Down Expand Up @@ -67,12 +68,42 @@ namespace
return pal::pal_utf8string(clsid_str, &clsidVect);
}

void log_hresult(HRESULT hr, std::ostream &ss)
{
if (FAILED(hr))
ss << "(" << std::hex << std::showbase << hr << ")";
}

void log_activation(const char *clsid, int activationNumber, int total, HRESULT hr, std::ostream &ss)
{
ss << "Activation of " << clsid << (FAILED(hr) ? " failed. " : " succeeded. ") << activationNumber << " of " << total;
log_hresult(hr, ss);
ss << std::endl;
}

HRESULT load_typelib(const pal::string_t &typelib_path)
{
HRESULT hr;
ITypeLib* typelib = nullptr;
hr = LoadTypeLibEx(typelib_path.c_str(), REGKIND_NONE, &typelib);
if (FAILED(hr))
ss << "(" << std::hex << std::showbase << hr << ")";
return hr;

typelib->Release();
return hr;
}

void log_typelib_load(int typelib_id, HRESULT hr, std::ostream &ss)
{
ss << "Loading type library " << typelib_id << (FAILED(hr) ? " failed. " : " succeeded. ");
log_hresult(hr, ss);
ss << std::endl;
}

void log_default_typelib_load(HRESULT hr, std::ostream &ss)
{
ss << "Loading default type library" << (FAILED(hr) ? " failed. " : " succeeded. ");
log_hresult(hr, ss);
ss << std::endl;
}
}
Expand Down Expand Up @@ -165,3 +196,26 @@ bool comhost_test::errorinfo(const pal::string_t &comhost_path, const pal::strin

return true;
}

bool comhost_test::typelib(const pal::string_t &comhost_path, int count)
{
HRESULT hr;

hr = load_typelib(comhost_path);
log_default_typelib_load(hr, std::cout);
if (FAILED(hr))
return false;

for (int i = 1; i < count + 1; i++)
{
// The path format for a non-default embedded TLB is 'C:\file\path\to.exe\\2' where '2' is the resource name of the tlb to load.
// See https://docs.microsoft.com/windows/win32/api/oleauto/nf-oleauto-loadtypelib#remarks for documentation on the path format.
pal::stringstream_t tlb_path;
tlb_path << comhost_path << '\\' << i;
hr = load_typelib(tlb_path.str());
log_typelib_load(i, hr, std::cout);
if (FAILED(hr))
return false;
}
return true;
}
2 changes: 2 additions & 0 deletions src/native/corehost/test/nativehost/comhost_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ namespace comhost_test
bool concurrent(const pal::string_t &comhost_path, const pal::string_t &clsid_str, int count);

bool errorinfo(const pal::string_t &comhost_path, const pal::string_t &clsid_str, int count);

bool typelib(const pal::string_t &comhost_path, int count);
}
4 changes: 4 additions & 0 deletions src/native/corehost/test/nativehost/nativehost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,10 @@ int main(const int argc, const pal::char_t *argv[])
{
success = comhost_test::errorinfo(comhost_path, clsid_str, count);
}
else if (pal::strcmp(scenario, _X("typelib")) == 0)
{
success = comhost_test::typelib(comhost_path, count);
}

return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
Expand Down
Loading