Add support for NativeCallableAttribute
authortijoytom <tijoytom.com>
Thu, 3 Sep 2015 20:58:14 +0000 (13:58 -0700)
committertijoytom <tijoytom.com>
Fri, 18 Sep 2015 17:00:14 +0000 (10:00 -0700)
Apply [NativeCallable] attribute to a managed method and then it can be
called from native code.Typical use would be passing a managed method as
callback to native, now it can be done by wrapping the method in a
delegate or directly using Marshal.GetFunctionPointerForDelegate.This's
fine as long as we make sure that delegate is not garbage
collected.[NativeCallable] introduce another way, where you can directly
load the function pointer of a native callable method and use it as
callback.This feature cannot be directly used from C#,but can be very
useful in dynamic code generation scenarios where you want a callback to
be passed to native.

Here's an example of how it can be used.

public static class NativeMethods {
 [DllImport("user32.dll")]
 public static extern int EnumWindows(IntPtr enumProc, IntPtr lParam);
}

//Method attributed with NativeCallable
[NativeCallable]
public static int CallbackMethod(IntPtr hWnd, IntPtr lParam){ return 1; }

Now you can generate the below IL to load native callable function pointer
( LDFTN) and then pass it a native method.
.locals init ([0] native int ptr)
nop
ldftn      int32 CallbackMethod(native int,native int)
stloc.0
ldloc.0
ldsfld     native int System.IntPtr::Zero
call       bool NativeMethods::EnumWindows(native int,native int)
pop
ret

Encoding native callable methods as  ENCODE_METHOD_NATIVECALLABLE_HANDLE
so that we don't have to check for the custom attribute at runtime to
decode the method.Also fixing the remaining code review comments.

Adding runtime check to prevent Native Callable methods from being used as
calli target with an ldftn. Also adding some negative test cases , they
are disabled for now since the tests failfast and msbuild report it as
failure.

23 files changed:
src/inc/corcompile.h
src/mscorlib/model.xml
src/mscorlib/mscorlib.shared.sources.props
src/mscorlib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs [new file with mode: 0644]
src/mscorlib/src/mscorlib.txt
src/vm/amd64/UMThunkStub.asm
src/vm/amd64/umthunkstub.S
src/vm/classnames.h
src/vm/comdelegate.cpp
src/vm/comdelegate.h
src/vm/compile.cpp
src/vm/compile.h
src/vm/dllimportcallback.cpp
src/vm/dllimportcallback.h
src/vm/jitinterface.cpp
src/vm/method.cpp
src/vm/method.hpp
src/zap/zapimport.cpp
tests/src/Interop/NativeCallable/NativeCallableTest.cs [new file with mode: 0644]
tests/src/Interop/NativeCallable/NativeCallableTest.csproj [new file with mode: 0644]
tests/src/Interop/NativeCallable/app.config [new file with mode: 0644]
tests/src/Interop/NativeCallable/project.json [new file with mode: 0644]
tests/src/Interop/NativeCallable/project.lock.json [new file with mode: 0644]

index e3f73a7..4576990 100644 (file)
@@ -724,6 +724,7 @@ enum CORCOMPILE_FIXUP_BLOB_KIND
     ENCODE_CHECK_FIELD_OFFSET,
 
     ENCODE_DELEGATE_CTOR,
+    ENCODE_METHOD_NATIVE_ENTRY,                     /* NativeCallable method token */
 
     ENCODE_MODULE_HANDLE      = 0x50,               /* Module token */
     ENCODE_STATIC_FIELD_ADDRESS,                    /* For accessing a static field */
@@ -1892,6 +1893,9 @@ class ICorCompileInfo
     // to 1 on the clone. The buffer has to be large enough to hold the stub object and the code
     virtual HRESULT GetStubClone(void *pStub, BYTE *pBuffer, DWORD dwBufferSize) = 0;
 
+    // true if the method has [NativeCallableAttribute]
+    virtual BOOL IsNativeCallableMethod(CORINFO_METHOD_HANDLE handle) = 0;
+
 #ifdef CLR_STANDALONE_BINDER
     virtual HRESULT GetMetadataRvaInfo(
             OUT DWORD   *pFirstMethodRvaOffset,
index 4d169f8..570c555 100644 (file)
       <Member MemberType="Field" Name="Sequential" />
       <Member MemberType="Field" Name="value__" />
     </Type>
+    <Type Name="System.Runtime.InteropServices.NativeCallableAttribute"> 
+      <Member Name="#ctor" />
+      <Member MemberType="Field" Name="CallingConvention" /> <!-- EE -->
+      <Member MemberType="Field" Name="EntryPoint" /> <!-- EE -->
+    </Type>
     <Type Name="System.Runtime.InteropServices.Marshal">
       <Member MemberType="Field" Name="SystemDefaultCharSize" />
       <Member Name="AddRef(System.IntPtr)" />
index a4b0511..ac27d3c 100644 (file)
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\InvalidComObjectException.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeArrayRankMismatchException.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeArrayTypeMismatchException.cs" />
+    <InteropSources Condition="'$(FeatureCoreClr)'=='true'"  Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeCallableAttribute.cs" />
     <InteropSources Condition="'$(FeatureCominterop)' != 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NonPortable.cs" />
     <InteropSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\DispatchWrapper.cs" />
     <InteropSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ExtensibleClassFactory.cs" />
diff --git a/src/mscorlib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs b/src/mscorlib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs
new file mode 100644 (file)
index 0000000..5afd57e
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+/*=============================================================================
+** Any method marked with NativeCallableAttribute can be directly called from 
+** native code.The function token can be loaded to a local variable using LDFTN and
+** passed as a callback to native method.
+=============================================================================*/
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices
+{
+
+    [AttributeUsage(AttributeTargets.Method)]
+    public sealed class NativeCallableAttribute : Attribute
+    {
+        public NativeCallableAttribute()
+        {
+        }
+        // Optional. If omitted , compiler will choose one for you.
+        public CallingConvention CallingConvention;
+        // Optional. If omitted, then the method is native callable, but no EAT is emitted.
+        public string EntryPoint;
+    }
+}
\ No newline at end of file
index 6a519b2..6435d1c 100644 (file)
@@ -1439,6 +1439,11 @@ NotSupported_CollectibleCOM = COM Interop is not supported for collectible types
 NotSupported_CollectibleAssemblyResolve = Resolving to a collectible assembly is not supported.
 NotSupported_CollectibleBoundNonCollectible = A non-collectible assembly may not reference a collectible assembly.
 NotSupported_CollectibleDelegateMarshal = Delegate marshaling for types within collectible assemblies is not supported.
+NotSupported_NonStaticMethod = Non-static methods with NativeCallableAttribute are not supported.
+NotSupported_NativeCallableTarget = Methods with NativeCallableAttribute cannot be used as delegate target.
+NotSupported_GenericMethod = Generic methods with NativeCallableAttribute are not supported.
+NotSupported_NonBlittableTypes = Non-blittable parameter types are not supported for NativeCallable methods.
+
 #if FEATURE_WINDOWSPHONE
 NotSupported_UserDllImport = DllImport cannot be used on user-defined methods.
 NotSupported_UserCOM = COM Interop is not supported for user-defined types.
index 3170257..f3d3b91 100644 (file)
@@ -35,7 +35,7 @@ extern gfHostConfig:dword
 extern NDirect__IsHostHookEnabled:proc
 endif
 extern UMThunkStubRareDisableWorker:proc
-
+extern ReversePInvokeBadTransition:proc
 
 ;
 ; METHODDESC_REGISTER: UMEntryThunk*
@@ -188,6 +188,10 @@ HaveThread:
 
         mov             r12, rax                ; r12 <- Thread*
 
+        ;FailFast if a native callable method invoked via ldftn and calli.
+        cmp             dword ptr [r12 + OFFSETOF__Thread__m_fPreemptiveGCDisabled], 1
+        jz              InvalidTransition
+
         ;
         ; disable preemptive GC
         ;
@@ -279,6 +283,10 @@ DoThreadSetup:
         
         jmp             HaveThread
         
+InvalidTransition:
+        ; ReversePInvokeBadTransition will failfast
+        call            ReversePInvokeBadTransition
+
 DoTrapReturningThreadsTHROW:
 
         mov             [rbp + UMThunkStubAMD64_ARGUMENTS_STACK_HOME_OFFSET +  0h], rcx
index a649c2c..31d5a0a 100644 (file)
@@ -85,6 +85,10 @@ LOCAL_LABEL(HaveThread):
 
         mov             r12, rax                // r12 <- Thread*
 
+        //FailFast if a native callable method invoked via ldftn and calli.
+        cmp             dword ptr [r12 + OFFSETOF__Thread__m_fPreemptiveGCDisabled], 1
+        jz              LOCAL_LABEL(InvalidTransition)
+
         //
         // disable preemptive GC
         //
@@ -153,6 +157,10 @@ LOCAL_LABEL(DoThreadSetup):
         call            C_FUNC(CreateThreadBlockThrow)
         jmp             LOCAL_LABEL(HaveThread)
         
+LOCAL_LABEL(InvalidTransition):
+        //No arguments to setup , ReversePInvokeBadTransition will failfast
+        call            C_FUNC(ReversePInvokeBadTransition)
+
 LOCAL_LABEL(DoTrapReturningThreadsTHROW):
         mov             rdi, r12                                                                        // Thread* pThread
         mov             rsi, [rbp - UMThunkStubAMD64_RBP_OFFSET + UMThunkStubAMD64_METHODDESC_OFFSET]   // UMEntryThunk* pUMEntry
index 755b2b8..03f4628 100644 (file)
 #define g_CompilerServicesUnsafeValueTypeAttribute "System.Runtime.CompilerServices.UnsafeValueTypeAttribute"
 #define g_UnmanagedFunctionPointerAttribute "System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute"
 #define g_DefaultDllImportSearchPathsAttribute "System.Runtime.InteropServices.DefaultDllImportSearchPathsAttribute"
+#define g_NativeCallableAttribute "System.Runtime.InteropServices.NativeCallableAttribute"
 
 #define g_CompilerServicesTypeDependencyAttribute "System.Runtime.CompilerServices.TypeDependencyAttribute"
 
index 66a19ac..a6c7e06 100644 (file)
@@ -29,6 +29,8 @@
 #include "security.h"
 #include "virtualcallstub.h"
 #include "callingconvention.h"
+#include "customattribute.h"
+#include "../md/compiler/custattr.h"
 #ifdef FEATURE_COMINTEROP
 #include "comcallablewrapper.h"
 #endif // FEATURE_COMINTEROP
@@ -1065,6 +1067,74 @@ BOOL COMDelegate::IsMethodAllowedToSinkReversePInvoke(MethodDesc *pMD)
 }
 #endif // FEATURE_CORECLR
 
+// Marshals a managed method to an unmanaged callback provided the 
+// managed method is static and it's parameters require no marshalling.
+PCODE COMDelegate::ConvertToCallback(MethodDesc* pMD)
+{
+    CONTRACTL
+    {
+        THROWS;
+    GC_TRIGGERS;
+    INJECT_FAULT(COMPlusThrowOM());
+    }
+    CONTRACTL_END;
+
+    PCODE pCode = NULL;
+
+    // only static methods are allowed
+    if (!pMD->IsStatic())
+        COMPlusThrow(kNotSupportedException, W("NotSupported_NonStaticMethod"));
+
+    // no generic methods
+    if (pMD->IsGenericMethodDefinition())
+        COMPlusThrow(kNotSupportedException, W("NotSupported_GenericMethod"));
+
+    // Arguments 
+    if (NDirect::MarshalingRequired(pMD, pMD->GetSig(), pMD->GetModule()))
+        COMPlusThrow(kNotSupportedException, W("NotSupported_NonBlittableTypes"));
+
+    // Get UMEntryThunk from appdomain thunkcache cache.
+    UMEntryThunk *pUMEntryThunk = GetAppDomain()->GetUMEntryThunkCache()->GetUMEntryThunk(pMD);
+
+#ifdef _TARGET_X86_
+
+    // System.Runtime.InteropServices.NativeCallableAttribute
+    BYTE* pData = NULL;
+    LONG cData = 0;
+    CorPinvokeMap callConv = (CorPinvokeMap)0;
+
+    HRESULT hr = pMD->GetMDImport()->GetCustomAttributeByName(pMD->GetMemberDef(), g_NativeCallableAttribute, (const VOID **)(&pData), (ULONG *)&cData);
+    IfFailThrow(hr);
+
+    if (cData > 0)
+    {
+        CustomAttributeParser ca(pData, cData);
+        // NativeCallable has two optional named arguments CallingConvention and EntryPoint.
+        CaNamedArg namedArgs[2];
+        CaTypeCtor caType(SERIALIZATION_TYPE_STRING);
+        // First, the void constructor.
+        IfFailThrow(ParseKnownCaArgs(ca, NULL, 0));
+
+        // Now the optional named properties
+        namedArgs[0].InitI4FieldEnum("CallingConvention", "System.Runtime.InteropServices.CallingConvention", (ULONG)callConv);
+        namedArgs[1].Init("EntryPoint", SERIALIZATION_TYPE_STRING, caType);
+        IfFailThrow(ParseKnownCaNamedArgs(ca, namedArgs, lengthof(namedArgs)));
+
+        callConv = (CorPinvokeMap)(namedArgs[0].val.u4 << 8);
+        // Let UMThunkMarshalInfo choose the default if calling convension not definied.
+        if (namedArgs[0].val.type.tag != SERIALIZATION_TYPE_UNDEFINED)
+        {
+            UMThunkMarshInfo* pUMThunkMarshalInfo = pUMEntryThunk->GetUMThunkMarshInfo();
+            pUMThunkMarshalInfo->SetCallingConvention(callConv);
+        }
+}
+#endif  //_TARGET_X86_
+
+    pCode = (PCODE)pUMEntryThunk->GetCode();
+    _ASSERTE(pCode != NULL);
+    return pCode;
+}
+
 // Marshals a delegate to a unmanaged callback.
 LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegateObj)
 {
@@ -3188,6 +3258,13 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT
     // that has the _methodBase field filled in with the LoaderAllocator of the collectible assembly
     // associated with the instantiation.
     BOOL fMaybeCollectibleAndStatic = FALSE;
+   
+    // Do not allow static methods with [NativeCallableAttribute] to be a delegate target.
+    // A native callable method is special and allowing it to be delegate target will destabilize the runtime.
+    if (pTargetMethod->HasNativeCallableAttribute())
+    {
+        COMPlusThrow(kNotSupportedException, W("NotSupported_NativeCallableTarget"));
+    }
 
     if (isStatic)
     {
index 1856216..cfb9afa 100644 (file)
@@ -79,6 +79,10 @@ public:
 
     // Marshals a delegate to a unmanaged callback.
     static LPVOID ConvertToCallback(OBJECTREF pDelegate);
+    
+    // Marshals a managed method to an unmanaged callback , provided the method is static and uses only 
+    // blittable parameter types.
+    static PCODE ConvertToCallback(MethodDesc* pMD);
 
     // Marshals an unmanaged callback to Delegate
     static OBJECTREF ConvertToDelegate(LPVOID pCallback, MethodTable* pMT);
index e03905e..cf1d399 100644 (file)
@@ -1795,13 +1795,13 @@ HRESULT CEECompileInfo::GetMethodDef(CORINFO_METHOD_HANDLE methodHandle,
 // Depends on what things are persisted by CEEPreloader
 
 BOOL CEEPreloader::CanEmbedFunctionEntryPoint(
-        CORINFO_METHOD_HANDLE   methodHandle,
-        CORINFO_METHOD_HANDLE   contextHandle, /* = NULL */
-        CORINFO_ACCESS_FLAGS    accessFlags /*=CORINFO_ACCESS_ANY*/)
+    CORINFO_METHOD_HANDLE   methodHandle,
+    CORINFO_METHOD_HANDLE   contextHandle, /* = NULL */
+    CORINFO_ACCESS_FLAGS    accessFlags /*=CORINFO_ACCESS_ANY*/)
 {
     STANDARD_VM_CONTRACT;
 
-    MethodDesc * pMethod  = GetMethod(methodHandle);
+    MethodDesc * pMethod = GetMethod(methodHandle);
     MethodDesc * pContext = GetMethod(contextHandle);
 
     // IsRemotingInterceptedViaVirtualDispatch is a rather special case.
@@ -1818,12 +1818,19 @@ BOOL CEEPreloader::CanEmbedFunctionEntryPoint(
     // don't save these stubs.  Unlike most other remoting stubs these ones 
     // are NOT inserted by DoPrestub.
     //
-    if (((accessFlags & CORINFO_ACCESS_THIS) == 0)       &&
-        (pMethod->IsRemotingInterceptedViaVirtualDispatch())    )
+    if (((accessFlags & CORINFO_ACCESS_THIS) == 0) &&
+        (pMethod->IsRemotingInterceptedViaVirtualDispatch()))
     {
         return FALSE;
     }
 
+    // Methods with native callable attribute are special , since 
+    // they are used as LDFTN targets.Native Callable methods
+    // uses the same code path as reverse pinvoke and embedding them
+    // in an ngen image require saving the reverse pinvoke stubs.
+    if (pMethod->HasNativeCallableAttribute())
+        return FALSE;
+
     return TRUE;
 }
 
@@ -1866,6 +1873,12 @@ BOOL CEEPreloader::DoesMethodNeedRestoringBeforePrestubIsRun(
     return FALSE;
 }
 
+BOOL CEECompileInfo::IsNativeCallableMethod(CORINFO_METHOD_HANDLE handle)
+{
+    MethodDesc * pMethod = GetMethod(handle);
+    return pMethod->HasNativeCallableAttribute();
+}
+
 BOOL CEEPreloader::CanSkipDependencyActivation(CORINFO_METHOD_HANDLE   context,
                                                CORINFO_MODULE_HANDLE   moduleFrom,
                                                CORINFO_MODULE_HANDLE   moduleTo)
index a34bd93..034f751 100644 (file)
@@ -330,6 +330,8 @@ class CEECompileInfo : public ICorCompileInfo
     BOOL IsEmptyString(mdString token,
                        CORINFO_MODULE_HANDLE module);
 
+    BOOL IsNativeCallableMethod(CORINFO_METHOD_HANDLE handle);
+
     BOOL IsCachingOfInliningHintsEnabled()
     {
         return m_fCachingOfInliningHintsEnabled;
index e3570ef..089e8a5 100644 (file)
@@ -1088,6 +1088,19 @@ UMEntryThunk *UMEntryThunkCache::GetUMEntryThunk(MethodDesc *pMD)
     RETURN pThunk;
 }
 
+// FailFast if a native callable method invoked directly from managed code.
+// UMThunkStub.asm check the mode and call this function to failfast.
+extern "C" VOID STDCALL ReversePInvokeBadTransition()
+{
+    STATIC_CONTRACT_THROWS;
+    STATIC_CONTRACT_GC_TRIGGERS;
+    // Fail 
+    EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(
+                                             COR_E_EXECUTIONENGINE,
+                                             W("Invalid Program: attempted to call a NativeCallable method from runtime-typesafe code.")
+                                            );
+}
+
 // Disable from a place that is calling into managed code via a UMEntryThunk.
 extern "C" VOID STDCALL UMThunkStubRareDisableWorker(Thread *pThread, UMEntryThunk *pUMEntryThunk)
 {
index bc36056..9663a15 100644 (file)
@@ -162,6 +162,12 @@ public:
 
         return (CorPinvokeMap)m_callConv;
     }
+
+    VOID SetCallingConvention(const CorPinvokeMap callConv)
+    {
+        m_callConv = (UINT16)callConv;
+    }
+
 #else
     PCODE GetExecStubEntryPoint();
 #endif
index b911d39..6c6692e 100644 (file)
@@ -9014,8 +9014,19 @@ void CEEInfo::getFunctionFixedEntryPoint(CORINFO_METHOD_HANDLE   ftn,
     MethodDesc * pMD = GetMethod(ftn);
 
     pResult->accessType = IAT_VALUE;
-    pResult->addr = (void *) pMD->GetMultiCallableAddrOfCode();
 
+
+#ifndef CROSSGEN_COMPILE
+    // If LDFTN target has [NativeCallable] attribute , then create a UMEntryThunk.
+    if (pMD->HasNativeCallableAttribute())
+    {
+        pResult->addr = (void*)COMDelegate::ConvertToCallback(pMD);
+    }
+    else
+#endif //CROSSGEN_COMPILE
+    {
+        pResult->addr = (void *)pMD->GetMultiCallableAddrOfCode();
+    }
     EE_TO_JIT_TRANSITION();
 }
 
@@ -13335,6 +13346,9 @@ BOOL LoadDynamicInfoEntry(Module *currentModule,
         }
         break;
 
+        // ENCODE_METHOD_NATIVECALLABLE_HANDLE is same as ENCODE_METHOD_ENTRY_DEF_TOKEN 
+        // except for AddrOfCode
+    case ENCODE_METHOD_NATIVE_ENTRY:
     case ENCODE_METHOD_ENTRY_DEF_TOKEN:
         {
             mdToken MethodDef = TokenFromRid(CorSigUncompressData(pBlob), mdtMethodDef);
@@ -13390,7 +13404,14 @@ BOOL LoadDynamicInfoEntry(Module *currentModule,
             }
 
         MethodEntry:
-            result = pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY);
+            if (kind == ENCODE_METHOD_NATIVE_ENTRY)
+            {
+                result = COMDelegate::ConvertToCallback(pMD);
+            }
+            else
+            {
+                result = pMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY);
+            }
 
         #ifndef _TARGET_ARM_
             if (CORCOMPILE_IS_PCODE_TAGGED(result))
index 2295502..fcd96d3 100644 (file)
@@ -5374,6 +5374,33 @@ void MethodDesc::ComputeSuppressUnmanagedCodeAccessAttr(IMDInternalImport *pImpo
 }
 
 //*******************************************************************************
+BOOL MethodDesc::HasNativeCallableAttribute()
+{
+
+    CONTRACTL
+    {
+        THROWS;
+        GC_NOTRIGGER;
+        FORBID_FAULT;
+    }
+    CONTRACTL_END;
+
+// enable only for amd64 now, other platforms are not tested.
+#if defined(_TARGET_AMD64_) 
+
+#ifdef FEATURE_CORECLR
+    HRESULT hr = GetMDImport()->GetCustomAttributeByName(GetMemberDef(),
+        g_NativeCallableAttribute,
+        NULL,
+        NULL);
+    return (hr == S_OK);
+#endif //FEATURE_CORECLR
+
+#endif //_TARGET_AMD64_
+    return FALSE;
+}
+
+//*******************************************************************************
 BOOL MethodDesc::HasSuppressUnmanagedCodeAccessAttr()
 {
     LIMITED_METHOD_CONTRACT;
index f6ae190..0f283e5 100644 (file)
@@ -720,6 +720,7 @@ public:
 
     void ComputeSuppressUnmanagedCodeAccessAttr(IMDInternalImport *pImport);
     BOOL HasSuppressUnmanagedCodeAccessAttr();
+    BOOL HasNativeCallableAttribute();
 
 #ifdef FEATURE_COMINTEROP 
     inline DWORD IsComPlusCall()
index 6252d52..890392f 100644 (file)
@@ -1287,10 +1287,18 @@ public:
         if (token != mdTokenNil)
         {
             _ASSERTE(TypeFromToken(token) == mdtMethodDef || TypeFromToken(token) == mdtMemberRef);
-
-            pTable->EncodeModule(
-                (TypeFromToken(token) == mdtMethodDef) ? ENCODE_METHOD_ENTRY_DEF_TOKEN : ENCODE_METHOD_ENTRY_REF_TOKEN,
-                referencingModule, pSigBuilder);
+            
+            // It's a NativeCallable method , then encode it as ENCODE_METHOD_NATIVE_ENTRY
+            if (pTable->GetCompileInfo()->IsNativeCallableMethod(handle))
+            {
+                pTable->EncodeModule(ENCODE_METHOD_NATIVE_ENTRY, referencingModule, pSigBuilder);
+            }
+            else
+            {
+                pTable->EncodeModule(
+                    (TypeFromToken(token) == mdtMethodDef) ? ENCODE_METHOD_ENTRY_DEF_TOKEN : ENCODE_METHOD_ENTRY_REF_TOKEN,
+                    referencingModule, pSigBuilder);
+            }
 
             pSigBuilder->AppendData(RidFromToken(token));
         }
diff --git a/tests/src/Interop/NativeCallable/NativeCallableTest.cs b/tests/src/Interop/NativeCallable/NativeCallableTest.cs
new file mode 100644 (file)
index 0000000..254933f
--- /dev/null
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+public class Program
+{
+    public static class NativeMethods
+    {
+        [DllImport("user32.dll")]
+        public static extern int EnumWindows(IntPtr enumProc, IntPtr lParam);
+    }
+
+    private delegate void NativeMethodInvoker();
+    static EventWaitHandle waitHandle = new AutoResetEvent(false);
+
+    public static int Main()
+    {
+        //NegativeTest_NonBlittable();
+        TestNativeCallableValid();
+        //NegativeTest_ViaDelegate();
+        //NegativeTest_ViaLdftn();
+        return 100;
+    }
+
+    public static void TestNativeCallableValid()
+    {
+        /*
+           void TestNativeCallable()
+           {
+                   .locals init ([0] native int ptr)
+                   IL_0000:  nop  
+                   IL_0002:  ldftn      int32 CallbackMethod(native int,native int)
+
+                   IL_0012:  stloc.0
+                   IL_0013:  ldloc.0
+                   IL_0014:  ldsfld     native int [mscorlib]System.IntPtr::Zero
+                   IL_0019:  call       bool NativeMethods::EnumWindows(native int,
+                                                                                      native int)
+                   IL_001e:  pop
+                   IL_001f:  ret
+             }
+           */
+        DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallable", null, null, typeof(Program).Module);
+        ILGenerator il = testNativeCallable.GetILGenerator();
+        il.DeclareLocal(typeof(IntPtr));
+        il.Emit(OpCodes.Nop);
+        il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod("CallbackMethod"));
+        il.Emit(OpCodes.Stloc_0);
+        il.Emit(OpCodes.Ldloc_0);
+        il.Emit(OpCodes.Ldsfld, typeof(IntPtr).GetField("Zero"));
+        il.Emit(OpCodes.Call, typeof(NativeMethods).GetMethod("EnumWindows"));
+        il.Emit(OpCodes.Pop);
+        il.Emit(OpCodes.Ret);
+        NativeMethodInvoker testNativeMethod = (NativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(NativeMethodInvoker));
+        testNativeMethod();
+    }
+
+    public static void NegativeTest_ViaDelegate()
+    {
+        // Try invoking method directly 
+        try
+        {
+            Func<IntPtr, IntPtr, int> invoker = CallbackMethod;
+            invoker(IntPtr.Zero, IntPtr.Zero);
+        }
+        catch (Exception)
+        {
+
+        }
+    }
+
+    public static void NegativeTest_NonBlittable()
+    {
+        // Try invoking method directly 
+        try
+        {
+            Func<bool, int> invoker = CallbackMethodNonBlitabble;
+            invoker(true);
+        }
+        catch (Exception)
+        {
+            Console.WriteLine(":bla");
+        }
+    }
+
+
+    public static void NegativeTest_ViaLdftn()
+    {
+        /*
+           .locals init (native int V_0)
+           IL_0000:  nop
+           IL_0001:  ldftn      void ConsoleApplication1.Program::callback(int32)
+           IL_0007:  stloc.0
+           IL_0008:  ldc.i4.s   12
+           IL_000a:  ldloc.0
+           IL_000b:  calli      void(int32)
+           IL_0010:  nop
+           IL_0011:  ret
+       */
+        DynamicMethod testNativeCallable = new DynamicMethod("TestNativeCallableLdftn", null, null, typeof(Program).Module);
+        ILGenerator il = testNativeCallable.GetILGenerator();
+        il.DeclareLocal(typeof(IntPtr));
+        il.Emit(OpCodes.Nop);
+        il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod("LdftnCallback"));
+        il.Emit(OpCodes.Stloc_0);
+        il.Emit(OpCodes.Ldc_I4,12);
+        il.Emit(OpCodes.Ldloc_0);
+
+        SignatureHelper sig =  SignatureHelper.GetMethodSigHelper(typeof(Program).Module, null, new Type[] { typeof(int) });
+        sig.AddArgument(typeof(int));
+
+        // il.EmitCalli is not available  and the below is not correct
+        il.Emit(OpCodes.Calli,sig);
+        il.Emit(OpCodes.Nop);
+        il.Emit(OpCodes.Ret);
+
+        NativeMethodInvoker testNativeMethod = (NativeMethodInvoker)testNativeCallable.CreateDelegate(typeof(NativeMethodInvoker));
+        testNativeMethod();
+
+    }
+
+    #region callbacks
+    [NativeCallable]
+    public static void LdftnCallback(int val)
+    {
+    }
+
+    [NativeCallable]
+    public static int CallbackMethod(IntPtr hWnd, IntPtr lParam)
+    {
+        waitHandle.Set();
+        return 1;
+    }
+
+    [NativeCallable]
+    public static int CallbackMethodGeneric<T>(IntPtr hWnd, IntPtr lParam)
+    {
+        return 1;
+    }
+
+    [NativeCallable]
+    public static int CallbackMethodNonBlitabble(bool x1)
+    {
+        return 1;
+    }
+    #endregion //callbacks
+
+}
\ No newline at end of file
diff --git a/tests/src/Interop/NativeCallable/NativeCallableTest.csproj b/tests/src/Interop/NativeCallable/NativeCallableTest.csproj
new file mode 100644 (file)
index 0000000..d2aa319
--- /dev/null
@@ -0,0 +1,41 @@
+<?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>NativeCallableTest</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\11.0\UITestExtensionPackages</ReferencePath>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <NuGetPackageImportStamp>7a9bfb7d</NuGetPackageImportStamp>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+    <ReferenceLocalMscorlib>true</ReferenceLocalMscorlib>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="project.json" />
+    <None Include="app.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="NativeCallableTest.cs" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
\ No newline at end of file
diff --git a/tests/src/Interop/NativeCallable/app.config b/tests/src/Interop/NativeCallable/app.config
new file mode 100644 (file)
index 0000000..58c0121
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+    </assemblyBinding>
+  </runtime>
+</configuration>
\ No newline at end of file
diff --git a/tests/src/Interop/NativeCallable/project.json b/tests/src/Interop/NativeCallable/project.json
new file mode 100644 (file)
index 0000000..b04d32f
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "dependencies": {
+  },
+  "frameworks": {
+    "dnxcore50": {}
+  }
+}
diff --git a/tests/src/Interop/NativeCallable/project.lock.json b/tests/src/Interop/NativeCallable/project.lock.json
new file mode 100644 (file)
index 0000000..861d3c3
--- /dev/null
@@ -0,0 +1,12 @@
+{
+  "locked": true,
+  "version": -9996,
+  "targets": {
+    "DNXCore,Version=v5.0": {}
+  },
+  "libraries": {},
+  "projectFileDependencyGroups": {
+    "": [],
+    "DNXCore,Version=v5.0": []
+  }
+}
\ No newline at end of file