diff options
author | Aaron Robinson <arobins@microsoft.com> | 2018-09-10 17:24:49 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-10 17:24:49 -0700 |
commit | fc3378095f04946815e627a5ab70b528a898abe6 (patch) | |
tree | 44a1e2cd85c2a5c369bf6f754a0379a0751ccb66 /src/coreclr | |
parent | efd7220234aacef4af25a747797984d43ba5b961 (diff) | |
download | coreclr-fc3378095f04946815e627a5ab70b528a898abe6.tar.gz coreclr-fc3378095f04946815e627a5ab70b528a898abe6.tar.bz2 coreclr-fc3378095f04946815e627a5ab70b528a898abe6.zip |
Basic implementation for testing of COM activation of a .NET class (#19760)
* Rough outline of managed implementation for COM activation in SPCL
* Add property for finding interop common
Add property to exclude default assertion file
Display exe ExeLaunchProgram class is going to launch
* Add a native client for the NETServer
Consume the ExeLauncherProgram.cs file as a wrapper for the native test
* Update COM Server contracts to use 'int' instead of 'long'
* Complete symmetric testing coverage for .NET server and native client.
* Block EXE launch from running on non-Windows machines
* Disable COM testing in helix since it has issues on Windows Nano and there
is no way to determine that is the platform.
* Update tests based on CLSID mapping manifest approach.
Diffstat (limited to 'src/coreclr')
-rw-r--r-- | src/coreclr/hosts/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/coreclr/hosts/coreshim/CMakeLists.txt | 25 | ||||
-rw-r--r-- | src/coreclr/hosts/coreshim/ComActivation.cpp | 91 | ||||
-rw-r--r-- | src/coreclr/hosts/coreshim/CoreShim.cpp | 293 | ||||
-rw-r--r-- | src/coreclr/hosts/coreshim/CoreShim.h | 179 | ||||
-rw-r--r-- | src/coreclr/hosts/coreshim/Exports.def | 3 |
6 files changed, 592 insertions, 0 deletions
diff --git a/src/coreclr/hosts/CMakeLists.txt b/src/coreclr/hosts/CMakeLists.txt index bb425b908a..c27ba16c56 100644 --- a/src/coreclr/hosts/CMakeLists.txt +++ b/src/coreclr/hosts/CMakeLists.txt @@ -3,6 +3,7 @@ include_directories(inc) if(WIN32) add_subdirectory(corerun) add_subdirectory(coreconsole) + add_subdirectory(coreshim) else(WIN32) add_subdirectory(unixcoreruncommon) add_subdirectory(unixcorerun) diff --git a/src/coreclr/hosts/coreshim/CMakeLists.txt b/src/coreclr/hosts/coreshim/CMakeLists.txt new file mode 100644 index 0000000000..828b91ce5b --- /dev/null +++ b/src/coreclr/hosts/coreshim/CMakeLists.txt @@ -0,0 +1,25 @@ +project (CoreShim) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CoreShim_SOURCES + CoreShim.cpp + ComActivation.cpp + Exports.def) + +add_library_clr(CoreShim + SHARED + ${CoreShim_SOURCES} +) + +target_link_libraries(CoreShim + utilcodestaticnohost + advapi32.lib + oleaut32.lib + uuid.lib + user32.lib + ${STATIC_MT_CRT_LIB} + ${STATIC_MT_VCRT_LIB} +) + +install_clr(CoreShim)
\ No newline at end of file diff --git a/src/coreclr/hosts/coreshim/ComActivation.cpp b/src/coreclr/hosts/coreshim/ComActivation.cpp new file mode 100644 index 0000000000..5df1d000c1 --- /dev/null +++ b/src/coreclr/hosts/coreshim/ComActivation.cpp @@ -0,0 +1,91 @@ +// 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 "CoreShim.h" + +#include <vector> + +namespace +{ + HRESULT InitializeCoreClr(_In_ coreclr* inst) + { + assert(inst != nullptr); + + HRESULT hr; + + std::string tpaList; + RETURN_IF_FAILED(coreclr::CreateTpaList(tpaList)); + + const char *keys[] = + { + "APP_PATHS", + "TRUSTED_PLATFORM_ASSEMBLIES", + }; + + // [TODO] Support UNICODE app path + char wd[MAX_PATH]; + (void)::GetCurrentDirectoryA(ARRAYSIZE(wd), wd); + + const char *values[] = + { + wd, + tpaList.c_str(), + }; + + static_assert(ARRAYSIZE(keys) == ARRAYSIZE(values), "key/values pairs should match in length"); + + return inst->Initialize(ARRAYSIZE(keys), keys, values, "COMAct"); + } +} + +STDAPI DllGetClassObject( + _In_ REFCLSID rclsid, + _In_ REFIID riid, + _Outptr_ LPVOID FAR* ppv) +{ + HRESULT hr; + + coreclr *inst; + RETURN_IF_FAILED(coreclr::GetCoreClrInstance(&inst)); + + if (hr == S_OK) + RETURN_IF_FAILED(InitializeCoreClr(inst)); + + using GetClassFactoryForTypeInternal_ptr = HRESULT(*)(void *); + GetClassFactoryForTypeInternal_ptr GetClassFactoryForTypeInternal; + RETURN_IF_FAILED(inst->CreateDelegate( + "System.Private.CoreLib", + "System.Runtime.InteropServices.ComActivator", + "GetClassFactoryForTypeInternal", (void**)&GetClassFactoryForTypeInternal)); + + // Get assembly and type for activation + std::string assemblyName; + RETURN_IF_FAILED(Utility::TryGetEnvVar(COMACT_ASSEMBLYNAME_ENVVAR, assemblyName)); + + std::string typeName; + RETURN_IF_FAILED(Utility::TryGetEnvVar(COMACT_TYPENAME_ENVVAR, typeName)); + + IUnknown *ccw = nullptr; + + struct ComActivationContext + { + GUID ClassId; + GUID InterfaceId; + const void *AssemblyName; + const void *TypeName; + void **ClassFactoryDest; + } comCxt{ rclsid, riid, assemblyName.data(), typeName.data(), (void**)&ccw }; + + RETURN_IF_FAILED(GetClassFactoryForTypeInternal(&comCxt)); + assert(ccw != nullptr); + + hr = ccw->QueryInterface(riid, ppv); + ccw->Release(); + return hr; +} + +STDAPI DllCanUnloadNow(void) +{ + return S_FALSE; +} diff --git a/src/coreclr/hosts/coreshim/CoreShim.cpp b/src/coreclr/hosts/coreshim/CoreShim.cpp new file mode 100644 index 0000000000..497c10e5d2 --- /dev/null +++ b/src/coreclr/hosts/coreshim/CoreShim.cpp @@ -0,0 +1,293 @@ +// 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 "CoreShim.h" + +#include <set> +#include <sstream> +#include <vector> +#include <mutex> + +namespace +{ + struct PathBuffer + { + PathBuffer() + : DefBuffer{} + , Buf{ DefBuffer } + , Len{ ARRAYSIZE(DefBuffer) } + { } + + void SetLength(_In_ DWORD len) + { + if (len > Len) + { + Buf = BigBuffer.data(); + Len = static_cast<DWORD>(BigBuffer.size()); + } + } + + void ExpandBuffer(_In_ DWORD factor = 2) + { + SetLength(Len * factor); + } + + operator DWORD() + { + return Len; + } + + operator WCHAR *() + { + return Buf; + } + + WCHAR DefBuffer[MAX_PATH]; + std::vector<WCHAR> BigBuffer; + + WCHAR *Buf; + DWORD Len; + }; + + std::string GetExePath() + { + PathBuffer buffer; + DWORD len = ::GetModuleFileNameW(nullptr, buffer, buffer); + while (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + buffer.ExpandBuffer(); + len = ::GetModuleFileNameW(nullptr, buffer, buffer); + } + + return std::string{ buffer.Buf, buffer.Buf + len }; + } + + std::wstring GetEnvVar(_In_z_ const WCHAR *env) + { + DWORD len = ::GetEnvironmentVariableW(env, nullptr, 0); + if (len == 0) + throw __HRESULT_FROM_WIN32(ERROR_ENVVAR_NOT_FOUND); + + PathBuffer buffer; + buffer.SetLength(len); + (void)::GetEnvironmentVariableW(env, buffer, buffer); + + return static_cast<WCHAR *>(buffer.Buf); + } + + coreclr *s_CoreClrInstance; +} + +namespace Utility +{ + HRESULT TryGetEnvVar(_In_z_ const WCHAR *env, _Inout_ std::string &envVar) + { + try + { + std::wstring envVarLocal = GetEnvVar(env); + envVar = { std::begin(envVarLocal), std::end(envVarLocal) }; + } + catch (HRESULT hr) + { + return hr; + } + + return S_OK; + } +} + +HRESULT coreclr::GetCoreClrInstance(_Outptr_ coreclr **instance, _In_opt_z_ const WCHAR *path) +{ + if (s_CoreClrInstance != nullptr) + { + *instance = s_CoreClrInstance; + return S_FALSE; + } + + try + { + std::wstring pathLocal; + if (path == nullptr) + { + pathLocal = GetEnvVar(W("CORE_ROOT")); + } + else + { + pathLocal = { path }; + } + + pathLocal.append(W("\\coreclr.dll")); + + AutoModule hmod = ::LoadLibraryExW(pathLocal.c_str() , nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + if (hmod == nullptr) + return HRESULT_FROM_WIN32(::GetLastError()); + + s_CoreClrInstance = new coreclr{ std::move(hmod) }; + } + catch (HRESULT hr) + { + return hr; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + + *instance = s_CoreClrInstance; + return S_OK; +} + +HRESULT coreclr::CreateTpaList(_Inout_ std::string &tpaList, _In_opt_z_ const WCHAR *dir) +{ + assert(tpaList.empty()); + + // Represents priority order + static const WCHAR * const tpaExtensions[] = + { + W(".ni.dll"), + W(".dll"), + W(".ni.exe"), + W(".exe"), + }; + + try + { + std::wstring w_dirLocal; + if (dir == nullptr) + { + w_dirLocal = GetEnvVar(W("CORE_ROOT")); + } + else + { + w_dirLocal = { dir }; + } + + std::string dirLocal{ std::begin(w_dirLocal), std::end(w_dirLocal) }; + w_dirLocal.append(W("\\*")); + + std::set<std::wstring> addedAssemblies; + std::stringstream tpaStream; + + // Walk the directory for each extension separately so assembly types + // are discovered in priority order - see above. + for (int extIndex = 0; extIndex < ARRAYSIZE(tpaExtensions); extIndex++) + { + const WCHAR* ext = tpaExtensions[extIndex]; + size_t extLength = ::wcslen(ext); + + WIN32_FIND_DATAW ffd; + AutoFindFile sh = ::FindFirstFileW(w_dirLocal.c_str(), &ffd); + if (sh == nullptr) + break; + + // For all entries in the directory + do + { + // Only examine non-directory entries + if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + std::wstring filename{ ffd.cFileName }; + + // Check if the extension matches + int extPos = static_cast<int>(filename.length() - extLength); + if ((extPos <= 0) || (filename.compare(extPos, extLength, ext) != 0)) + { + continue; + } + + std::wstring filenameWithoutExt{ filename.substr(0, extPos) }; + + // Only one type of a particular assembly instance should be inserted + // See extension list above. + if (addedAssemblies.find(filenameWithoutExt) == std::end(addedAssemblies)) + { + addedAssemblies.insert(std::move(filenameWithoutExt)); + + // [TODO] Properly convert to UTF-8 + std::string filename_utf8{ std::begin(filename), std::end(filename) }; + tpaStream << dirLocal << "\\" << filename_utf8 << ";"; + } + } + } while (::FindNextFileW(sh, &ffd) != FALSE); + } + + tpaList = tpaStream.str(); + } + catch (HRESULT hr) + { + return hr; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +coreclr::coreclr(_Inout_ AutoModule hmod) + : _hmod{ std::move(hmod) } + , _clrInst{ nullptr } + , _appDomainId{ std::numeric_limits<uint32_t>::max() } +{ + _initialize = (decltype(_initialize))::GetProcAddress(_hmod, "coreclr_initialize"); + assert(_initialize != nullptr); + + _create_delegate = (decltype(_create_delegate))::GetProcAddress(_hmod, "coreclr_create_delegate"); + assert(_create_delegate != nullptr); + + _shutdown = (decltype(_shutdown))::GetProcAddress(_hmod, "coreclr_shutdown"); + assert(_shutdown != nullptr); +} + +coreclr::~coreclr() +{ + if (_clrInst != nullptr) + { + HRESULT hr = _shutdown(_clrInst, _appDomainId); + assert(SUCCEEDED(hr)); + (void)hr; + } +} + +HRESULT coreclr::Initialize( + _In_ int propertyCount, + _In_reads_(propertCount) const char **keys, + _In_reads_(propertCount) const char **values, + _In_opt_z_ const char *appDomainName) +{ + if (_clrInst != nullptr) + return __HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + + if (appDomainName == nullptr) + appDomainName = "CoreShim"; + + HRESULT hr; + try + { + const std::string exePath = GetExePath(); + RETURN_IF_FAILED(_initialize(exePath.c_str(), appDomainName, propertyCount, keys, values, &_clrInst, &_appDomainId)); + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT coreclr::CreateDelegate( + _In_z_ const char *assembly, + _In_z_ const char *type, + _In_z_ const char *method, + _Out_ void **del) +{ + if (_clrInst == nullptr) + return E_NOT_VALID_STATE; + + HRESULT hr; + RETURN_IF_FAILED(_create_delegate(_clrInst, _appDomainId, assembly, type, method, del)); + + return S_OK; +} diff --git a/src/coreclr/hosts/coreshim/CoreShim.h b/src/coreclr/hosts/coreshim/CoreShim.h new file mode 100644 index 0000000000..dd5e9d1297 --- /dev/null +++ b/src/coreclr/hosts/coreshim/CoreShim.h @@ -0,0 +1,179 @@ +// 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. + +#ifndef _CORESHIM_H_ +#define _CORESHIM_H_ + +// Platform +#define NOMINMAX +#include <Windows.h> +#include <combaseapi.h> + +// Standard library +#include <utility> +#include <string> +#include <cstdint> +#include <cassert> + +// CoreCLR +#include <palclr.h> +#include <coreclrhost.h> + +#define WCHAR wchar_t + +#define RETURN_IF_FAILED(exp) { hr = (exp); if (FAILED(hr)) { assert(false && #exp); return hr; } } + +template +< + typename T, + T DEFAULT, + void(*RELEASE)(T) +> +struct AutoClass +{ + T c; + + AutoClass() : c{ DEFAULT } + { } + + AutoClass(_Inout_ T t) : c{ t } + { } + + AutoClass(_In_ const AutoClass&) = delete; + AutoClass& operator=(_In_ const AutoClass&) = delete; + + AutoClass(_Inout_ AutoClass &&other) + : c{ other.Detach() } + { } + + AutoClass& operator=(_Inout_ AutoClass &&other) + { + Attach(other.Detach()); + } + + ~AutoClass() + { + Attach(DEFAULT); + } + + operator T() + { + return c; + } + + T* operator &() + { + return &c; + } + + void Attach(_In_opt_ T cm) + { + RELEASE(c); + c = cm; + } + + T Detach() + { + T tmp = c; + c = DEFAULT; + return tmp; + } +}; + +inline void ReleaseHandle(_In_ HANDLE h) +{ + if (h != nullptr && h != INVALID_HANDLE_VALUE) + ::CloseHandle(h); +} + +using AutoHandle = AutoClass<HANDLE, nullptr, &ReleaseHandle>; + +inline void ReleaseFindFile(_In_ HANDLE h) +{ + if (h != nullptr) + ::FindClose(h); +} + +using AutoFindFile = AutoClass<HANDLE, nullptr, &ReleaseFindFile>; + +inline void ReleaseModule(_In_ HMODULE m) +{ + if (m != nullptr) + ::FreeLibrary(m); +} + +using AutoModule = AutoClass<HMODULE, nullptr, &ReleaseModule>; + +namespace Utility +{ + /// <summary> + /// Get the supplied environment variable. + /// </summary> + HRESULT TryGetEnvVar(_In_z_ const WCHAR *env, _Inout_ std::string &envVar); +} + +// CoreShim environment variables used to indicate what assembly/type tuple +// to load during COM activation. +#define COMACT_ASSEMBLYNAME_ENVVAR W("CORESHIM_COMACT_ASSEMBLYNAME") +#define COMACT_TYPENAME_ENVVAR W("CORESHIM_COMACT_TYPENAME") + +// CoreCLR class to handle lifetime and provide a simpler API surface +class coreclr +{ +public: // static + /// <summary> + /// Get a CoreCLR instance + /// </summary> + /// <returns>S_OK if newly created and needs initialization, S_FALSE if already exists and no initialization needed, otherwise an error code</returns> + /// <remarks> + /// If a CoreCLR instance has already been created, the existing instance is returned. + /// If the <paramref name="path"/> is not supplied, the 'CORE_ROOT' environment variable is used. + /// </remarks> + static HRESULT GetCoreClrInstance(_Outptr_ coreclr **instance, _In_opt_z_ const WCHAR *path = nullptr); + + /// <summary> + /// Populate the supplied string with a delimited string of TPA assemblies in from the supplied directory path. + /// </summary> + /// <remarks> + /// If <paramref name="dir"/> is not supplied, the 'CORE_ROOT' environment variable is used. + /// </remarks> + static HRESULT CreateTpaList(_Inout_ std::string &tpaList, _In_opt_z_ const WCHAR *dir = nullptr); + +public: + coreclr(_Inout_ AutoModule hmod); + + coreclr(_In_ const coreclr &) = delete; + coreclr& operator=(_In_ const coreclr &) = delete; + + coreclr(_Inout_ coreclr &&) = delete; + coreclr& operator=(_Inout_ coreclr &&) = delete; + + ~coreclr(); + + // See exported function 'coreclr_initialize' from coreclr library + HRESULT Initialize( + _In_ int propertyCount, + _In_reads_(propertyCount) const char **keys, + _In_reads_(propertyCount) const char **values, + _In_opt_z_ const char *appDomainName = nullptr); + + // See exported function 'coreclr_create_delegate' from coreclr library + HRESULT CreateDelegate( + _In_z_ const char *assembly, + _In_z_ const char *type, + _In_z_ const char *method, + _Out_ void **del); + +private: + AutoModule _hmod; + + void *_clrInst; + uint32_t _appDomainId; + + coreclr_initialize_ptr _initialize; + coreclr_create_delegate_ptr _create_delegate; + coreclr_shutdown_ptr _shutdown; +}; + +#endif /* _CORESHIM_H_ */ diff --git a/src/coreclr/hosts/coreshim/Exports.def b/src/coreclr/hosts/coreshim/Exports.def new file mode 100644 index 0000000000..fbdded0f69 --- /dev/null +++ b/src/coreclr/hosts/coreshim/Exports.def @@ -0,0 +1,3 @@ +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE
\ No newline at end of file |