Handle 'unmanaged' calling convention value (#39030)
authorElinor Fung <47805090+elinor-fung@users.noreply.github.com>
Wed, 15 Jul 2020 17:13:45 +0000 (10:13 -0700)
committerGitHub <noreply@github.com>
Wed, 15 Jul 2020 17:13:45 +0000 (10:13 -0700)
src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs
src/coreclr/src/tools/Common/TypeSystem/Common/MethodDesc.cs
src/coreclr/src/vm/dllimport.cpp
src/coreclr/src/vm/jitinterface.cpp
src/coreclr/src/vm/siginfo.cpp
src/coreclr/src/vm/siginfo.hpp
src/tests/baseservices/callconvs/CallFunctionPointers.il
src/tests/baseservices/callconvs/TestCallingConventions.cs

index 2be9978..88bc3a5 100644 (file)
@@ -510,6 +510,49 @@ namespace Internal.JitInterface
             }
         }
 
+        private bool TryGetUnmanagedCallingConventionFromModOpt(MethodSignature signature, out CorInfoCallConv callConv)
+        {
+            callConv = CorInfoCallConv.CORINFO_CALLCONV_UNMANAGED;
+            if (!signature.HasEmbeddedSignatureData || signature.GetEmbeddedSignatureData() == null)
+                return false;
+
+            foreach (EmbeddedSignatureData data in signature.GetEmbeddedSignatureData())
+            {
+                if (data.kind != EmbeddedSignatureDataKind.OptionalCustomModifier)
+                    continue;
+
+                // We only care about the modifiers for the return type. These will be at the start of
+                // the signature, so will be first in the array of embedded signature data.
+                if (data.index != MethodSignature.IndexOfCustomModifiersOnReturnType)
+                    break;
+
+                if (!(data.type is DefType defType))
+                    continue;
+
+                if (defType.Namespace != "System.Runtime.CompilerServices")
+                    continue;
+
+                // Take the first recognized calling convention in metadata.
+                switch (defType.Name)
+                {
+                    case "CallConvCdecl":
+                        callConv = CorInfoCallConv.CORINFO_CALLCONV_C;
+                        return true;
+                    case "CallConvStdcall":
+                        callConv = CorInfoCallConv.CORINFO_CALLCONV_STDCALL;
+                        return true;
+                    case "CallConvFastcall":
+                        callConv = CorInfoCallConv.CORINFO_CALLCONV_FASTCALL;
+                        return true;
+                    case "CallConvThiscall":
+                        callConv = CorInfoCallConv.CORINFO_CALLCONV_THISCALL;
+                        return true;
+                }
+            }
+
+            return false;
+        }
+
         private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* sig)
         {
             sig->callConv = (CorInfoCallConv)(signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask);
@@ -520,6 +563,22 @@ namespace Internal.JitInterface
 
             if (!signature.IsStatic) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_HASTHIS;
 
+            // Unmanaged calling convention indicates modopt should be read
+            if (sig->callConv == CorInfoCallConv.CORINFO_CALLCONV_UNMANAGED)
+            {
+                if (TryGetUnmanagedCallingConventionFromModOpt(signature, out CorInfoCallConv callConvMaybe))
+                {
+                    sig->callConv = callConvMaybe;
+                }
+                else
+                {
+                    // Use platform default
+                    sig->callConv = _compilation.TypeSystemContext.Target.IsWindows
+                        ? CorInfoCallConv.CORINFO_CALLCONV_STDCALL
+                        : CorInfoCallConv.CORINFO_CALLCONV_C;
+                }
+            }
+
             TypeDesc returnType = signature.ReturnType;
 
             CorInfoType corInfoRetType = asCorInfoType(signature.ReturnType, &sig->retTypeClass);
index 66143d0..0545857 100644 (file)
@@ -48,6 +48,9 @@ namespace Internal.TypeSystem
         internal TypeDesc[] _parameters;
         internal EmbeddedSignatureData[] _embeddedSignatureData;
 
+        // Value of <see cref="EmbeddedSignatureData.index" /> for any custom modifiers on the return type
+        public const string IndexOfCustomModifiersOnReturnType = "0.1.1.1";
+
         public MethodSignature(MethodSignatureFlags flags, int genericParameterCount, TypeDesc returnType, TypeDesc[] parameters, EmbeddedSignatureData[] embeddedSignatureData = null)
         {
             _flags = flags;
index 1debfb2..05813cd 100644 (file)
@@ -2923,6 +2923,57 @@ inline CorPinvokeMap GetDefaultCallConv(BOOL bIsVarArg)
 #endif // !TARGET_UNIX
 }
 
+namespace
+{
+    bool TryConvertCallConvValueToPInvokeCallConv(_In_ BYTE callConv, _Out_ CorPinvokeMap *pPinvokeMapOut)
+    {
+        LIMITED_METHOD_CONTRACT;
+
+        switch (callConv)
+        {
+        case IMAGE_CEE_CS_CALLCONV_C:
+            *pPinvokeMapOut = pmCallConvCdecl;
+            return true;
+        case IMAGE_CEE_CS_CALLCONV_STDCALL:
+            *pPinvokeMapOut = pmCallConvStdcall;
+            return true;
+        case IMAGE_CEE_CS_CALLCONV_THISCALL:
+            *pPinvokeMapOut = pmCallConvThiscall;
+            return true;
+        case IMAGE_CEE_CS_CALLCONV_FASTCALL:
+            *pPinvokeMapOut = pmCallConvFastcall;
+            return true;
+        }
+
+        return false;
+    }
+
+    HRESULT GetUnmanagedPInvokeCallingConvention(
+        _In_ Module *pModule,
+        _In_ PCCOR_SIGNATURE pSig,
+        _In_ ULONG cSig,
+        _Out_ CorPinvokeMap *pPinvokeMapOut)
+    {
+        CONTRACTL
+        {
+            NOTHROW;
+            GC_NOTRIGGER;
+            MODE_ANY;
+        }
+        CONTRACTL_END
+
+        CorUnmanagedCallingConvention callConvMaybe;
+        HRESULT hr = MetaSig::TryGetUnmanagedCallingConventionFromModOpt(pModule, pSig, cSig, &callConvMaybe);
+        if (hr != S_OK)
+            return hr;
+        
+        if (!TryConvertCallConvValueToPInvokeCallConv(callConvMaybe, pPinvokeMapOut))
+            return S_FALSE;
+
+        return hr;
+    }
+}
+
 void PInvokeStaticSigInfo::InitCallConv(CorPinvokeMap callConv, BOOL bIsVarArg)
 {
     CONTRACTL
@@ -2938,9 +2989,8 @@ void PInvokeStaticSigInfo::InitCallConv(CorPinvokeMap callConv, BOOL bIsVarArg)
         callConv = GetDefaultCallConv(bIsVarArg);
 
     CorPinvokeMap sigCallConv = (CorPinvokeMap)0;
-    BOOL fSuccess = MetaSig::GetUnmanagedCallingConvention(m_pModule, m_sig.GetRawSig(), m_sig.GetRawSigLen(), &sigCallConv);
-
-    if (!fSuccess)
+    HRESULT hr = GetUnmanagedPInvokeCallingConvention(m_pModule, m_sig.GetRawSig(), m_sig.GetRawSigLen(), &sigCallConv);
+    if (FAILED(hr))
     {
         SetError(IDS_EE_NDIRECT_BADNATL); //Bad metadata format
     }
@@ -6778,26 +6828,25 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD)
         dwStubFlags |= NDIRECTSTUB_FL_UNMANAGED_CALLI;
 
         // need to convert the CALLI signature to stub signature with managed calling convention
-        switch (MetaSig::GetCallingConvention(pVASigCookie->pModule, pVASigCookie->signature))
+        BYTE callConv = MetaSig::GetCallingConvention(pVASigCookie->pModule, signature);
+
+        // Unmanaged calling convention indicates modopt should be read
+        if (callConv == IMAGE_CEE_CS_CALLCONV_UNMANAGED)
         {
-            case IMAGE_CEE_CS_CALLCONV_C:
-                unmgdCallConv = pmCallConvCdecl;
-                break;
-            case IMAGE_CEE_CS_CALLCONV_STDCALL:
-                unmgdCallConv = pmCallConvStdcall;
-                break;
-            case IMAGE_CEE_CS_CALLCONV_THISCALL:
-                unmgdCallConv = pmCallConvThiscall;
-                break;
-            case IMAGE_CEE_CS_CALLCONV_FASTCALL:
-                unmgdCallConv = pmCallConvFastcall;
-                break;
-            case IMAGE_CEE_CS_CALLCONV_UNMANAGED:
-                COMPlusThrow(kNotImplementedException);
-            default:
-                COMPlusThrow(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV);
+            CorUnmanagedCallingConvention callConvMaybe;
+            if (S_OK == MetaSig::TryGetUnmanagedCallingConventionFromModOpt(pVASigCookie->pModule, signature.GetRawSig(), signature.GetRawSigLen(), &callConvMaybe))
+            {
+                callConv = callConvMaybe;
+            }
+            else
+            {
+                callConv = MetaSig::GetDefaultUnmanagedCallingConvention();
+            }
         }
 
+        if (!TryConvertCallConvValueToPInvokeCallConv(callConv, &unmgdCallConv))
+            COMPlusThrow(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV);
+
         LoaderHeap *pHeap = pVASigCookie->pModule->GetLoaderAllocator()->GetHighFrequencyHeap();
         PCOR_SIGNATURE new_sig = (PCOR_SIGNATURE)(void *)pHeap->AllocMem(S_SIZE_T(signature.GetRawSigLen()));
         CopyMemory(new_sig, signature.GetRawSig(), signature.GetRawSigLen());
index 99073be..7c41498 100644 (file)
@@ -465,10 +465,10 @@ CEEInfo::ConvToJitSig(
         SigTypeContext::InitTypeContext(contextType, &typeContext);
     }
 
-    _ASSERTE(CORINFO_CALLCONV_DEFAULT == (CorInfoCallConv) IMAGE_CEE_CS_CALLCONV_DEFAULT);
-    _ASSERTE(CORINFO_CALLCONV_VARARG == (CorInfoCallConv) IMAGE_CEE_CS_CALLCONV_VARARG);
-    _ASSERTE(CORINFO_CALLCONV_MASK == (CorInfoCallConv) IMAGE_CEE_CS_CALLCONV_MASK);
-    _ASSERTE(CORINFO_CALLCONV_HASTHIS == (CorInfoCallConv) IMAGE_CEE_CS_CALLCONV_HASTHIS);
+    static_assert_no_msg(CORINFO_CALLCONV_DEFAULT == (CorInfoCallConv) IMAGE_CEE_CS_CALLCONV_DEFAULT);
+    static_assert_no_msg(CORINFO_CALLCONV_VARARG == (CorInfoCallConv) IMAGE_CEE_CS_CALLCONV_VARARG);
+    static_assert_no_msg(CORINFO_CALLCONV_MASK == (CorInfoCallConv) IMAGE_CEE_CS_CALLCONV_MASK);
+    static_assert_no_msg(CORINFO_CALLCONV_HASTHIS == (CorInfoCallConv) IMAGE_CEE_CS_CALLCONV_HASTHIS);
 
     TypeHandle typeHnd = TypeHandle();
 
@@ -506,6 +506,25 @@ CEEInfo::ConvToJitSig(
         }
 #endif // defined(TARGET_UNIX) || defined(TARGET_ARM)
 
+        // Unmanaged calling convention indicates modopt should be read
+        if (sigRet->callConv == CORINFO_CALLCONV_UNMANAGED)
+        {
+            static_assert_no_msg(CORINFO_CALLCONV_C == (CorInfoCallConv)IMAGE_CEE_UNMANAGED_CALLCONV_C);
+            static_assert_no_msg(CORINFO_CALLCONV_STDCALL == (CorInfoCallConv)IMAGE_CEE_UNMANAGED_CALLCONV_STDCALL);
+            static_assert_no_msg(CORINFO_CALLCONV_THISCALL == (CorInfoCallConv)IMAGE_CEE_UNMANAGED_CALLCONV_THISCALL);
+            static_assert_no_msg(CORINFO_CALLCONV_FASTCALL == (CorInfoCallConv)IMAGE_CEE_UNMANAGED_CALLCONV_FASTCALL);
+
+            CorUnmanagedCallingConvention callConvMaybe;
+            if (S_OK == MetaSig::TryGetUnmanagedCallingConventionFromModOpt(module, pSig, cbSig, &callConvMaybe))
+            {
+                sigRet->callConv = (CorInfoCallConv)callConvMaybe;
+            }
+            else
+            {
+                sigRet->callConv = (CorInfoCallConv)MetaSig::GetDefaultUnmanagedCallingConvention();
+            }
+        }
+
         // Skip number of type arguments
         if (sigRet->callConv & IMAGE_CEE_CS_CALLCONV_GENERIC)
           IfFailThrow(sig.GetData(NULL));
@@ -566,10 +585,7 @@ CEEInfo::ConvToJitSig(
         sigRet->args = (CORINFO_ARG_LIST_HANDLE)sig.GetPtr();
     }
 
-    if (sigRet->callConv == CORINFO_CALLCONV_UNMANAGED)
-    {
-        COMPlusThrowHR(E_NOTIMPL);
-    }
+    _ASSERTE(sigRet->callConv != CORINFO_CALLCONV_UNMANAGED);
 
     // Set computed flags
     sigRet->flags = sigRetFlags;
index 1148d53..ba2906f 100644 (file)
@@ -5219,16 +5219,55 @@ BOOL MetaSig::IsReturnTypeVoid() const
 
 #ifndef DACCESS_COMPILE
 
+namespace
+{
+    HRESULT GetNameOfTypeRefOrDef(
+        _In_ const Module *pModule,
+        _In_ mdToken token,
+        _Out_ LPCSTR *namespaceOut,
+        _Out_ LPCSTR *nameOut)
+    {
+        CONTRACTL
+        {
+            NOTHROW;
+            GC_NOTRIGGER;
+            FORBID_FAULT;
+            MODE_ANY;
+        }
+        CONTRACTL_END
+
+        IMDInternalImport *pInternalImport = pModule->GetMDImport();
+        if (TypeFromToken(token) == mdtTypeDef)
+        {
+            HRESULT hr = pInternalImport->GetNameOfTypeDef(token, nameOut, namespaceOut);
+            if (FAILED(hr))
+                return hr;
+        }
+        else if (TypeFromToken(token) == mdtTypeRef)
+        {
+            HRESULT hr = pInternalImport->GetNameOfTypeRef(token, namespaceOut, nameOut);
+            if (FAILED(hr))
+                return hr;
+        }
+        else
+        {
+            return E_INVALIDARG;
+        }
+
+        return S_OK;
+    }
+}
+
 //----------------------------------------------------------
 // Returns the unmanaged calling convention.
 //----------------------------------------------------------
 /*static*/
-BOOL
-MetaSig::GetUnmanagedCallingConvention(
-    Module *        pModule,
-    PCCOR_SIGNATURE pSig,
-    ULONG           cSig,
-    CorPinvokeMap * pPinvokeMapOut)
+HRESULT
+MetaSig::TryGetUnmanagedCallingConventionFromModOpt(
+    _In_ Module *pModule,
+    _In_ PCCOR_SIGNATURE pSig,
+    _In_ ULONG cSig,
+    _Out_ CorUnmanagedCallingConvention *callConvOut)
 {
     CONTRACTL
     {
@@ -5236,53 +5275,61 @@ MetaSig::GetUnmanagedCallingConvention(
         GC_NOTRIGGER;
         FORBID_FAULT;
         MODE_ANY;
+        PRECONDITION(callConvOut != NULL);
     }
     CONTRACTL_END
 
-
     // Instantiations aren't relevant here
     MetaSig msig(pSig, cSig, pModule, NULL);
     PCCOR_SIGNATURE pWalk = msig.m_pRetType.GetPtr();
     _ASSERTE(pWalk <= pSig + cSig);
+
+    *callConvOut = (CorUnmanagedCallingConvention)0;
     while ((pWalk < (pSig + cSig)) && ((*pWalk == ELEMENT_TYPE_CMOD_OPT) || (*pWalk == ELEMENT_TYPE_CMOD_REQD)))
     {
         BOOL fIsOptional = (*pWalk == ELEMENT_TYPE_CMOD_OPT);
 
         pWalk++;
         if (pWalk + CorSigUncompressedDataSize(pWalk) > pSig + cSig)
-        {
-            return FALSE; // Bad formatting
-        }
+            return E_FAIL; // Bad formatting
+
         mdToken tk;
         pWalk += CorSigUncompressToken(pWalk, &tk);
 
-        if (fIsOptional)
+        if (!fIsOptional)
+            continue;
+
+        LPCSTR typeNamespace;
+        LPCSTR typeName;
+
+        // Check for CallConv types specified in modopt
+        if (FAILED(GetNameOfTypeRefOrDef(pModule, tk, &typeNamespace, &typeName)))
+            continue;
+
+        if (::strcmp(typeNamespace, CMOD_CALLCONV_NAMESPACE) != 0)
+            continue;
+
+        const struct {
+            LPCSTR name;
+            CorUnmanagedCallingConvention value;
+        } knownCallConvs[] = {
+            { CMOD_CALLCONV_NAME_CDECL,     IMAGE_CEE_UNMANAGED_CALLCONV_C },
+            { CMOD_CALLCONV_NAME_STDCALL,   IMAGE_CEE_UNMANAGED_CALLCONV_STDCALL },
+            { CMOD_CALLCONV_NAME_THISCALL,  IMAGE_CEE_UNMANAGED_CALLCONV_THISCALL },
+            { CMOD_CALLCONV_NAME_FASTCALL,  IMAGE_CEE_UNMANAGED_CALLCONV_FASTCALL } };
+
+        for (const auto &callConv : knownCallConvs)
         {
-            if (IsTypeRefOrDef("System.Runtime.CompilerServices.CallConvCdecl", pModule, tk))
+            // Take the first recognized calling convention in metadata.
+            if (::strcmp(typeName, callConv.name) == 0)
             {
-                *pPinvokeMapOut = pmCallConvCdecl;
-                return TRUE;
-            }
-            else if (IsTypeRefOrDef("System.Runtime.CompilerServices.CallConvStdcall", pModule, tk))
-            {
-                *pPinvokeMapOut = pmCallConvStdcall;
-                return TRUE;
-            }
-            else if (IsTypeRefOrDef("System.Runtime.CompilerServices.CallConvThiscall", pModule, tk))
-            {
-                *pPinvokeMapOut = pmCallConvThiscall;
-                return TRUE;
-            }
-            else if (IsTypeRefOrDef("System.Runtime.CompilerServices.CallConvFastcall", pModule, tk))
-            {
-                *pPinvokeMapOut = pmCallConvFastcall;
-                return TRUE;
+                *callConvOut = callConv.value;
+                return S_OK;
             }
         }
     }
 
-    *pPinvokeMapOut = (CorPinvokeMap)0;
-    return TRUE;
+    return S_FALSE;
 }
 
 //---------------------------------------------------------------------------------------
index 91e51fc..64c9341 100644 (file)
@@ -780,9 +780,30 @@ class MetaSig
         }
 
         //----------------------------------------------------------
-        // Returns the unmanaged calling convention.
+        // Gets the unmanaged calling convention by reading any modopts.
+        // If there are multiple modopts specifying recognized calling
+        // conventions, the first one that is found in the metadata wins.
+        // Note: the order in the metadata is the reverse of that in IL.
+        //
+        // Returns:
+        //   E_FAIL - Signature had an invalid format
+        //   S_OK - Calling convention was read from modopt
+        //   S_FALSE - Calling convention was not read from modopt
         //----------------------------------------------------------
-        static BOOL GetUnmanagedCallingConvention(Module *pModule, PCCOR_SIGNATURE pSig, ULONG cSig, CorPinvokeMap *pPinvokeMapOut);
+        static HRESULT TryGetUnmanagedCallingConventionFromModOpt(
+            _In_ Module *pModule,
+            _In_ PCCOR_SIGNATURE pSig,
+            _In_ ULONG cSig,
+            _Out_ CorUnmanagedCallingConvention *callConvOut);
+
+        static CorUnmanagedCallingConvention GetDefaultUnmanagedCallingConvention()
+        {
+#ifdef TARGET_UNIX
+            return IMAGE_CEE_UNMANAGED_CALLCONV_C;
+#else // TARGET_UNIX
+            return IMAGE_CEE_UNMANAGED_CALLCONV_STDCALL;
+#endif // !TARGET_UNIX
+        }
 
         //------------------------------------------------------------------
         // Like NextArg, but return only normalized type (enums flattned to
index 2f46bba..8fb05ff 100644 (file)
     ret
   }
 
+  .method public hidebysig static int32 CallUnmanagedIntInt_ModOptCdecl(void* p, int32 b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged int32 modopt([System.Runtime]System.Runtime.CompilerServices.CallConvCdecl) (int32)
+    ret
+  }
+
+  .method public hidebysig static int32 CallUnmanagedIntInt_ModOptStdcall(void* p, int32 b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged int32 modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) (int32)
+    ret
+  }
+
+  .method public hidebysig static int32 CallUnmanagedIntInt_ModOptUnknown(void* p, int32 b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged int32 modopt([System.Runtime]System.Runtime.CompilerServices.RuntimeFeature) (int32)
+    ret
+  }
+
+  .method public hidebysig static int32 CallUnmanagedIntInt_ModOptStdcall_ModOptCdecl(void* p, int32 b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged int32 modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) modopt([System.Runtime]System.Runtime.CompilerServices.CallConvCdecl) (int32)
+    ret
+  }
+
+  .method public hidebysig static int32 CallUnmanagedIntInt_ModOptStdcall_ModOptUnknown(void* p, int32 b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged int32 modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) modopt([System.Runtime]System.Runtime.CompilerServices.RuntimeFeature) (int32)
+    ret
+  }
+
+  .method public hidebysig static int32 CallUnmanagedCdeclIntInt_ModOptStdcall(void* p, int32 b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged cdecl int32 modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) (int32)
+    ret
+  }
+
+  .method public hidebysig static int32 CallUnmanagedStdcallIntInt_ModOptCdecl(void* p, int32 b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged stdcall int32 modopt([System.Runtime]System.Runtime.CompilerServices.CallConvCdecl) (int32)
+    ret
+  }
+
   .method public hidebysig static char CallUnmanagedCharChar(void* p, char b) cil managed
   {
     .maxstack 2
     calli unmanaged stdcall char(char)
     ret
   }
+
+  .method public hidebysig static char CallUnmanagedCharChar_ModOptCdecl(void* p, char b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged char modopt([System.Runtime]System.Runtime.CompilerServices.CallConvCdecl) (char)
+    ret
+  }
+
+  .method public hidebysig static char CallUnmanagedCharChar_ModOptStdcall(void* p, char b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged char modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) (char)
+    ret
+  }
+
+  .method public hidebysig static char CallUnmanagedCharChar_ModOptUnknown(void* p, char b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged char modopt([System.Runtime]System.Runtime.CompilerServices.RuntimeFeature) (char)
+    ret
+  }
+
+  .method public hidebysig static char CallUnmanagedCharChar_ModOptStdcall_ModOptCdecl(void* p, char b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged char modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) modopt([System.Runtime]System.Runtime.CompilerServices.CallConvCdecl) (char)
+    ret
+  }
+
+  .method public hidebysig static char CallUnmanagedCharChar_ModOptStdcall_ModOptUnknown(void* p, char b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged char modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) modopt([System.Runtime]System.Runtime.CompilerServices.RuntimeFeature) (char)
+    ret
+  }
+
+  .method public hidebysig static char CallUnmanagedCdeclCharChar_ModOptStdcall(void* p, char b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged cdecl char modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) (char)
+    ret
+  }
+
+  .method public hidebysig static char CallUnmanagedStdcallCharChar_ModOptCdecl(void* p, char b) cil managed
+  {
+    .maxstack 2
+    ldarg.1
+    ldarg.0
+    calli unmanaged stdcall char modopt([System.Runtime]System.Runtime.CompilerServices.CallConvCdecl) (char)
+    ret
+  }
 }
index 38c4a1b..935e401 100644 (file)
@@ -41,24 +41,74 @@ unsafe class Program
         Console.WriteLine($"Running {nameof(BlittableFunctionPointers)}...");
 
         IntPtr mod = NativeLibrary.Load(NativeFunctions.GetFullPath());
+        var cbDefault = NativeLibrary.GetExport(mod, "DoubleInt").ToPointer();
+        var cbCdecl = NativeLibrary.GetExport(mod, "DoubleIntCdecl").ToPointer();
+        var cbStdcall = NativeLibrary.GetExport(mod, "DoubleIntStdcall").ToPointer();
 
         const int a = 7;
         const int expected = a * 2;
+
+        {
+            // No modopt
+            Console.WriteLine($" -- unmanaged");
+            int b = CallFunctionPointers.CallUnmanagedIntInt(cbDefault, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            Console.WriteLine($" -- unmanaged cdecl");
+            int b = CallFunctionPointers.CallUnmanagedCdeclIntInt(cbCdecl, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            Console.WriteLine($" -- unmanaged stdcall");
+            int b = CallFunctionPointers.CallUnmanagedStdcallIntInt(cbStdcall, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            Console.WriteLine($" -- unmanaged modopt(cdecl)");
+            int b = CallFunctionPointers.CallUnmanagedIntInt_ModOptCdecl(cbCdecl, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            Console.WriteLine($" -- unmanaged modopt(stdcall)");
+            int b = CallFunctionPointers.CallUnmanagedIntInt_ModOptStdcall(cbStdcall, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            // Value in modopt is not a recognized calling convention
+            Console.WriteLine($" -- unmanaged modopt unrecognized");
+            int b = CallFunctionPointers.CallUnmanagedIntInt_ModOptUnknown(cbDefault, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            // Multiple modopts with calling conventions
+            Console.WriteLine($" -- unmanaged modopt(stdcall) modopt(cdecl)");
+            int b = CallFunctionPointers.CallUnmanagedIntInt_ModOptStdcall_ModOptCdecl(cbCdecl, a);
+            Assert.AreEqual(expected, b);
+        }
+
         {
-            var cb = NativeLibrary.GetExport(mod, "DoubleInt").ToPointer();
-            Assert.Throws<NotImplementedException>(() => CallFunctionPointers.CallUnmanagedIntInt(cb, a));
+            Console.WriteLine($" -- unmanaged modopt(stdcall) modopt(unrecognized)");
+            int b = CallFunctionPointers.CallUnmanagedIntInt_ModOptStdcall_ModOptUnknown(cbStdcall, a);
+            Assert.AreEqual(expected, b);
         }
 
         {
-            var cb = NativeLibrary.GetExport(mod, "DoubleIntCdecl").ToPointer();
-            int b = CallFunctionPointers.CallUnmanagedCdeclIntInt(cb, a);
-            Assert.AreEqual(b, expected);
+            Console.WriteLine($" -- unmanaged cdecl modopt(stdcall)");
+            int b = CallFunctionPointers.CallUnmanagedCdeclIntInt_ModOptStdcall(cbCdecl, a);
+            Assert.AreEqual(expected, b);
         }
 
         {
-            var cb = NativeLibrary.GetExport(mod, "DoubleIntStdcall").ToPointer();
-            int b = CallFunctionPointers.CallUnmanagedStdcallIntInt(cb, a);
-            Assert.AreEqual(b, expected);
+            Console.WriteLine($" -- unmanaged stdcall modopt(cdecl)");
+            int b = CallFunctionPointers.CallUnmanagedStdcallIntInt_ModOptCdecl(cbStdcall, a);
+            Assert.AreEqual(expected, b);
         }
     }
 
@@ -67,24 +117,74 @@ unsafe class Program
         Console.WriteLine($"Running {nameof(NonblittableFunctionPointers)}...");
 
         IntPtr mod = NativeLibrary.Load(NativeFunctions.GetFullPath());
+        var cbDefault = NativeLibrary.GetExport(mod, "ToUpper").ToPointer();
+        var cbCdecl = NativeLibrary.GetExport(mod, "ToUpperCdecl").ToPointer();
+        var cbStdcall = NativeLibrary.GetExport(mod, "ToUpperStdcall").ToPointer();
 
         const char a = 'i';
         const char expected = 'I';
+
+        {
+            // No modopt
+            Console.WriteLine($" -- unmanaged");
+            var b = CallFunctionPointers.CallUnmanagedCharChar(cbDefault, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            Console.WriteLine($" -- unmanaged cdecl");
+            var b = CallFunctionPointers.CallUnmanagedCdeclCharChar(cbCdecl, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            Console.WriteLine($" -- unmanaged stdcall");
+            var b = CallFunctionPointers.CallUnmanagedStdcallCharChar(cbStdcall, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            Console.WriteLine($" -- unmanaged modopt(cdecl)");
+            var b = CallFunctionPointers.CallUnmanagedCharChar_ModOptCdecl(cbCdecl, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            Console.WriteLine($" -- unmanaged modopt(stdcall)");
+            var b = CallFunctionPointers.CallUnmanagedCharChar_ModOptStdcall(cbStdcall, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            // Value in modopt is not a recognized calling convention
+            Console.WriteLine($" -- unmanaged modopt(unrecognized)");
+            var b = CallFunctionPointers.CallUnmanagedCharChar_ModOptUnknown(cbDefault, a);
+            Assert.AreEqual(expected, b);
+        }
+
+        {
+            // Multiple modopts with calling conventions
+            Console.WriteLine($" -- unmanaged modopt(stdcall) modopt(cdecl)");
+            var b = CallFunctionPointers.CallUnmanagedCharChar_ModOptStdcall_ModOptCdecl(cbCdecl, a);
+            Assert.AreEqual(expected, b);
+        }
+
         {
-            var cb = NativeLibrary.GetExport(mod, "ToUpper").ToPointer();
-            Assert.Throws<NotImplementedException>(() => CallFunctionPointers.CallUnmanagedCharChar(cb, a));
+            Console.WriteLine($" -- unmanaged modopt(stdcall) modopt(unrecognized)");
+            var b = CallFunctionPointers.CallUnmanagedCharChar_ModOptStdcall_ModOptUnknown(cbStdcall, a);
+            Assert.AreEqual(expected, b);
         }
 
         {
-            var cb = NativeLibrary.GetExport(mod, "ToUpperCdecl").ToPointer();
-            var b = CallFunctionPointers.CallUnmanagedCdeclCharChar(cb, a);
-            Assert.AreEqual(b, expected);
+            Console.WriteLine($" -- unmanaged cdecl modopt(stdcall)");
+            var b = CallFunctionPointers.CallUnmanagedCdeclCharChar_ModOptStdcall(cbCdecl, a);
+            Assert.AreEqual(expected, b);
         }
 
         {
-            var cb = NativeLibrary.GetExport(mod, "ToUpperStdcall").ToPointer();
-            var b = CallFunctionPointers.CallUnmanagedStdcallCharChar(cb, a);
-            Assert.AreEqual(b, expected);
+            Console.WriteLine($" -- unmanaged stdcall modopt(cdecl)");
+            var b = CallFunctionPointers.CallUnmanagedStdcallCharChar_ModOptCdecl(cbStdcall, a);
+            Assert.AreEqual(expected, b);
         }
     }