Runtime changes for IJW Activation (#22636)
authorJeremy Koritzinsky <jkoritzinsky@gmail.com>
Thu, 21 Mar 2019 05:59:29 +0000 (22:59 -0700)
committerGitHub <noreply@github.com>
Thu, 21 Mar 2019 05:59:29 +0000 (22:59 -0700)
* Implement loading an assembly from an HMODULE on Windows.

* Use the native runtime to execute the main method so I don't have to replicate all of the startup behavior.

* If ijwhost is loaded, then call back into ijwhost to resolve tokens for vtable entries.

* Refactor our various component loaders (COM and IJW) and have IJW load components into separate ALCs when loaded from native.

* Move VTableFixups after DeliverSyncEvents in our incremental load. We need the module to be loaded to at least FILE_LOAD_DELIVER_EVENTS when resolving dependencies loaded via VTableFixups, otherwise they try to load into the default ALC.

* Only try to get ijwhost module handle on Windows.

* Use defined() instead of ifndef

* Fix off-Windows build break and fix unvalid comment.

* ComponentLoadContext->IsolatedComponentLoadContext

* Only build InMemoryAssemblyLoader when targeting windows.

* Add doc comments for IsolatedComponentLoadContext.

* Rename qcall.

* Add comment for boolean parameter.

* Add null check for managed ExecuteMainMethod entrypoint.

* Add comments in ceeload.cpp for the ijwhost method resolution.

* Add test verifying runtime callback to IJW host.

* Add test for InMemoryAssemblyLoader.LoadInMemoryAssembly.

* Fix x86 behavior rel. loading symbols by name and stdcall mangling.

* Remove exe entrypoints. The executable case for mixed-mode will go through the normal .NET Core app model (no special hosting support needed).

* Clean up whitespace

* Add test verifying M->N->M ALC switch behavior with IJW.

* Resolve the ijwhost module by probing the PE for the IJW assembly instead of hardcoding the name.

* Remove static caching in GetTokenGetterFromHostModule.

* Disable new IJW tests on Win7.

* PR Feedback.

* Cleanup extra copy of function.

* Use old-style environment checking in LoadIjwFromModuleHandle since we reference S.P.CL directly.

* Fix break in rebase

* Clean up fake mscoree code.

* Only validate that a CLR header exists. The OS verifies everything else.

* Make ijw host resolution code static to ceeload.cpp

* Use bracketed include for xplatform.h

* Update comment on _CorDllMain

* PR feedback.

* Remove .def file.

* PR feedback.

* Verify we export correctly on x86 and x64.

* Fix path to ijwhostmock for CopyConstructorMarshaler

* Disable IJW tests on arm32 nightly. Can't repro the dependency load failures. Fixes #23358

29 files changed:
src/System.Private.CoreLib/System.Private.CoreLib.csproj
src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivator.cs
src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs [new file with mode: 0644]
src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/IsolatedComponentLoadContext.cs [new file with mode: 0644]
src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs
src/vm/appdomain.cpp
src/vm/assemblynative.cpp
src/vm/assemblynative.hpp
src/vm/ceeload.cpp
src/vm/domainfile.cpp
src/vm/domainfile.h
src/vm/ecalllist.h
tests/issues.targets
tests/src/Interop/CMakeLists.txt
tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj
tests/src/Interop/IJW/FakeMscoree/mscoree.cpp [deleted file]
tests/src/Interop/IJW/FakeMscoree/mscoree.def [deleted file]
tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.cs [new file with mode: 0644]
tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj [new file with mode: 0644]
tests/src/Interop/IJW/IjwNativeCallingManagedDll/CMakeLists.txt [moved from tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt with 100% similarity]
tests/src/Interop/IJW/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp [moved from tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp with 53% similarity]
tests/src/Interop/IJW/IjwNativeDll/CMakeLists.txt [moved from tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/CMakeLists.txt with 100% similarity]
tests/src/Interop/IJW/IjwNativeDll/IjwNativeDll.cpp [moved from tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/IjwNativeDll.cpp with 100% similarity]
tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.cs [new file with mode: 0644]
tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.csproj [new file with mode: 0644]
tests/src/Interop/IJW/ManagedCallingNative/ManagedCallingNative.csproj
tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj
tests/src/Interop/IJW/ijwhostmock/CMakeLists.txt [moved from tests/src/Interop/IJW/FakeMscoree/CMakeLists.txt with 88% similarity]
tests/src/Interop/IJW/ijwhostmock/mscoree.cpp [new file with mode: 0644]

index 0cf6733..b052272 100644 (file)
   <!-- Sources -->
   <ItemGroup>
     <Compile Include="$(BclSourcesRoot)\Internal\Console.cs" />
+    <Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\IsolatedComponentLoadContext.cs" />
     <Compile Include="$(BclSourcesRoot)\Microsoft\Win32\UnsafeNativeMethods.cs" />
     <Compile Include="$(BclSourcesRoot)\System\__Canon.cs" />
     <Compile Include="$(BclSourcesRoot)\System\AppContext.CoreCLR.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Unix.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsWindows)' == 'true'">
+    <Compile Include="$(BclSourcesRoot)\Internal\Runtime\InteropServices\InMemoryAssemblyLoader.cs" />
     <Compile Include="$(BclSourcesRoot)\System\DateTime.Windows.cs" />
     <Compile Include="$(BclSourcesRoot)\Interop\Windows\OleAut32\Interop.VariantClear.cs" />
     <Compile Include="$(BclSourcesRoot)\System\ApplicationModel.Windows.cs" />
index 608d157..d1c6aa9 100644 (file)
@@ -215,7 +215,7 @@ $@"{nameof(GetClassFactoryForTypeInternal)} arguments:
             {
                 if (!s_AssemblyLoadContexts.TryGetValue(assemblyPath, out alc))
                 {
-                    alc = new ComServerLoadContext(assemblyPath);
+                    alc = new IsolatedComponentLoadContext(assemblyPath);
                     s_AssemblyLoadContexts.Add(assemblyPath, alc);
                 }
             }
@@ -223,38 +223,6 @@ $@"{nameof(GetClassFactoryForTypeInternal)} arguments:
             return alc;
         }
 
-        private class ComServerLoadContext : AssemblyLoadContext
-        {
-            private readonly AssemblyDependencyResolver _resolver;
-
-            public ComServerLoadContext(string comServerAssemblyPath)
-            {
-                _resolver = new AssemblyDependencyResolver(comServerAssemblyPath);
-            }
-
-            protected override Assembly Load(AssemblyName assemblyName)
-            {
-                string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
-                if (assemblyPath != null)
-                {
-                    return LoadFromAssemblyPath(assemblyPath);
-                }
-
-                return null;
-            }
-
-            protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
-            {
-                string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
-                if (libraryPath != null)
-                {
-                    return LoadUnmanagedDllFromPath(libraryPath);
-                }
-
-                return IntPtr.Zero;
-            }
-        }
-
         [ComVisible(true)]
         private class BasicClassFactory : IClassFactory
         {
diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs b/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs
new file mode 100644 (file)
index 0000000..097f47a
--- /dev/null
@@ -0,0 +1,32 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Runtime.Loader;
+
+namespace Internal.Runtime.InteropServices
+{
+    /// <summary>
+    /// This class enables the .NET IJW host to load an in-memory module as a .NET assembly
+    /// </summary>
+    public static class InMemoryAssemblyLoader
+    {
+        /// <summary>
+        /// Loads into an isolated AssemblyLoadContext an assembly that has already been loaded into memory by the OS loader as a native module.
+        /// </summary>
+        /// <param name="moduleHandle">The native module handle for the assembly.</param>
+        /// <param name="assemblyPath">The path to the assembly (as a pointer to a UTF-16 C string).</param>
+        public static unsafe void LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assemblyPath)
+        {
+            // We don't cache the ALCs here since each IJW assembly will call this method at most once
+            // (the load process rewrites the stubs that call here to call the actual methods they're supposed to)
+            AssemblyLoadContext context = new IsolatedComponentLoadContext(Marshal.PtrToStringUni(assemblyPath));
+            context.LoadFromInMemoryModule(moduleHandle);
+        }
+    }
+}
diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/IsolatedComponentLoadContext.cs b/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/IsolatedComponentLoadContext.cs
new file mode 100644 (file)
index 0000000..d78d7dd
--- /dev/null
@@ -0,0 +1,47 @@
+// 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.Reflection;
+using System.Runtime.Loader;
+
+namespace Internal.Runtime.InteropServices
+{
+    /// <summary>
+    /// An <see cref="IsolatedComponentLoadContext" /> is an AssemblyLoadContext that can be used to isolate components such as COM components
+    /// or IJW components loaded from native. It provides a load context that uses an <see cref="AssemblyDependencyResolver" /> to resolve the component's
+    /// dependencies within the ALC and not pollute the default ALC.
+    ///</summary>
+    internal class IsolatedComponentLoadContext : AssemblyLoadContext
+    {
+        private readonly AssemblyDependencyResolver _resolver;
+
+        public IsolatedComponentLoadContext(string componentAssemblyPath)
+        {
+            _resolver = new AssemblyDependencyResolver(componentAssemblyPath);
+        }
+
+        protected override Assembly Load(AssemblyName assemblyName)
+        {
+            string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
+            if (assemblyPath != null)
+            {
+                return LoadFromAssemblyPath(assemblyPath);
+            }
+
+            return null;
+        }
+
+        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
+        {
+            string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
+            if (libraryPath != null)
+            {
+                return LoadUnmanagedDllFromPath(libraryPath);
+            }
+
+            return IntPtr.Zero;
+        }
+    }
+}
index 60052d9..b36c382 100644 (file)
@@ -52,6 +52,34 @@ namespace System.Runtime.Loader
 
             return loadedAssembly;
         }
+        
+#if !FEATURE_PAL
+        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+        private static extern IntPtr LoadFromInMemoryModuleInternal(IntPtr ptrNativeAssemblyLoadContext, IntPtr hModule, ObjectHandleOnStack retAssembly);
+
+
+        /// <summary>
+        /// Load a module that has already been loaded into memory by the OS loader as a .NET assembly.
+        /// </summary>
+        internal Assembly LoadFromInMemoryModule(IntPtr moduleHandle)
+        {
+            if (moduleHandle == IntPtr.Zero)
+            {
+                throw new ArgumentNullException(nameof(moduleHandle));
+            }
+            lock (_unloadLock)
+            {
+                VerifyIsAlive();
+
+                RuntimeAssembly loadedAssembly = null;
+                LoadFromInMemoryModuleInternal(
+                    _nativeAssemblyLoadContext,
+                    moduleHandle,
+                    JitHelpers.GetObjectHandleOnStack(ref loadedAssembly));
+                return loadedAssembly;
+            }
+        }
+#endif
 
         // This method is invoked by the VM when using the host-provided assembly load context
         // implementation.
index 9362dd9..bcffb3b 100644 (file)
@@ -4223,8 +4223,8 @@ static const char *fileLoadLevelName[] =
     "LOADLIBRARY",                        // FILE_LOAD_LOADLIBRARY
     "POST_LOADLIBRARY",                   // FILE_LOAD_POST_LOADLIBRARY                
     "EAGER_FIXUPS",                       // FILE_LOAD_EAGER_FIXUPS
-    "VTABLE FIXUPS",                      // FILE_LOAD_VTABLE_FIXUPS
     "DELIVER_EVENTS",                     // FILE_LOAD_DELIVER_EVENTS
+    "VTABLE FIXUPS",                      // FILE_LOAD_VTABLE_FIXUPS
     "LOADED",                             // FILE_LOADED
     "VERIFY_EXECUTION",                   // FILE_LOAD_VERIFY_EXECUTION
     "ACTIVE",                             // FILE_ACTIVE
index 30ee95b..ed2ce66 100644 (file)
@@ -363,6 +363,42 @@ void QCALLTYPE AssemblyNative::LoadFromStream(INT_PTR ptrNativeAssemblyLoadConte
     END_QCALL;
 }
 
+#ifndef FEATURE_PAL
+/*static */
+void QCALLTYPE AssemblyNative::LoadFromInMemoryModule(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR hModule, QCall::ObjectHandleOnStack retLoadedAssembly)
+{
+    QCALL_CONTRACT;
+    
+    BEGIN_QCALL;
+    
+    // Ensure that the invariants are in place
+    _ASSERTE(ptrNativeAssemblyLoadContext != NULL);
+    _ASSERTE(hModule != NULL);
+
+    PEImageHolder pILImage(PEImage::LoadImage((HMODULE)hModule));
+    
+    // Need to verify that this is a valid CLR assembly. 
+    if (!pILImage->HasCorHeader())
+        ThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL);
+    
+    // Get the binder context in which the assembly will be loaded
+    ICLRPrivBinder *pBinderContext = reinterpret_cast<ICLRPrivBinder*>(ptrNativeAssemblyLoadContext);
+    
+    // Pass the in memory module as IL in an attempt to bind and load it
+    Assembly* pLoadedAssembly = AssemblyNative::LoadFromPEImage(pBinderContext, pILImage, NULL); 
+    {
+        GCX_COOP();
+        retLoadedAssembly.Set(pLoadedAssembly->GetExposedObject());
+    }
+
+    LOG((LF_CLASSLOADER, 
+            LL_INFO100, 
+            "\tLoaded assembly from pre-loaded native module\n"));
+
+    END_QCALL;
+}
+#endif
+
 void QCALLTYPE AssemblyNative::GetLocation(QCall::AssemblyHandle pAssembly, QCall::StringHandleOnStack retString)
 {
     QCALL_CONTRACT;
index d506b6e..13db261 100644 (file)
@@ -120,6 +120,9 @@ public:
     static void QCALLTYPE LoadFromPath(INT_PTR ptrNativeAssemblyLoadContext, LPCWSTR pwzILPath, LPCWSTR pwzNIPath, QCall::ObjectHandleOnStack retLoadedAssembly);
     static INT_PTR QCALLTYPE InternalLoadUnmanagedDllFromPath(LPCWSTR unmanagedLibraryPath);
     static void QCALLTYPE LoadFromStream(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR ptrAssemblyArray, INT32 cbAssemblyArrayLength, INT_PTR ptrSymbolArray, INT32 cbSymbolArrayLength, QCall::ObjectHandleOnStack retLoadedAssembly);
+#ifndef FEATURE_PAL
+    static void QCALLTYPE LoadFromInMemoryModule(INT_PTR ptrNativeAssemblyLoadContext, INT_PTR hModule, QCall::ObjectHandleOnStack retLoadedAssembly);
+#endif
     static Assembly* LoadFromPEImage(ICLRPrivBinder* pBinderContext, PEImage *pILImage, PEImage *pNIImage);
     static INT_PTR QCALLTYPE GetLoadContextForAssembly(QCall::AssemblyHandle pAssembly);
 
index 1fec2c2..b9cf4a7 100644 (file)
@@ -6482,6 +6482,59 @@ void Module::NotifyDebuggerUnload(AppDomain *pDomain)
 }
 
 #if !defined(CROSSGEN_COMPILE)
+using GetTokenForVTableEntry_t = mdToken(STDMETHODCALLTYPE*)(HMODULE module, BYTE**ppVTEntry);
+
+static HMODULE GetIJWHostForModule(Module* module)
+{
+#if !defined(FEATURE_PAL)
+    PEDecoder* pe = module->GetFile()->GetLoadedIL();
+
+    BYTE* baseAddress = (BYTE*)module->GetFile()->GetIJWBase();
+
+    IMAGE_IMPORT_DESCRIPTOR* importDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)pe->GetDirectoryData(pe->GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT));
+
+    for(; importDescriptor->Characteristics != 0; importDescriptor++)
+    {
+        IMAGE_THUNK_DATA* importNameTable = (IMAGE_THUNK_DATA*)pe->GetRvaData(importDescriptor->OriginalFirstThunk);
+
+        IMAGE_THUNK_DATA* importAddressTable = (IMAGE_THUNK_DATA*)pe->GetRvaData(importDescriptor->FirstThunk);
+
+        for (int thunkIndex = 0; importNameTable[thunkIndex].u1.AddressOfData != 0; thunkIndex++)
+        {
+            // The most significant bit will be set if the entry points to an ordinal.
+            if ((importNameTable[thunkIndex].u1.Ordinal & (1LL << (sizeof(importNameTable[thunkIndex].u1.Ordinal) * CHAR_BIT - 1))) == 0)
+            {
+                IMAGE_IMPORT_BY_NAME* nameImport = (IMAGE_IMPORT_BY_NAME*)(baseAddress + importNameTable[thunkIndex].u1.AddressOfData);
+                if (strcmp("_CorDllMain", nameImport->Name) == 0)
+                {
+                    HMODULE ijwHost;
+
+                    if (WszGetModuleHandleEx(
+                        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+                        (LPCWSTR)importAddressTable[thunkIndex].u1.Function,
+                        &ijwHost))
+                    {
+                        return ijwHost;
+                    }
+
+                }
+            }
+        }
+    }
+#endif
+    return nullptr;
+}
+
+static GetTokenForVTableEntry_t GetTokenGetterFromHostModule(HMODULE ijwHost)
+{
+    if (ijwHost != nullptr)
+    {
+        return (GetTokenForVTableEntry_t)GetProcAddress(ijwHost, "GetTokenForVTableEntry");
+    }
+
+    return nullptr;
+}
+
 //=================================================================================
 mdToken GetTokenForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry)
 {
@@ -6504,7 +6557,6 @@ void SetTargetForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry, BYTE *pTarget)
     DWORD oldProtect;
     if (!ClrVirtualProtect(ppVTEntry, sizeof(BYTE*), PAGE_READWRITE, &oldProtect))
     {
-        
         // This is very bad.  We are not going to be able to update header.
         _ASSERTE(!"SetTargetForVTableEntry(): VirtualProtect() changing IJW thunk vtable to R/W failed.\n");
         ThrowLastError();
@@ -6548,6 +6600,18 @@ void Module::FixupVTables()
         return;
     }
 
+    // Try getting a callback to the IJW host if it is loaded.
+    // The IJW host substitutes in special shims in the vtfixup table
+    // so if it is loaded, we need to query it for the tokens that were in the slots.
+    // If it is not loaded, then we know that the vtfixup table entries are tokens,
+    // so we can resolve them ourselves.
+    GetTokenForVTableEntry_t GetTokenForVTableEntryCallback = GetTokenGetterFromHostModule(GetIJWHostForModule(this));
+
+    if (GetTokenForVTableEntryCallback == nullptr)
+    {
+        GetTokenForVTableEntryCallback = GetTokenForVTableEntry;
+    }
+
     HINSTANCE hInstThis = GetFile()->GetIJWBase();
 
     // <REVISIT_TODO>@todo: workaround!</REVISIT_TODO>
@@ -6648,7 +6712,7 @@ void Module::FixupVTables()
                     {
                         if (pData->IsMethodFixedUp(iFixup, iMethod))
                             continue;
-                        mdToken mdTok = GetTokenForVTableEntry(hInstThis, (BYTE **)(pPointers + iMethod));
+                        mdToken mdTok = GetTokenForVTableEntryCallback(hInstThis, (BYTE**)(pPointers + iMethod));
                         CONSISTENCY_CHECK(mdTok != mdTokenNil);
                         rgMethodsToLoad[iCurMethod++].token = mdTok;
                     }
index 0fd9c81..5c89767 100644 (file)
@@ -555,14 +555,14 @@ BOOL DomainFile::DoIncrementalLoad(FileLoadLevel level)
         EagerFixups();
         break;
 
-    case FILE_LOAD_VTABLE_FIXUPS:
-        VtableFixups();
-        break;
-
     case FILE_LOAD_DELIVER_EVENTS:
         DeliverSyncEvents();
         break;
 
+    case FILE_LOAD_VTABLE_FIXUPS:
+        VtableFixups();
+        break;
+
     case FILE_LOADED:
         FinishLoad();
         break;
index d6917b1..ddc0384 100644 (file)
@@ -45,8 +45,8 @@ enum FileLoadLevel
     FILE_LOAD_LOADLIBRARY,
     FILE_LOAD_POST_LOADLIBRARY,
     FILE_LOAD_EAGER_FIXUPS,
-    FILE_LOAD_VTABLE_FIXUPS,
     FILE_LOAD_DELIVER_EVENTS,
+    FILE_LOAD_VTABLE_FIXUPS,
     FILE_LOADED,                    // Loaded by not yet active
     FILE_LOAD_VERIFY_EXECUTION,
     FILE_ACTIVE                     // Fully active (constructors run & security checked)
index 7302bb4..c184633 100644 (file)
@@ -520,6 +520,9 @@ FCFuncStart(gAssemblyLoadContextFuncs)
     QCFuncElement("LoadFromPath", AssemblyNative::LoadFromPath)
     QCFuncElement("InternalLoadUnmanagedDllFromPath", AssemblyNative::InternalLoadUnmanagedDllFromPath)
     QCFuncElement("LoadFromStream", AssemblyNative::LoadFromStream)
+#ifndef FEATURE_PAL
+    QCFuncElement("LoadFromInMemoryModuleInternal", AssemblyNative::LoadFromInMemoryModule)
+#endif
     QCFuncElement("GetLoadContextForAssembly", AssemblyNative::GetLoadContextForAssembly)
     FCFuncElement("GetLoadedAssemblies", AppDomainNative::GetLoadedAssemblies)
 #if defined(FEATURE_MULTICOREJIT)
index a206167..73a2884 100644 (file)
         <ExcludeList Include="$(XunitTestBinBase)/JIT/Directed/arglist/vararg/*">
             <Issue>Needs triage</Issue>
         </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler/*">
+            <Issue>Needs triage</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded/*">
+            <Issue>Needs triage</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle/*">
+            <Issue>Needs triage</Issue>
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/ManagedCallingNative/ManagedCallingNative/*">
             <Issue>Needs triage</Issue>
         </ExcludeList>
index 32e4b77..acf4c84 100644 (file)
@@ -83,12 +83,12 @@ if(WIN32)
     add_subdirectory(COM/NativeClients/Primitives)
     add_subdirectory(COM/NativeClients/Licensing)
     add_subdirectory(COM/NativeClients/DefaultInterfaces)
-    add_subdirectory(IJW/FakeMscoree)
 
     # IJW isn't supported on ARM64
     if(NOT CLR_CMAKE_PLATFORM_ARCH_ARM64)
-        add_subdirectory(IJW/ManagedCallingNative/IjwNativeDll)
-        add_subdirectory(IJW/NativeCallingManaged/IjwNativeCallingManagedDll)
+        add_subdirectory(IJW/ijwhostmock)
+        add_subdirectory(IJW/IjwNativeDll)
+        add_subdirectory(IJW/IjwNativeCallingManagedDll)
         add_subdirectory(IJW/CopyConstructorMarshaler)
     endif()
 endif(WIN32)
index 34aad53..b1d38a9 100644 (file)
@@ -38,7 +38,7 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="CMakeLists.txt" />
-    <ProjectReference Include="../FakeMscoree/CMakeLists.txt" />
+    <ProjectReference Include="../ijwhostmock/CMakeLists.txt" />
   </ItemGroup>
   <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
 </Project>
diff --git a/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp b/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp
deleted file mode 100644 (file)
index 99319ec..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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 <windows.h>
-
-#ifndef DLLEXPORT
-#ifdef _MSC_VER
-#define DLLEXPORT __declspec(dllexport)
-#else
-#define DLLEXPORT __attribute__((visibility("default")))
-#endif
-#endif
-
-// Entrypoint jumped to by IJW dlls when their dllmain is called
-extern "C" DLLEXPORT BOOL WINAPI _CorDllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved)
-{
-    return TRUE;
-}
diff --git a/tests/src/Interop/IJW/FakeMscoree/mscoree.def b/tests/src/Interop/IJW/FakeMscoree/mscoree.def
deleted file mode 100644 (file)
index 9279b03..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-LIBRARY MSCOREE
-EXPORTS
-    _CorDllMain
\ No newline at end of file
diff --git a/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.cs b/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.cs
new file mode 100644 (file)
index 0000000..cfc5a9a
--- /dev/null
@@ -0,0 +1,53 @@
+// 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;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using TestLibrary;
+
+namespace FixupCallsHostWhenLoaded
+{
+    class FixupCallsHostWhenLoaded
+    {
+        static int Main(string[] args)
+        {
+            // Disable running on Windows 7 until IJW activation work is complete.
+            if(Environment.OSVersion.Platform != PlatformID.Win32NT || TestLibrary.Utilities.IsWindows7)
+            {
+                return 100;
+            }
+
+            try
+            {
+                // Load a fake mscoree.dll to avoid starting desktop
+                IntPtr ijwHost = NativeLibrary.Load(Path.Combine(Environment.CurrentDirectory, "mscoree.dll"));
+
+                WasModuleVTableQueriedDelegate wasModuleVTableQueried = Marshal.GetDelegateForFunctionPointer<WasModuleVTableQueriedDelegate>(NativeLibrary.GetExport(ijwHost, "WasModuleVTableQueried"));
+
+                // Load IJW via reflection
+                Assembly.Load("IjwNativeDll");
+
+                IntPtr ijwModuleHandle = GetModuleHandle("IjwNativeDll.dll");
+                
+                Assert.AreNotEqual(IntPtr.Zero, ijwModuleHandle);
+                Assert.IsTrue(wasModuleVTableQueried(ijwModuleHandle));
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e);
+                return 101;
+            }
+
+            return 100;
+        }
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        delegate bool WasModuleVTableQueriedDelegate(IntPtr handle);
+
+        [DllImport("kernel32.dll")]
+        static extern IntPtr GetModuleHandle(string lpModuleName);
+    }
+}
diff --git a/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj b/tests/src/Interop/IJW/FixupCallsHostWhenLoaded/FixupCallsHostWhenLoaded.csproj
new file mode 100644 (file)
index 0000000..20dc72f
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Interop.settings.targets))\Interop.settings.targets" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>FixupCallsHostWhenLoaded</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{49D1D482-E783-4CA9-B6BA-A9714BF81036}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    
+    <!-- IJW is Windows-only -->
+    <!-- Test unsupported outside of windows -->
+    <TestUnsupportedOutsideWindows>true</TestUnsupportedOutsideWindows>
+    <DisableProjectBuild Condition="'$(TargetsUnix)' == 'true'">true</DisableProjectBuild>
+
+    <!-- IJW is not supported on ARM64 -->
+    <DisableProjectBuild Condition="'$(Platform)' == 'arm64'">true</DisableProjectBuild>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <PropertyGroup>
+    <CopyDebugCRTDllsToOutputDirectory>true</CopyDebugCRTDllsToOutputDirectory>
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="FixupCallsHostWhenLoaded.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="../IjwNativeDll/CMakeLists.txt" />
+    <ProjectReference Include="../ijwhostmock/CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
@@ -1,12 +1,10 @@
 // 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 "platformdefines.h"
 
 #pragma managed
-int ManagedCallee()
-{
-    return 100;
-}
+int ManagedCallee();
 
 #pragma unmanaged
 int NativeFunction()
@@ -14,12 +12,34 @@ int NativeFunction()
     return ManagedCallee();
 }
 
+extern "C" DLL_EXPORT int __cdecl NativeEntryPoint()
+{
+    return NativeFunction();
+}
+
 #pragma managed
 public ref class TestClass
 {
+private:
+    static int s_valueToReturn = 100;
 public:
     int ManagedEntryPoint()
     {
         return NativeFunction();
     }
+
+    static void ChangeReturnedValue(int i)
+    {
+        s_valueToReturn = i;
+    }
+
+    static int GetReturnValue()
+    {
+        return s_valueToReturn;
+    }
 };
+
+int ManagedCallee()
+{
+    return TestClass::GetReturnValue();
+}
diff --git a/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.cs b/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.cs
new file mode 100644 (file)
index 0000000..e44d534
--- /dev/null
@@ -0,0 +1,88 @@
+// 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;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Internal.Runtime.InteropServices;
+using TestLibrary;
+
+using Console = Internal.Console;
+
+namespace LoadIjwFromModuleHandle
+{
+    class LoadIjwFromModuleHandle
+    {
+        unsafe static int Main(string[] args)
+        {
+            // Disable running on Windows 7 until IJW activation work is complete.
+            if(Environment.OSVersion.Platform != PlatformID.Win32NT || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1))
+            {
+                return 100;
+            }
+
+            try
+            {
+                HostPolicyMock.Initialize(Environment.CurrentDirectory, null);
+
+                // Load our fake mscoree to prevent desktop from loading.
+                NativeLibrary.Load(Path.Combine(Environment.CurrentDirectory, "mscoree.dll"));
+
+                Console.WriteLine("Verify that we can load an IJW assembly from native code.");
+                string ijwModulePath = Path.Combine(Environment.CurrentDirectory, "IjwNativeCallingManagedDll.dll");
+                IntPtr ijwNativeHandle = NativeLibrary.Load(ijwModulePath);
+
+                using (HostPolicyMock.Mock_corehost_resolve_component_dependencies(
+                    0,
+                    ijwModulePath,
+                    string.Empty,
+                    string.Empty))
+                fixed (char* path = ijwModulePath)
+                {
+                    InMemoryAssemblyLoader.LoadInMemoryAssembly(ijwNativeHandle, (IntPtr)path);
+                }
+                
+                NativeEntryPointDelegate nativeEntryPoint = Marshal.GetDelegateForFunctionPointer<NativeEntryPointDelegate>(NativeLibrary.GetExport(ijwNativeHandle, "NativeEntryPoint"));
+
+                Assert.AreEqual(100, nativeEntryPoint());
+
+                Console.WriteLine("Test calls from managed to native to managed when an IJW assembly was first loaded via native.");
+
+                Assembly ijwAssemblyManaged = Assembly.Load("IjwNativeCallingManagedDll");
+                Type testType = ijwAssemblyManaged.GetType("TestClass");
+                object testInstance = Activator.CreateInstance(testType);
+                MethodInfo testMethod = testType.GetMethod("ManagedEntryPoint");
+
+                Assert.AreEqual(100, (int)testMethod.Invoke(testInstance, null));
+
+                MethodInfo changeReturnedValueMethod = testType.GetMethod("ChangeReturnedValue");
+                MethodInfo getReturnValueMethod = testType.GetMethod("GetReturnValue");
+
+                int newValue = 42;
+                changeReturnedValueMethod.Invoke(null, new object[] { newValue });
+
+                Assert.AreEqual(newValue, (int)getReturnValueMethod.Invoke(null, null));
+                
+                // Native images are only loaded into memory once. As a result, the stubs in the vtfixup table
+                // will always point to JIT stubs that exist in the first ALC that the module was loaded into.
+                // As a result, if an IJW module is loaded into two different ALCs, or if the module is
+                // first loaded via a native call and then loaded via the managed loader, the call stack can change ALCs when
+                // jumping from managed->native->managed code within the IJW module.
+                Assert.AreEqual(100, (int)testMethod.Invoke(testInstance, null));
+                return 100;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.ToString());
+
+                return 101;
+            }
+        }
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        delegate int NativeEntryPointDelegate();
+
+    }
+}
diff --git a/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.csproj b/tests/src/Interop/IJW/LoadIjwFromModuleHandle/LoadIjwFromModuleHandle.csproj
new file mode 100644 (file)
index 0000000..6228f2c
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>LoadIjwFromModuleHandle</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{8B76A001-5654-4F11-A80B-EF12644EAD3D}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <ReferenceSystemPrivateCoreLib>true</ReferenceSystemPrivateCoreLib>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+
+    <!-- IJW is Windows-only -->
+    <!-- Test unsupported outside of windows -->
+    <TestUnsupportedOutsideWindows>true</TestUnsupportedOutsideWindows>
+    <DisableProjectBuild Condition="'$(TargetsUnix)' == 'true'">true</DisableProjectBuild>
+
+    <!-- IJW is not supported on ARM64 -->
+    <DisableProjectBuild Condition="'$(Platform)' == 'arm64'">true</DisableProjectBuild>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <PropertyGroup>
+    <CopyDebugCRTDllsToOutputDirectory>true</CopyDebugCRTDllsToOutputDirectory>
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="LoadIjwFromModuleHandle.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="../IjwNativeCallingManagedDll/CMakeLists.txt" />
+    <ProjectReference Include="../ijwhostmock/CMakeLists.txt" />
+    <ProjectReference Include="../../../Common/hostpolicymock/CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Interop.settings.targets))\Interop.settings.targets" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
index 55b6321..c8c1cca 100644 (file)
@@ -37,8 +37,8 @@
     <Compile Include="ManagedCallingNative.cs" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="IjwNativeDll/CMakeLists.txt" />
-    <ProjectReference Include="../FakeMscoree/CMakeLists.txt" />
+    <ProjectReference Include="../IjwNativeDll/CMakeLists.txt" />
+    <ProjectReference Include="../ijwhostmock/CMakeLists.txt" />
     <ProjectReference Include="../../../Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
   </ItemGroup>
   <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
index 9decb48..d40837d 100644 (file)
@@ -37,8 +37,8 @@
     <Compile Include="NativeCallingManaged.cs" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="IjwNativeCallingManagedDll/CMakeLists.txt" />
-    <ProjectReference Include="../FakeMscoree/CMakeLists.txt" />
+    <ProjectReference Include="../IjwNativeCallingManagedDll/CMakeLists.txt" />
+    <ProjectReference Include="../ijwhostmock/CMakeLists.txt" />
     <ProjectReference Include="../../../Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
   </ItemGroup>
   <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
@@ -1,7 +1,7 @@
 cmake_minimum_required (VERSION 2.6)
 project (mscoree)
 include_directories( ${INC_PLATFORM_DIR} )
-set(SOURCES mscoree.cpp mscoree.def)
+set(SOURCES mscoree.cpp)
 
 # add the shared library
 add_library (mscoree SHARED ${SOURCES})
diff --git a/tests/src/Interop/IJW/ijwhostmock/mscoree.cpp b/tests/src/Interop/IJW/ijwhostmock/mscoree.cpp
new file mode 100644 (file)
index 0000000..7c398cb
--- /dev/null
@@ -0,0 +1,34 @@
+// 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 <windows.h>
+#include <xplatform.h>
+#include <set>
+
+std::set<HINSTANCE> g_modulesQueried = {};
+
+#if defined _X86_
+// We need to use a double-underscore here because the VC linker drops the first underscore
+// to help people who are exporting cdecl functions to easily export the right thing.
+#pragma comment(linker, "/export:__CorDllMain=__CorDllMain@12")
+#pragma comment(linker, "/export:GetTokenForVTableEntry=_GetTokenForVTableEntry@8")
+#endif
+
+// Entry-point that coreclr looks for.
+extern "C" DLL_EXPORT INT32 STDMETHODCALLTYPE GetTokenForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry)
+{
+    g_modulesQueried.emplace(hInst);
+    return (INT32)(UINT_PTR)*ppVTEntry;
+}
+
+extern "C" DLL_EXPORT BOOL __cdecl WasModuleVTableQueried(HINSTANCE hInst)
+{
+    return g_modulesQueried.find(hInst) != g_modulesQueried.end() ? TRUE : FALSE;
+}
+
+// Entrypoint jumped to by IJW dlls when their dllmain is called
+extern "C" DLL_EXPORT BOOL WINAPI _CorDllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved)
+{
+    return TRUE;
+}