void **class_factory_dest;
};
-using com_activation_fn = int(*)(com_activation_context*);
+using com_activation_fn = int(STDMETHODCALLTYPE*)(com_activation_context*);
namespace
{
./nativehost.cpp
)
+if(WIN32)
+ list(APPEND SOURCES
+ ./comhost_test.cpp)
+
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DELAYLOAD:nethost.dll")
+endif()
+
include(../testexe.cmake)
-target_link_libraries(${DOTNET_PROJECT_NAME} nethost)
\ No newline at end of file
+target_link_libraries(${DOTNET_PROJECT_NAME} nethost)
+
+# Specify non-default Windows libs to be used for Arm/Arm64 builds
+if (WIN32 AND (CLI_CMAKE_PLATFORM_ARCH_ARM OR CLI_CMAKE_PLATFORM_ARCH_ARM64))
+ target_link_libraries(${DOTNET_PROJECT_NAME} Advapi32.lib Ole32.lib OleAut32.lib)
+endif()
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "comhost_test.h"
+#include <error_codes.h>
+#include <iostream>
+#include <future>
+#include <pal.h>
+
+namespace
+{
+ class comhost_exports
+ {
+ public:
+ comhost_exports(const pal::string_t &comhost_path)
+ {
+ if (!pal::load_library(&comhost_path, &_dll))
+ {
+ std::cout << "Load library of comhost failed" << std::endl;
+ throw StatusCode::CoreHostLibLoadFailure;
+ }
+
+ get_class_obj_fn = (decltype(get_class_obj_fn))pal::get_symbol(_dll, "DllGetClassObject");
+ if (get_class_obj_fn == nullptr)
+ {
+ std::cout << "Failed to get DllGetClassObject export from comhost" << std::endl;
+ throw StatusCode::CoreHostEntryPointFailure;
+ }
+ }
+
+ ~comhost_exports()
+ {
+ pal::unload_library(_dll);
+ }
+
+ decltype(&DllGetClassObject) get_class_obj_fn;
+
+ private:
+ pal::dll_t _dll;
+ };
+
+ HRESULT activate_class(comhost_exports &comhost, REFCLSID clsid)
+ {
+ IClassFactory *classFactory;
+ HRESULT hr = comhost.get_class_obj_fn(clsid, __uuidof(IClassFactory), (void**)&classFactory);
+ if (FAILED(hr))
+ return hr;
+
+ IUnknown *instance;
+ hr = classFactory->CreateInstance(nullptr, __uuidof(instance), (void**)&instance);
+ classFactory->Release();
+ if (FAILED(hr))
+ return hr;
+
+ instance->Release();
+ return S_OK;
+ }
+
+ bool get_clsid(const pal::string_t &clsid_str, CLSID *clsid, std::vector<char> &clsidVect)
+ {
+ if (FAILED(::CLSIDFromString(clsid_str.c_str(), clsid)))
+ {
+ std::cout << "Invalid CLSID: " << clsid_str.c_str() << std::endl;
+ return false;
+ }
+
+ return pal::pal_utf8string(clsid_str, &clsidVect);
+ }
+}
+
+bool comhost_test::synchronous(const pal::string_t &comhost_path, const pal::string_t &clsid_str, int count)
+{
+ CLSID clsid;
+ std::vector<char> clsidVect;
+ if (!get_clsid(clsid_str, &clsid, clsidVect))
+ return false;
+
+ comhost_exports comhost(comhost_path);
+
+ for (int i = 0; i < count; ++i)
+ {
+ HRESULT hr = activate_class(comhost, clsid);
+ if (FAILED(hr))
+ {
+ std::cout << "Activation of " << clsidVect.data() << " failed. "
+ << i + 1 << " of " << count << "(" << std::hex << std::showbase << hr << ")" << std::endl;
+ return false;
+ }
+
+ std::cout << "Activation of " << clsidVect.data() << " succeeded. "
+ << i + 1 << " of " << count << std::endl;
+ }
+
+ return true;
+}
+
+bool comhost_test::concurrent(const pal::string_t &comhost_path, const pal::string_t &clsid_str, int count)
+{
+ CLSID clsid;
+ std::vector<char> clsidVect;
+ if (!get_clsid(clsid_str, &clsid, clsidVect))
+ return false;
+
+ comhost_exports comhost(comhost_path);
+
+ std::vector<std::future<HRESULT>> activations;
+ activations.reserve(count);
+ for (int i = 0; i < count; ++i)
+ activations.push_back(std::async(std::launch::async, activate_class, comhost, clsid));
+
+ bool succeeded = true;
+ for (int i = 0; i < count; ++i)
+ {
+ HRESULT hr = activations[i].get();
+ if (FAILED(hr))
+ {
+ std::cout << "Activation of " << clsidVect.data() << " failed. "
+ << i + 1 << " of " << count << "(" << std::hex << std::showbase << hr << ")" << std::endl;
+ succeeded = false;
+ }
+ else
+ {
+ std::cout << "Activation of " << clsidVect.data() << " succeeded. "
+ << i + 1 << " of " << count << std::endl;
+ }
+ }
+
+ return true;
+}
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include <pal.h>
+
+namespace comhost_test
+{
+ bool synchronous(const pal::string_t &comhost_path, const pal::string_t &clsid_str, int count);
+
+ bool concurrent(const pal::string_t &comhost_path, const pal::string_t &clsid_str, int count);
+}
\ No newline at end of file
#include <pal.h>
#include <error_codes.h>
#include <nethost.h>
+#include "comhost_test.h"
namespace
{
{
std::cout << "get_hostfxr_path succeeded" << std::endl;
std::cout << "hostfxr_path: " << tostr(pal::to_lower(fxr_path)).data() << std::endl;
- return 0;
+ return EXIT_SUCCESS;
}
else
{
std::cout << "get_hostfxr_path failed: " << std::hex << std::showbase << res << std::endl;
- return 1;
+ return EXIT_FAILURE;
}
}
+#if defined(_WIN32)
+ else if (pal::strcmp(command, _X("comhost")) == 0)
+ {
+ // args: ... <scenario> <activation_count> <comhost_path> <clsid>
+ if (argc < 6)
+ {
+ std::cerr << "Invalid arguments" << std::endl;
+ return -1;
+ }
+
+ const pal::char_t *scenario = argv[2];
+ int count = pal::xtoi(argv[3]);
+ const pal::string_t comhost_path = argv[4];
+ const pal::string_t clsid_str = argv[5];
+
+ bool success = false;
+ if (pal::strcmp(scenario, _X("synchronous")) == 0)
+ {
+ success = comhost_test::synchronous(comhost_path, clsid_str, count);
+ }
+ else if (pal::strcmp(scenario, _X("concurrent")) == 0)
+ {
+ success = comhost_test::concurrent(comhost_path, clsid_str, count);
+ }
+
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+ }
+#endif
else
{
std::cerr << "Invalid arguments" << std::endl;
--- /dev/null
+using System;
+using System.Runtime.InteropServices;
+
+namespace ComLibrary
+{
+ [ComVisible(true)]
+ [Guid("438968CE-5950-4FBC-90B0-E64691350DF5")]
+ public class Server
+ {
+ public Server()
+ {
+ Console.WriteLine($"New instance of {nameof(Server)} created");
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>$(NETCoreAppFramework)</TargetFramework>
+ <RuntimeFrameworkVersion>$(MNAVersion)</RuntimeFrameworkVersion>
+ </PropertyGroup>
+
+</Project>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.DotNet.Cli.Build.Framework;
+using Newtonsoft.Json.Linq;
+using System.IO;
+using System.Runtime.InteropServices;
+using Xunit;
+
+namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
+{
+ public class Comhost : IClassFixture<Comhost.SharedTestState>
+ {
+ private readonly SharedTestState sharedState;
+
+ public Comhost(SharedTestState sharedTestState)
+ {
+ sharedState = sharedTestState;
+ }
+
+ [Theory]
+ [InlineData(1, true)]
+ [InlineData(10, true)]
+ [InlineData(10, false)]
+ public void ActivateClass(int count, bool synchronous)
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // COM activation is only supported on Windows
+ return;
+ }
+
+ var fixture = sharedState.ComLibraryFixture
+ .Copy();
+
+ string scenario = synchronous ? "synchronous" : "concurrent";
+ string args = $"comhost {scenario} {count} {sharedState.ComHostPath} {sharedState.ClsidString}";
+ CommandResult result = Command.Create(sharedState.NativeHostPath, args)
+ .CaptureStdErr()
+ .CaptureStdOut()
+ .EnvironmentVariable("COREHOST_TRACE", "1")
+ .EnvironmentVariable("DOTNET_ROOT", fixture.BuiltDotnet.BinPath)
+ .EnvironmentVariable("DOTNET_ROOT(x86)", fixture.BuiltDotnet.BinPath)
+ .Execute();
+
+ result.Should().Pass()
+ .And.HaveStdOutContaining("New instance of Server created");
+
+ for (var i = 1; i <= count; ++i)
+ {
+ result.Should().HaveStdOutContaining($"Activation of {sharedState.ClsidString} succeeded. {i} of {count}");
+ }
+ }
+
+ public class SharedTestState : SharedTestStateBase
+ {
+ public string ComHostPath { get; }
+
+ public string ClsidString = "{438968CE-5950-4FBC-90B0-E64691350DF5}";
+ public TestProjectFixture ComLibraryFixture { get; }
+
+ public SharedTestState()
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // COM activation is only supported on Windows
+ return;
+ }
+
+ ComLibraryFixture = new TestProjectFixture("ComLibrary", RepoDirectories)
+ .EnsureRestored(RepoDirectories.CorehostPackages)
+ .BuildProject();
+
+ ComHostPath = Path.Combine(
+ ComLibraryFixture.TestProject.BuiltApp.Location,
+ $"{ ComLibraryFixture.TestProject.AssemblyName }.comhost.dll");
+
+ File.Copy(Path.Combine(RepoDirectories.CorehostPackages, "comhost.dll"), ComHostPath);
+
+ RuntimeConfig.FromFile(ComLibraryFixture.TestProject.RuntimeConfigJson)
+ .WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", RepoDirectories.MicrosoftNETCoreAppVersion))
+ .Save();
+
+ JObject clsidMap = new JObject()
+ {
+ {
+ ClsidString,
+ new JObject() { {"assembly", "ComLibrary" }, {"type", "ComLibrary.Server" } }
+ }
+ };
+ File.WriteAllText($"{ ComHostPath }.clsidmap", clsidMap.ToString());
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (ComLibraryFixture != null)
+ ComLibraryFixture.Dispose();
+
+ base.Dispose(disposing);
+ }
+ }
+ }
+}
// See the LICENSE file in the project root for more information.
using Microsoft.DotNet.Cli.Build.Framework;
-using Microsoft.Win32;
-using System;
using System.IO;
using System.Runtime.InteropServices;
using Xunit;
.And.HaveStdOutContaining($"hostfxr_path: {hostFxrPath}".ToLower());
}
- public class SharedTestState : IDisposable
+ public class SharedTestState : SharedTestStateBase
{
- public string BaseDirectory { get; }
- public string NativeHostPath { get; }
- public RepoDirectoriesProvider RepoDirectories { get; }
-
public string HostFxrPath { get; }
public string InvalidInstallRoot { get; }
public string ValidInstallRoot { get; }
public SharedTestState()
{
- BaseDirectory = SharedFramework.CalculateUniqueTestDirectory(Path.Combine(TestArtifact.TestArtifactsPath, "nativeHosting"));
- Directory.CreateDirectory(BaseDirectory);
-
- string nativeHostName = RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("nativehost");
- NativeHostPath = Path.Combine(BaseDirectory, nativeHostName);
-
- // Copy over native host and nethost
- RepoDirectories = new RepoDirectoriesProvider();
+ // Copy nethost next to native host
string nethostName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("nethost");
- File.Copy(Path.Combine(RepoDirectories.CorehostPackages, nethostName), Path.Combine(BaseDirectory, nethostName));
- File.Copy(Path.Combine(RepoDirectories.Artifacts, "corehost_test", nativeHostName), NativeHostPath);
+ File.Copy(
+ Path.Combine(RepoDirectories.CorehostPackages, nethostName),
+ Path.Combine(Path.GetDirectoryName(NativeHostPath), nethostName));
InvalidInstallRoot = Path.Combine(BaseDirectory, "invalid");
Directory.CreateDirectory(InvalidInstallRoot);
return Path.Combine(fxrRoot, "2.3.0", HostFxrName);
}
-
- public void Dispose()
- {
- if (!TestArtifact.PreserveTestRuns() && Directory.Exists(BaseDirectory))
- {
- Directory.Delete(BaseDirectory, true);
- }
- }
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+
+namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
+{
+ public class SharedTestStateBase : IDisposable
+ {
+ public string BaseDirectory { get; }
+ public string NativeHostPath { get; }
+ public RepoDirectoriesProvider RepoDirectories { get; }
+
+ public SharedTestStateBase()
+ {
+ BaseDirectory = SharedFramework.CalculateUniqueTestDirectory(Path.Combine(TestArtifact.TestArtifactsPath, "nativeHosting"));
+ Directory.CreateDirectory(BaseDirectory);
+
+ string nativeHostName = RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("nativehost");
+ NativeHostPath = Path.Combine(BaseDirectory, nativeHostName);
+
+ // Copy over native host
+ RepoDirectories = new RepoDirectoriesProvider();
+ File.Copy(Path.Combine(RepoDirectories.Artifacts, "corehost_test", nativeHostName), NativeHostPath);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!TestArtifact.PreserveTestRuns() && Directory.Exists(BaseDirectory))
+ {
+ Directory.Delete(BaseDirectory, true);
+ }
+ }
+ }
+}