Skip to content

Commit

Permalink
Add support in comhost tooling to embed type libraries. (#50986)
Browse files Browse the repository at this point in the history
* Add support in comhost tooling to embed type libraries.

Sdk work will still be needed to enable developers to embed tlbs in their comhosts.

* Cleanup.

* PR feedback. Update validation to throw specific exception types for specific errors so the SDK can accurately report errors to the user.
  • Loading branch information
jkoritzinsky authored Apr 13, 2021
1 parent 36e3cef commit 91c16fa
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 8 deletions.
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

0 comments on commit 91c16fa

Please sign in to comment.