From: tijoytom Date: Thu, 3 Sep 2015 20:58:14 +0000 (-0700) Subject: Add support for NativeCallableAttribute X-Git-Tag: accepted/tizen/base/20180629.140029~6384^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=1e62862b631fb64c6a9fae0a0084c0ae1d9fbeba;p=platform%2Fupstream%2Fcoreclr.git Add support for NativeCallableAttribute 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. --- diff --git a/src/inc/corcompile.h b/src/inc/corcompile.h index e3f73a7..4576990 100644 --- a/src/inc/corcompile.h +++ b/src/inc/corcompile.h @@ -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, diff --git a/src/mscorlib/model.xml b/src/mscorlib/model.xml index 4d169f8..570c555 100644 --- a/src/mscorlib/model.xml +++ b/src/mscorlib/model.xml @@ -6083,6 +6083,11 @@ + + + + + diff --git a/src/mscorlib/mscorlib.shared.sources.props b/src/mscorlib/mscorlib.shared.sources.props index a4b0511..ac27d3c 100644 --- a/src/mscorlib/mscorlib.shared.sources.props +++ b/src/mscorlib/mscorlib.shared.sources.props @@ -134,6 +134,7 @@ + diff --git a/src/mscorlib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs b/src/mscorlib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs new file mode 100644 index 0000000..5afd57e --- /dev/null +++ b/src/mscorlib/src/System/Runtime/InteropServices/NativeCallableAttribute.cs @@ -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 diff --git a/src/mscorlib/src/mscorlib.txt b/src/mscorlib/src/mscorlib.txt index 6a519b2..6435d1c 100644 --- a/src/mscorlib/src/mscorlib.txt +++ b/src/mscorlib/src/mscorlib.txt @@ -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. diff --git a/src/vm/amd64/UMThunkStub.asm b/src/vm/amd64/UMThunkStub.asm index 3170257..f3d3b91 100644 --- a/src/vm/amd64/UMThunkStub.asm +++ b/src/vm/amd64/UMThunkStub.asm @@ -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 diff --git a/src/vm/amd64/umthunkstub.S b/src/vm/amd64/umthunkstub.S index a649c2c..31d5a0a 100644 --- a/src/vm/amd64/umthunkstub.S +++ b/src/vm/amd64/umthunkstub.S @@ -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 diff --git a/src/vm/classnames.h b/src/vm/classnames.h index 755b2b8..03f4628 100644 --- a/src/vm/classnames.h +++ b/src/vm/classnames.h @@ -150,6 +150,7 @@ #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" diff --git a/src/vm/comdelegate.cpp b/src/vm/comdelegate.cpp index 66a19ac..a6c7e06 100644 --- a/src/vm/comdelegate.cpp +++ b/src/vm/comdelegate.cpp @@ -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) { diff --git a/src/vm/comdelegate.h b/src/vm/comdelegate.h index 1856216..cfb9afa 100644 --- a/src/vm/comdelegate.h +++ b/src/vm/comdelegate.h @@ -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); diff --git a/src/vm/compile.cpp b/src/vm/compile.cpp index e03905e..cf1d399 100644 --- a/src/vm/compile.cpp +++ b/src/vm/compile.cpp @@ -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) diff --git a/src/vm/compile.h b/src/vm/compile.h index a34bd93..034f751 100644 --- a/src/vm/compile.h +++ b/src/vm/compile.h @@ -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; diff --git a/src/vm/dllimportcallback.cpp b/src/vm/dllimportcallback.cpp index e3570ef..089e8a5 100644 --- a/src/vm/dllimportcallback.cpp +++ b/src/vm/dllimportcallback.cpp @@ -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) { diff --git a/src/vm/dllimportcallback.h b/src/vm/dllimportcallback.h index bc36056..9663a15 100644 --- a/src/vm/dllimportcallback.h +++ b/src/vm/dllimportcallback.h @@ -162,6 +162,12 @@ public: return (CorPinvokeMap)m_callConv; } + + VOID SetCallingConvention(const CorPinvokeMap callConv) + { + m_callConv = (UINT16)callConv; + } + #else PCODE GetExecStubEntryPoint(); #endif diff --git a/src/vm/jitinterface.cpp b/src/vm/jitinterface.cpp index b911d39..6c6692e 100644 --- a/src/vm/jitinterface.cpp +++ b/src/vm/jitinterface.cpp @@ -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)) diff --git a/src/vm/method.cpp b/src/vm/method.cpp index 2295502..fcd96d3 100644 --- a/src/vm/method.cpp +++ b/src/vm/method.cpp @@ -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; diff --git a/src/vm/method.hpp b/src/vm/method.hpp index f6ae190..0f283e5 100644 --- a/src/vm/method.hpp +++ b/src/vm/method.hpp @@ -720,6 +720,7 @@ public: void ComputeSuppressUnmanagedCodeAccessAttr(IMDInternalImport *pImport); BOOL HasSuppressUnmanagedCodeAccessAttr(); + BOOL HasNativeCallableAttribute(); #ifdef FEATURE_COMINTEROP inline DWORD IsComPlusCall() diff --git a/src/zap/zapimport.cpp b/src/zap/zapimport.cpp index 6252d52..890392f 100644 --- a/src/zap/zapimport.cpp +++ b/src/zap/zapimport.cpp @@ -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 index 0000000..254933f --- /dev/null +++ b/tests/src/Interop/NativeCallable/NativeCallableTest.cs @@ -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 invoker = CallbackMethod; + invoker(IntPtr.Zero, IntPtr.Zero); + } + catch (Exception) + { + + } + } + + public static void NegativeTest_NonBlittable() + { + // Try invoking method directly + try + { + Func 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(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 index 0000000..d2aa319 --- /dev/null +++ b/tests/src/Interop/NativeCallable/NativeCallableTest.csproj @@ -0,0 +1,41 @@ + + + + + Debug + AnyCPU + NativeCallableTest + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + Properties + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(ProgramFiles)\Common Files\microsoft shared\VSTT\11.0\UITestExtensionPackages + ..\..\ + 7a9bfb7d + $(DefineConstants);STATIC + true + + + + + + + + + False + + + + + + + + + + + + + + \ 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 index 0000000..58c0121 --- /dev/null +++ b/tests/src/Interop/NativeCallable/app.config @@ -0,0 +1,7 @@ + + + + + + + \ 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 index 0000000..b04d32f --- /dev/null +++ b/tests/src/Interop/NativeCallable/project.json @@ -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 index 0000000..861d3c3 --- /dev/null +++ b/tests/src/Interop/NativeCallable/project.lock.json @@ -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