* Add CoreCLR implementation of CallConvMemberFunction.
* Add tests and make a few fixes to get them passing.
* Fix Clang build
* Fix argument ordering for calling a cdecl or stdcall instance method from managed.
* Update src/coreclr/dlls/mscorrc/mscorrc.rc
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
* Fix UnmanagedCallersOnly instance method calls for stdcall and cdecl.
* Update issues.targets.
* Fix formatting.
* Add dummy param to validate param order. Rewrite traversal to second-to-last argument.
* Merge implementations of call conv modopt name comparison.
Use a macro-based implementation initially since it allows us to add new calling conventions by adding one line and the macro-ized code is relatively small.
* Fix assignment alignment.
* SAL and contracts.
* Add assert
* Fix UnmanagedCallersOnly parsing in the refactored parser.
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
IDS_INVALID_REDIM "Illegal attempt to replace or redimension a fixed or locked SafeArray."
- IDS_INVALID_PINVOKE_CALLCONV "Invalid unmanaged calling convention: must be one of stdcall, cdecl, or thiscall."
+ IDS_INVALID_PINVOKE_CALLCONV "Unsupported unmanaged calling convention."
IDS_CLASSLOAD_NSTRUCT_EXPLICIT_OFFSET "Could not load type '%1' from assembly '%2' because field '%3' was not given an explicit offset."
IDS_WRONGSIZEARRAY_IN_NSTRUCT "Type could not be marshaled because the length of an embedded array instance does not match the declared length in the layout."
#define CMOD_CALLCONV_NAME_THISCALL "CallConvThiscall"
#define CMOD_CALLCONV_NAME_FASTCALL "CallConvFastcall"
#define CMOD_CALLCONV_NAME_SUPPRESSGCTRANSITION "CallConvSuppressGCTransition"
+#define CMOD_CALLCONV_NAME_MEMBERFUNCTION "CallConvMemberFunction"
#endif // MACROS_NOT_SUPPORTED
CORINFO_HELP_JIT_PINVOKE_BEGIN, // Transition to preemptive mode before a P/Invoke, frame is the first argument
CORINFO_HELP_JIT_PINVOKE_END, // Transition to cooperative mode after a P/Invoke, frame is the first argument
- CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, // Transition to cooperative mode in reverse P/Invoke prolog, frame is the first argument
+ CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, // Transition to cooperative mode in reverse P/Invoke prolog, frame is the first argument
CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS, // Transition to cooperative mode and track transitions in reverse P/Invoke prolog.
CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, // Transition to preemptive mode in reverse P/Invoke epilog, frame is the first argument
CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS, // Transition to preemptive mode and track transitions in reverse P/Invoke prolog.
C,
Stdcall,
Thiscall,
- Fastcall
+ Fastcall,
// New calling conventions supported with the extensible calling convention encoding go here.
+ CMemberFunction,
+ StdcallMemberFunction,
+ FastcallMemberFunction
};
#ifdef TARGET_X86
inline bool IsCallerPop(CorInfoCallConvExtension callConv)
{
#ifdef UNIX_X86_ABI
- return callConv == CorInfoCallConvExtension::Managed || callConv == CorInfoCallConvExtension::C;
+ return callConv == CorInfoCallConvExtension::Managed || callConv == CorInfoCallConvExtension::C || callConv == CorInfoCallConvExtension::CMemberFunction;
#else
- return callConv == CorInfoCallConvExtension::C;
+ return callConv == CorInfoCallConvExtension::C || callConv == CorInfoCallConvExtension::CMemberFunction;
#endif // UNIX_X86_ABI
}
#endif
// Determines whether or not this calling convention is an instance method calling convention.
inline bool callConvIsInstanceMethodCallConv(CorInfoCallConvExtension callConv)
{
- return callConv == CorInfoCallConvExtension::Thiscall;
+ return callConv == CorInfoCallConvExtension::Thiscall || callConv == CorInfoCallConvExtension::CMemberFunction || callConv == CorInfoCallConvExtension::StdcallMemberFunction || callConv == CorInfoCallConvExtension::FastcallMemberFunction;
}
// These are returned from getMethodOptions
{
if (callConvIsInstanceMethodCallConv(srcCall->GetUnmanagedCallConv()))
{
+#ifdef TARGET_X86
+ // The argument list has already been reversed.
+ // Insert the return buffer as the second-to-last node
+ // so it will be pushed on to the stack after the user args but before the native this arg
+ // as required by the native ABI.
+ GenTreeCall::Use* lastArg = srcCall->gtCallArgs;
+ if (lastArg == nullptr)
+ {
+ srcCall->gtCallArgs = gtPrependNewCallArg(destAddr, srcCall->gtCallArgs);
+ }
+ else if (srcCall->GetUnmanagedCallConv() == CorInfoCallConvExtension::Thiscall)
+ {
+ // For thiscall, the "this" parameter is not included in the argument list reversal,
+ // so we need to put the return buffer as the last parameter.
+ for (; lastArg->GetNext() != nullptr; lastArg = lastArg->GetNext())
+ ;
+ gtInsertNewCallArgAfter(destAddr, lastArg);
+ }
+ else if (lastArg->GetNext() == nullptr)
+ {
+ srcCall->gtCallArgs = gtPrependNewCallArg(destAddr, lastArg);
+ }
+ else
+ {
+ assert(lastArg != nullptr && lastArg->GetNext() != nullptr);
+ GenTreeCall::Use* secondLastArg = lastArg;
+ lastArg = lastArg->GetNext();
+ for (; lastArg->GetNext() != nullptr; secondLastArg = lastArg, lastArg = lastArg->GetNext())
+ ;
+ assert(secondLastArg->GetNext() != nullptr);
+ gtInsertNewCallArgAfter(destAddr, secondLastArg);
+ }
+#else
GenTreeCall::Use* thisArg = gtInsertNewCallArgAfter(destAddr, srcCall->gtCallArgs);
+#endif
}
else
{
call->gtCallMoreFlags |= GTF_CALL_M_SUPPRESS_GC_TRANSITION;
}
- if (unmanagedCallConv != CorInfoCallConvExtension::C && unmanagedCallConv != CorInfoCallConvExtension::Stdcall &&
- unmanagedCallConv != CorInfoCallConvExtension::Thiscall)
+ // If we can't get the unmanaged calling convention or the calling convention is unsupported in the JIT,
+ // return here without inlining the native call.
+ if (unmanagedCallConv == CorInfoCallConvExtension::Managed ||
+ unmanagedCallConv == CorInfoCallConvExtension::Fastcall ||
+ unmanagedCallConv == CorInfoCallConvExtension::FastcallMemberFunction)
{
return;
}
}
// AMD64 convention is same for native and managed
- if (unmanagedCallConv == CorInfoCallConvExtension::C)
+ if (unmanagedCallConv == CorInfoCallConvExtension::C ||
+ unmanagedCallConv == CorInfoCallConvExtension::CMemberFunction)
{
call->gtFlags |= GTF_CALL_POP_ARGS;
}
break;
case CorInfoCallConvExtension::C:
case CorInfoCallConvExtension::Stdcall:
+ case CorInfoCallConvExtension::CMemberFunction:
+ case CorInfoCallConvExtension::StdcallMemberFunction:
varDscInfo.Init(lvaTable, hasRetBuffArg, 0, 0);
break;
case CorInfoCallConvExtension::Managed:
case CorInfoCallConvExtension::Fastcall:
+ case CorInfoCallConvExtension::FastcallMemberFunction:
default:
varDscInfo.Init(lvaTable, hasRetBuffArg, MAX_REG_ARG, MAX_FLOAT_REG_ARG);
break;
// the native return buffer parameter.
if (callConvIsInstanceMethodCallConv(info.compCallConv))
{
- noway_assert(lvaTable[lclNum].lvIsRegArg);
-#ifndef TARGET_X86
- argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum, REGSIZE_BYTES, argOffs);
+#ifdef TARGET_X86
+ if (!lvaTable[lclNum].lvIsRegArg)
+ {
+ argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum, REGSIZE_BYTES, argOffs);
+ }
+#else
+ argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum, REGSIZE_BYTES, argOffs);
#endif // TARGET_X86
lclNum++;
userArgsToSkip++;
}
bool found = false;
+ bool memberFunctionVariant = false;
foreach (CustomAttributeTypedArgument<TypeDesc> type in callConvArray)
{
if (!(type.Value is DefType defType))
if (defType.Namespace != "System.Runtime.CompilerServices")
continue;
+ if (defType.Name == "CallConvMemberFunction")
+ {
+ memberFunctionVariant = true;
+ continue;
+ }
+
CorInfoCallConvExtension? callConvLocal = GetCallingConventionForCallConvType(defType);
if (callConvLocal.HasValue)
found = true;
}
}
+
+ if (found && memberFunctionVariant)
+ {
+ callConv = GetMemberFunctionCallingConventionVariant(callConv);
+ }
+
return callConv;
}
return false;
bool found = false;
+ bool memberFunctionVariant = false;
foreach (EmbeddedSignatureData data in signature.GetEmbeddedSignatureData())
{
if (data.kind != EmbeddedSignatureDataKind.OptionalCustomModifier)
suppressGCTransition = true;
continue;
}
+ else if (defType.Name == "CallConvMemberFunction")
+ {
+ memberFunctionVariant = true;
+ continue;
+ }
CorInfoCallConvExtension? callConvLocal = GetCallingConventionForCallConvType(defType);
}
}
+ if (found && memberFunctionVariant)
+ {
+ callConv = GetMemberFunctionCallingConventionVariant(callConv);
+ }
+
return found;
}
_ => null
};
+ private static CorInfoCallConvExtension GetMemberFunctionCallingConventionVariant(CorInfoCallConvExtension baseCallConv) =>
+ baseCallConv switch
+ {
+ CorInfoCallConvExtension.C => CorInfoCallConvExtension.CMemberFunction,
+ CorInfoCallConvExtension.Stdcall => CorInfoCallConvExtension.StdcallMemberFunction,
+ CorInfoCallConvExtension.Fastcall => CorInfoCallConvExtension.FastcallMemberFunction,
+ var c => c
+ };
+
private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* sig)
{
sig->callConv = (CorInfoCallConv)(signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask);
C,
Stdcall,
Thiscall,
- Fastcall
+ Fastcall,
// New calling conventions supported with the extensible calling convention encoding go here.
+ CMemberFunction,
+ StdcallMemberFunction,
+ FastcallMemberFunction
}
public enum CORINFO_CALLINFO_FLAGS
DEFINE_CLASS(CALLCONV_THISCALL, CompilerServices, CallConvThiscall)
DEFINE_CLASS(CALLCONV_FASTCALL, CompilerServices, CallConvFastcall)
DEFINE_CLASS(CALLCONV_SUPPRESSGCTRANSITION, CompilerServices, CallConvSuppressGCTransition)
+DEFINE_CLASS(CALLCONV_MEMBERFUNCTION, CompilerServices, CallConvMemberFunction)
DEFINE_CLASS_U(Interop, SafeHandle, SafeHandle)
DEFINE_FIELD_U(handle, SafeHandle, m_handle)
}
else
{
- if (unmgdCallConv != CorInfoCallConvExtension::Stdcall &&
- unmgdCallConv != CorInfoCallConvExtension::C &&
- unmgdCallConv != CorInfoCallConvExtension::Thiscall)
+ if (unmgdCallConv == CorInfoCallConvExtension::Managed ||
+ unmgdCallConv == CorInfoCallConvExtension::Fastcall ||
+ unmgdCallConv == CorInfoCallConvExtension::FastcallMemberFunction)
{
COMPlusThrow(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV);
}
}
#endif
-namespace
-{
- // Templated function to compute if a char string begins with a constant string.
- template<size_t S2LEN>
- bool BeginsWith(ULONG s1Len, const char* s1, const char (&s2)[S2LEN])
- {
- WRAPPER_NO_CONTRACT;
-
- ULONG s2Len = (ULONG)S2LEN - 1; // Remove null
- if (s1Len < s2Len)
- return false;
-
- return (0 == strncmp(s1, s2, s2Len));
- }
-}
-
bool TryGetCallingConventionFromUnmanagedCallersOnly(MethodDesc* pMD, CorInfoCallConvExtension* pCallConv)
{
STANDARD_VM_CONTRACT;
else
{
// Set WinAPI as the default
- callConvLocal = MetaSig::GetDefaultUnmanagedCallingConvention();
+ callConvLocal = CorInfoCallConvExtension::Managed;
+
+ MetaSig::CallingConventionModifiers modifiers = MetaSig::CALL_CONV_MOD_NONE;
+
+ bool foundBaseCallConv = false;
+ bool useMemberFunctionVariant = false;
CaValue* arrayOfTypes = &namedArgs[0].val;
for (ULONG i = 0; i < arrayOfTypes->arr.length; i++)
{
CaValue& typeNameValue = arrayOfTypes->arr[i];
- // According to ECMA-335, type name strings are UTF-8. Since we are
- // looking for type names that are equivalent in ASCII and UTF-8,
- // using a const char constant is acceptable. Type name strings are
- // in Fully Qualified form, so we include the ',' delimiter.
- if (BeginsWith(typeNameValue.str.cbStr, typeNameValue.str.pStr, "System.Runtime.CompilerServices.CallConvCdecl,"))
- {
- callConvLocal = CorInfoCallConvExtension::C;
- }
- else if (BeginsWith(typeNameValue.str.cbStr, typeNameValue.str.pStr, "System.Runtime.CompilerServices.CallConvStdcall,"))
- {
- callConvLocal = CorInfoCallConvExtension::Stdcall;
- }
- else if (BeginsWith(typeNameValue.str.cbStr, typeNameValue.str.pStr, "System.Runtime.CompilerServices.CallConvFastcall,"))
+ if (!MetaSig::TryApplyModOptToCallingConvention(
+ typeNameValue.str.pStr,
+ typeNameValue.str.cbStr,
+ MetaSig::CallConvModOptNameType::FullyQualifiedName,
+ &callConvLocal,
+ &modifiers))
{
- callConvLocal = CorInfoCallConvExtension::Fastcall;
- }
- else if (BeginsWith(typeNameValue.str.cbStr, typeNameValue.str.pStr, "System.Runtime.CompilerServices.CallConvThiscall,"))
- {
- callConvLocal = CorInfoCallConvExtension::Thiscall;
+ // We found a second base calling convention.
+ return false;
}
}
+
+ if (callConvLocal == CorInfoCallConvExtension::Managed)
+ {
+ callConvLocal = MetaSig::GetDefaultUnmanagedCallingConvention();
+ }
+
+ if (modifiers & MetaSig::CALL_CONV_MOD_MEMBERFUNCTION)
+ {
+ callConvLocal = MetaSig::GetMemberFunctionUnmanagedCallingConventionVariant(callConvLocal);
+ }
}
*pCallConv = callConvLocal;
return true;
_ASSERTE(pWalk <= pSig + cSig);
*callConvOut = CorInfoCallConvExtension::Managed;
- bool found = false;
+ CallingConventionModifiers modifiers = CALL_CONV_MOD_NONE;
while ((pWalk < (pSig + cSig)) && ((*pWalk == ELEMENT_TYPE_CMOD_OPT) || (*pWalk == ELEMENT_TYPE_CMOD_REQD)))
{
BOOL fIsOptional = (*pWalk == ELEMENT_TYPE_CMOD_OPT);
if (::strcmp(typeNamespace, CMOD_CALLCONV_NAMESPACE) != 0)
continue;
- if (::strcmp(typeName, CMOD_CALLCONV_NAME_SUPPRESSGCTRANSITION) == 0)
+ if (!TryApplyModOptToCallingConvention(
+ typeName,
+ ::strlen(typeName),
+ CallConvModOptNameType::TypeName,
+ callConvOut,
+ &modifiers))
{
- *suppressGCTransitionOut = true;
- continue;
+ // Error if there are multiple recognized base calling conventions
+ *errorResID = IDS_EE_MULTIPLE_CALLCONV_UNSUPPORTED;
+ return COR_E_INVALIDPROGRAM;
}
+ }
- const struct {
- LPCSTR name;
- CorInfoCallConvExtension value;
- } knownCallConvs[] = {
- { CMOD_CALLCONV_NAME_CDECL, CorInfoCallConvExtension::C },
- { CMOD_CALLCONV_NAME_STDCALL, CorInfoCallConvExtension::Stdcall },
- { CMOD_CALLCONV_NAME_THISCALL, CorInfoCallConvExtension::Thiscall },
- { CMOD_CALLCONV_NAME_FASTCALL, CorInfoCallConvExtension::Fastcall } };
+ *suppressGCTransitionOut = ((modifiers & CALL_CONV_MOD_SUPPRESSGCTRANSITION) != 0);
- for (const auto &callConv : knownCallConvs)
+ if (modifiers & CALL_CONV_MOD_MEMBERFUNCTION)
+ {
+ if (*callConvOut == CorInfoCallConvExtension::Managed)
{
- // Look for a recognized calling convention in metadata.
- if (::strcmp(typeName, callConv.name) == 0)
- {
- // Error if there are multiple recognized calling conventions
- if (found)
- {
- *errorResID = IDS_EE_MULTIPLE_CALLCONV_UNSUPPORTED;
- return COR_E_INVALIDPROGRAM;
- }
-
- *callConvOut = callConv.value;
- found = true;
- }
+ // In this case, the only specified calling convention is CallConvMemberFunction.
+ // Set *callConvOut to the default unmanaged calling convention.
+ *callConvOut = MetaSig::GetDefaultUnmanagedCallingConvention();
}
+
+ *callConvOut = MetaSig::GetMemberFunctionUnmanagedCallingConventionVariant(*callConvOut);
}
- return found ? S_OK : S_FALSE;
+ return *callConvOut != CorInfoCallConvExtension::Managed ? S_OK : S_FALSE;
+}
+
+// According to ECMA-335, type name strings are UTF-8. Since we are
+// looking for type names that are equivalent in ASCII and UTF-8,
+// using a const char constant is acceptable. Type name strings are
+// in Fully Qualified form, so we include the ',' delimiter.
+#define MAKE_FULLY_QUALIFIED_CALLCONV_TYPE_NAME_PREFIX(callConvTypeName) CMOD_CALLCONV_NAMESPACE "." callConvTypeName ","
+
+namespace
+{
+ // Templated function to compute if a char string begins with a constant string.
+ template<size_t S2LEN>
+ bool BeginsWith(size_t s1Len, const char* s1, const char (&s2)[S2LEN])
+ {
+ WRAPPER_NO_CONTRACT;
+
+ size_t s2Len = S2LEN - 1; // Remove null
+
+ if (s1Len < s2Len)
+ return false;
+
+ return (0 == strncmp(s1, s2, s2Len));
+ }
}
+bool MetaSig::TryApplyModOptToCallingConvention(
+ _In_z_ LPCSTR callConvModOptName,
+ _In_ size_t callConvModOptNameLength,
+ _In_ CallConvModOptNameType nameType,
+ _Inout_ CorInfoCallConvExtension* pBaseCallConv,
+ _Inout_ CallingConventionModifiers* pCallConvModifiers)
+{
+ CONTRACTL
+ {
+ STANDARD_VM_CHECK;
+ PRECONDITION(CheckPointer(pBaseCallConv));
+ PRECONDITION(CheckPointer(pCallConvModifiers));
+ }
+ CONTRACTL_END;
+
+#define BASE_CALL_CONV(callConvModOptMetadataName, CallConvExtensionMember) \
+ if (COMPARE_CALLCONV_NAME(callConvModOptName, callConvModOptMetadataName)) \
+ { \
+ if (*pBaseCallConv != CorInfoCallConvExtension::Managed) return false; \
+ *pBaseCallConv = CorInfoCallConvExtension::CallConvExtensionMember; \
+ return true; \
+ }
+ \
+#define CALL_CONV_MODIFIER(callConvModOptMetadataName, CallConvModifiersMember) \
+ if (COMPARE_CALLCONV_NAME(callConvModOptName, callConvModOptMetadataName)) \
+ { \
+ *pCallConvModifiers = (CallingConventionModifiers)(*pCallConvModifiers | CallConvModifiersMember); \
+ return true; \
+ }
+
+#define PARSE_CALL_CONVS \
+ BASE_CALL_CONV(CMOD_CALLCONV_NAME_CDECL, C) \
+ BASE_CALL_CONV(CMOD_CALLCONV_NAME_STDCALL, Stdcall) \
+ BASE_CALL_CONV(CMOD_CALLCONV_NAME_THISCALL, Thiscall) \
+ BASE_CALL_CONV(CMOD_CALLCONV_NAME_FASTCALL, Fastcall) \
+ CALL_CONV_MODIFIER(CMOD_CALLCONV_NAME_SUPPRESSGCTRANSITION, CALL_CONV_MOD_SUPPRESSGCTRANSITION) \
+ CALL_CONV_MODIFIER(CMOD_CALLCONV_NAME_MEMBERFUNCTION, CALL_CONV_MOD_MEMBERFUNCTION) \
+
+ if (nameType == CallConvModOptNameType::TypeName)
+ {
+#define COMPARE_CALLCONV_NAME(userProvidedName, metadataName) ::strcmp(userProvidedName, metadataName) == 0
+ PARSE_CALL_CONVS;
+#undef COMPARE_CALLCONV_NAME
+ }
+ else
+ {
+ _ASSERTE(nameType == CallConvModOptNameType::FullyQualifiedName);
+
+#define COMPARE_CALLCONV_NAME(userProvidedName, metadataName) BeginsWith(callConvModOptNameLength, userProvidedName, MAKE_FULLY_QUALIFIED_CALLCONV_TYPE_NAME_PREFIX(metadataName))
+ PARSE_CALL_CONVS;
+#undef COMPARE_CALLCONV_NAME
+ }
+ return true;
+}
//---------------------------------------------------------------------------------------
//
// Substitution from a token (TypeDef and TypeRef have empty instantiation, TypeSpec gets it from MetaData).
_Out_ bool* suppressGCTransitionOut,
_Out_ UINT *errorResID);
+ enum CallingConventionModifiers
+ {
+ CALL_CONV_MOD_NONE = 0,
+ CALL_CONV_MOD_SUPPRESSGCTRANSITION = 0x1,
+ CALL_CONV_MOD_MEMBERFUNCTION = 0x2
+ };
+
+ enum class CallConvModOptNameType
+ {
+ TypeName,
+ FullyQualifiedName
+ };
+
+ // Attempt to parse the provided calling convention name and add it to the collected modifiers and calling convention
+ // Returns false if the modopt is known and cannot be applied.
+ // This occurs when the modopt is a base calling convention and a base calling convention has already been supplied.
+ // Otherwise, returns true.
+ static bool TryApplyModOptToCallingConvention(
+ _In_z_ LPCSTR callConvModOptName,
+ _In_ size_t callConvModOptNameLength,
+ _In_ CallConvModOptNameType nameType,
+ _Inout_ CorInfoCallConvExtension* pBaseCallConv,
+ _Inout_ CallingConventionModifiers* pCallConvModifiers);
+
static CorInfoCallConvExtension GetDefaultUnmanagedCallingConvention()
{
#ifdef TARGET_UNIX
#endif // !TARGET_UNIX
}
+ static CorInfoCallConvExtension GetMemberFunctionUnmanagedCallingConventionVariant(CorInfoCallConvExtension baseCallConv)
+ {
+ switch (baseCallConv)
+ {
+ case CorInfoCallConvExtension::C:
+ return CorInfoCallConvExtension::CMemberFunction;
+ case CorInfoCallConvExtension::Stdcall:
+ return CorInfoCallConvExtension::StdcallMemberFunction;
+ case CorInfoCallConvExtension::Fastcall:
+ return CorInfoCallConvExtension::FastcallMemberFunction;
+ case CorInfoCallConvExtension::Thiscall:
+ return CorInfoCallConvExtension::Thiscall;
+ default:
+ _ASSERTE("Calling convention is not an unmanaged base calling convention.");
+ return baseCallConv;
+ }
+ }
+
//------------------------------------------------------------------
// Like NextArg, but return only normalized type (enums flattned to
// underlying type ...
case CorInfoCallConvExtension::Fastcall:
m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_FASTCALL)));
break;
+ case CorInfoCallConvExtension::CMemberFunction:
+ m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_CDECL)));
+ m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_MEMBERFUNCTION)));
+ break;
+ case CorInfoCallConvExtension::StdcallMemberFunction:
+ m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_STDCALL)));
+ m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_MEMBERFUNCTION)));
+ break;
+ case CorInfoCallConvExtension::FastcallMemberFunction:
+ m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_FASTCALL)));
+ m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_MEMBERFUNCTION)));
+ break;
default:
_ASSERTE("Unknown calling convention. Unable to encode it in the native function pointer signature.");
break;
/// </summary>
public CallConvSuppressGCTransition() { }
}
+
public class CallConvThiscall
{
public CallConvThiscall() { }
}
+
+ /// <summary>
+ /// Indicates that the calling convention used is the member function variant.
+ /// </summary>
+ public class CallConvMemberFunction
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CallConvMemberFunction" /> class.
+ /// </summary>
+ public CallConvMemberFunction() { }
+ }
}
{
public CallConvFastcall() { }
}
+ public partial class CallConvMemberFunction
+ {
+ public CallConvMemberFunction() { }
+ }
public partial class CallConvStdcall
{
public CallConvStdcall() { }
#define __stdcall __attribute__((stdcall))
#define _cdecl __attribute__((cdecl))
#define __cdecl __attribute__((cdecl))
+#define __thiscall __attribute__((thiscall))
#else
#define __stdcall
#define _cdecl
#define __cdecl
+#define __thiscall
#endif
#endif
typedef void* LPVOID;
typedef unsigned char BYTE;
typedef WCHAR OLECHAR;
-typedef double DATE;
+typedef double DATE;
typedef DWORD LCID;
#endif
add_subdirectory(PInvoke/Array/MarshalArrayAsField/LPArrayNative)
add_subdirectory(PInvoke/Array/MarshalArrayAsParam/LPArrayNative)
add_subdirectory(PInvoke/Miscellaneous/HandleRef)
-add_subdirectory(PInvoke/Miscellaneous/ThisCall)
add_subdirectory(PInvoke/Miscellaneous/MultipleAssembliesWithSamePInvoke)
add_subdirectory(PInvoke/CriticalHandles)
add_subdirectory(PInvoke/Generics)
+++ /dev/null
-
-project (ThisCallNative)
-include ("${CLR_INTEROP_TEST_ROOT}/Interop.cmake")
-set(SOURCES
- ThisCallNative.cpp
-)
-add_library (ThisCallNative SHARED ${SOURCES})
-install (TARGETS ThisCallNative DESTINATION bin)
--- /dev/null
+include_directories(${INC_PLATFORM_DIR})
+
+set(SOURCES
+ InstanceCallConvTest.cpp
+)
+
+add_library (ThisCallNative SHARED ${SOURCES})
+target_compile_definitions(ThisCallNative PRIVATE INSTANCE_CALLCONV=__thiscall)
+
+add_library (StdCallMemberFunctionNative SHARED ${SOURCES})
+target_compile_definitions(StdCallMemberFunctionNative PRIVATE INSTANCE_CALLCONV=__stdcall)
+
+add_library (CdeclMemberFunctionNative SHARED ${SOURCES})
+target_compile_definitions(CdeclMemberFunctionNative PRIVATE INSTANCE_CALLCONV=__cdecl)
+
+add_library (PlatformDefaultMemberFunctionNative SHARED ${SOURCES})
+target_compile_definitions(PlatformDefaultMemberFunctionNative PRIVATE INSTANCE_CALLCONV=STDMETHODCALLTYPE)
+
+install (TARGETS ThisCallNative DESTINATION bin)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Reflection;
+using System.Text;
+using TestLibrary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+unsafe class CdeclMemberFunctionNative
+{
+ public struct C
+ {
+ public struct VtableLayout
+ {
+ public delegate* unmanaged[Cdecl, MemberFunction]<C*, int, SizeF> getSize;
+ public delegate* unmanaged[Cdecl, MemberFunction]<C*, Width> getWidth;
+ public delegate* unmanaged[Cdecl, MemberFunction]<C*, IntWrapper> getHeightAsInt;
+ public delegate* unmanaged[Cdecl, MemberFunction]<C*, E> getE;
+ public delegate* unmanaged[Cdecl, MemberFunction]<C*, CLong> getWidthAsLong;
+ }
+
+ public VtableLayout* vtable;
+ public E dummy;
+ public float width;
+ public float height;
+ }
+
+ public struct SizeF
+ {
+ public float width;
+ public float height;
+ }
+
+ public struct Width
+ {
+ public float width;
+ }
+
+ public struct IntWrapper
+ {
+ public int i;
+ }
+
+ public enum E : uint
+ {
+ Value = 42
+ }
+
+ [DllImport(nameof(CdeclMemberFunctionNative))]
+ public static extern C* CreateInstanceOfC(float width, float height);
+
+ [DllImport(nameof(CdeclMemberFunctionNative))]
+ public static extern SizeF GetSizeFromManaged(C* c);
+ [DllImport(nameof(CdeclMemberFunctionNative))]
+ public static extern Width GetWidthFromManaged(C* c);
+ [DllImport(nameof(CdeclMemberFunctionNative))]
+ public static extern IntWrapper GetHeightAsIntFromManaged(C* c);
+ [DllImport(nameof(CdeclMemberFunctionNative))]
+ public static extern E GetEFromManaged(C* c);
+ [DllImport(nameof(CdeclMemberFunctionNative))]
+ public static extern CLong GetWidthAsLongFromManaged(C* c);
+}
+
+unsafe class CdeclMemberFunctionTest
+{
+ public static int Main(string[] args)
+ {
+ try
+ {
+ float width = 1.0f;
+ float height = 2.0f;
+ CdeclMemberFunctionNative.C* instance = CdeclMemberFunctionNative.CreateInstanceOfC(width, height);
+ Test8ByteHFA(instance);
+ Test4ByteHFA(instance);
+ Test4ByteNonHFA(instance);
+ TestEnum(instance);
+ TestCLong(instance);
+ Test8ByteHFAUnmanagedCallersOnly();
+ Test4ByteHFAUnmanagedCallersOnly();
+ Test4ByteNonHFAUnmanagedCallersOnly();
+ TestEnumUnmanagedCallersOnly();
+ TestCLongUnmanagedCallersOnly();
+ }
+ catch (System.Exception ex)
+ {
+ Console.WriteLine(ex);
+ return 101;
+ }
+ return 100;
+ }
+
+ private static void Test8ByteHFA(CdeclMemberFunctionNative.C* instance)
+ {
+ CdeclMemberFunctionNative.SizeF result = instance->vtable->getSize(instance, 1234);
+
+ Assert.AreEqual(instance->width, result.width);
+ Assert.AreEqual(instance->height, result.height);
+ }
+
+ private static void Test4ByteHFA(CdeclMemberFunctionNative.C* instance)
+ {
+ CdeclMemberFunctionNative.Width result = instance->vtable->getWidth(instance);
+
+ Assert.AreEqual(instance->width, result.width);
+ }
+
+ private static void Test4ByteNonHFA(CdeclMemberFunctionNative.C* instance)
+ {
+ CdeclMemberFunctionNative.IntWrapper result = instance->vtable->getHeightAsInt(instance);
+
+ Assert.AreEqual((int)instance->height, result.i);
+ }
+
+ private static void TestEnum(CdeclMemberFunctionNative.C* instance)
+ {
+ CdeclMemberFunctionNative.E result = instance->vtable->getE(instance);
+
+ Assert.AreEqual(instance->dummy, result);
+ }
+
+ private static void TestCLong(CdeclMemberFunctionNative.C* instance)
+ {
+ CLong result = instance->vtable->getWidthAsLong(instance);
+
+ Assert.AreEqual((nint)instance->width, result.Value);
+ }
+
+ private static void Test8ByteHFAUnmanagedCallersOnly()
+ {
+ CdeclMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ CdeclMemberFunctionNative.SizeF result = CdeclMemberFunctionNative.GetSizeFromManaged(&c);
+
+ Assert.AreEqual(c.width, result.width);
+ Assert.AreEqual(c.height, result.height);
+ }
+
+ private static void Test4ByteHFAUnmanagedCallersOnly()
+ {
+ CdeclMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ CdeclMemberFunctionNative.Width result = CdeclMemberFunctionNative.GetWidthFromManaged(&c);
+
+ Assert.AreEqual(c.width, result.width);
+ }
+
+ private static void Test4ByteNonHFAUnmanagedCallersOnly()
+ {
+ CdeclMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ CdeclMemberFunctionNative.IntWrapper result = CdeclMemberFunctionNative.GetHeightAsIntFromManaged(&c);
+
+ Assert.AreEqual((int)c.height, result.i);
+ }
+
+ private static void TestEnumUnmanagedCallersOnly()
+ {
+ CdeclMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ CdeclMemberFunctionNative.E result = CdeclMemberFunctionNative.GetEFromManaged(&c);
+
+ Assert.AreEqual(c.dummy, result);
+ }
+
+ private static void TestCLongUnmanagedCallersOnly()
+ {
+ CdeclMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ CLong result = CdeclMemberFunctionNative.GetWidthAsLongFromManaged(&c);
+
+ Assert.AreEqual((nint)c.width, result.Value);
+ }
+
+ private static CdeclMemberFunctionNative.C CreateCWithUnmanagedCallersOnlyVTable(float width, float height)
+ {
+ return new CdeclMemberFunctionNative.C
+ {
+ vtable = UnmanagedCallersOnlyVtable,
+ dummy = CdeclMemberFunctionNative.E.Value,
+ width = width,
+ height = height
+ };
+ }
+
+ private static CdeclMemberFunctionNative.C.VtableLayout* unmanagedCallersOnlyVtable;
+
+ private static CdeclMemberFunctionNative.C.VtableLayout* UnmanagedCallersOnlyVtable
+ {
+ get
+ {
+ if (unmanagedCallersOnlyVtable == null)
+ {
+ unmanagedCallersOnlyVtable = (CdeclMemberFunctionNative.C.VtableLayout*)Marshal.AllocHGlobal(sizeof(CdeclMemberFunctionNative.C.VtableLayout));
+ unmanagedCallersOnlyVtable->getSize = &GetSize;
+ unmanagedCallersOnlyVtable->getWidth = &GetWidth;
+ unmanagedCallersOnlyVtable->getHeightAsInt = &GetHeightAsInt;
+ unmanagedCallersOnlyVtable->getE = &GetE;
+ unmanagedCallersOnlyVtable->getWidthAsLong = &GetWidthAsLong;
+ }
+ return unmanagedCallersOnlyVtable;
+ }
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvCdecl), typeof(CallConvMemberFunction)})]
+ private static CdeclMemberFunctionNative.SizeF GetSize(CdeclMemberFunctionNative.C* c, int unused)
+ {
+ return new CdeclMemberFunctionNative.SizeF
+ {
+ width = c->width,
+ height = c->height
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvCdecl), typeof(CallConvMemberFunction)})]
+ private static CdeclMemberFunctionNative.Width GetWidth(CdeclMemberFunctionNative.C* c)
+ {
+ return new CdeclMemberFunctionNative.Width
+ {
+ width = c->width
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvCdecl), typeof(CallConvMemberFunction)})]
+ private static CdeclMemberFunctionNative.IntWrapper GetHeightAsInt(CdeclMemberFunctionNative.C* c)
+ {
+ return new CdeclMemberFunctionNative.IntWrapper
+ {
+ i = (int)c->height
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvCdecl), typeof(CallConvMemberFunction)})]
+ private static CdeclMemberFunctionNative.E GetE(CdeclMemberFunctionNative.C* c)
+ {
+ return c->dummy;
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvCdecl), typeof(CallConvMemberFunction)})]
+ private static CLong GetWidthAsLong(CdeclMemberFunctionNative.C* c)
+ {
+ return new CLong((nint)c->width);
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="../CMakeLists.txt" />
+ <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+ </ItemGroup>
+</Project>
// The .NET Foundation licenses this file to you under the MIT license.
#include <stdio.h>
-#include <xplatform.h>
#include <platformdefines.h>
+#ifndef INSTANCE_CALLCONV
+#error The INSTANCE_CALLCONV define must be defined as the calling convention to use for the instance methods.
+#endif
+
struct SizeF
{
float width;
height(height)
{}
- virtual SizeF GetSize()
+ virtual SizeF INSTANCE_CALLCONV GetSize(int)
{
return {width, height};
}
- virtual Width GetWidth()
+ virtual Width INSTANCE_CALLCONV GetWidth()
{
return {width};
}
- virtual IntWrapper GetHeightAsInt()
+ virtual IntWrapper INSTANCE_CALLCONV GetHeightAsInt()
{
return {(int)height};
}
- virtual E GetE()
+ virtual E INSTANCE_CALLCONV GetE()
{
return dummy;
}
- virtual long GetWidthAsLong()
+ virtual long INSTANCE_CALLCONV GetWidthAsLong()
{
return (long)width;
}
};
-
extern "C" DLL_EXPORT C* STDMETHODCALLTYPE CreateInstanceOfC(float width, float height)
{
return new C(width, height);
extern "C" DLL_EXPORT SizeF STDMETHODCALLTYPE GetSizeFromManaged(C* c)
{
- return c->GetSize();
+ return c->GetSize(9876);
}
extern "C" DLL_EXPORT Width STDMETHODCALLTYPE GetWidthFromManaged(C* c)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Reflection;
+using System.Text;
+using TestLibrary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+unsafe class PlatformDefaultMemberFunctionNative
+{
+ public struct C
+ {
+ public struct VtableLayout
+ {
+ public delegate* unmanaged[MemberFunction]<C*, int, SizeF> getSize;
+ public delegate* unmanaged[MemberFunction]<C*, Width> getWidth;
+ public delegate* unmanaged[MemberFunction]<C*, IntWrapper> getHeightAsInt;
+ public delegate* unmanaged[MemberFunction]<C*, E> getE;
+ public delegate* unmanaged[MemberFunction]<C*, CLong> getWidthAsLong;
+ }
+
+ public VtableLayout* vtable;
+ public E dummy;
+ public float width;
+ public float height;
+ }
+
+ public struct SizeF
+ {
+ public float width;
+ public float height;
+ }
+
+ public struct Width
+ {
+ public float width;
+ }
+
+ public struct IntWrapper
+ {
+ public int i;
+ }
+
+ public enum E : uint
+ {
+ Value = 42
+ }
+
+ [DllImport(nameof(PlatformDefaultMemberFunctionNative))]
+ public static extern C* CreateInstanceOfC(float width, float height);
+
+ [DllImport(nameof(PlatformDefaultMemberFunctionNative))]
+ public static extern SizeF GetSizeFromManaged(C* c);
+ [DllImport(nameof(PlatformDefaultMemberFunctionNative))]
+ public static extern Width GetWidthFromManaged(C* c);
+ [DllImport(nameof(PlatformDefaultMemberFunctionNative))]
+ public static extern IntWrapper GetHeightAsIntFromManaged(C* c);
+ [DllImport(nameof(PlatformDefaultMemberFunctionNative))]
+ public static extern E GetEFromManaged(C* c);
+ [DllImport(nameof(PlatformDefaultMemberFunctionNative))]
+ public static extern CLong GetWidthAsLongFromManaged(C* c);
+}
+
+unsafe class PlatformDefaultMemberFunctionTest
+{
+ public static int Main(string[] args)
+ {
+ try
+ {
+ float width = 1.0f;
+ float height = 2.0f;
+ PlatformDefaultMemberFunctionNative.C* instance = PlatformDefaultMemberFunctionNative.CreateInstanceOfC(width, height);
+ Test8ByteHFA(instance);
+ Test4ByteHFA(instance);
+ Test4ByteNonHFA(instance);
+ TestEnum(instance);
+ TestCLong(instance);
+ Test8ByteHFAUnmanagedCallersOnly();
+ Test4ByteHFAUnmanagedCallersOnly();
+ Test4ByteNonHFAUnmanagedCallersOnly();
+ TestEnumUnmanagedCallersOnly();
+ TestCLongUnmanagedCallersOnly();
+ }
+ catch (System.Exception ex)
+ {
+ Console.WriteLine(ex);
+ return 101;
+ }
+ return 100;
+ }
+
+ private static void Test8ByteHFA(PlatformDefaultMemberFunctionNative.C* instance)
+ {
+ PlatformDefaultMemberFunctionNative.SizeF result = instance->vtable->getSize(instance, 1234);
+
+ Assert.AreEqual(instance->width, result.width);
+ Assert.AreEqual(instance->height, result.height);
+ }
+
+ private static void Test4ByteHFA(PlatformDefaultMemberFunctionNative.C* instance)
+ {
+ PlatformDefaultMemberFunctionNative.Width result = instance->vtable->getWidth(instance);
+
+ Assert.AreEqual(instance->width, result.width);
+ }
+
+ private static void Test4ByteNonHFA(PlatformDefaultMemberFunctionNative.C* instance)
+ {
+ PlatformDefaultMemberFunctionNative.IntWrapper result = instance->vtable->getHeightAsInt(instance);
+
+ Assert.AreEqual((int)instance->height, result.i);
+ }
+
+ private static void TestEnum(PlatformDefaultMemberFunctionNative.C* instance)
+ {
+ PlatformDefaultMemberFunctionNative.E result = instance->vtable->getE(instance);
+
+ Assert.AreEqual(instance->dummy, result);
+ }
+
+ private static void TestCLong(PlatformDefaultMemberFunctionNative.C* instance)
+ {
+ CLong result = instance->vtable->getWidthAsLong(instance);
+
+ Assert.AreEqual((nint)instance->width, result.Value);
+ }
+
+ private static void Test8ByteHFAUnmanagedCallersOnly()
+ {
+ PlatformDefaultMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ PlatformDefaultMemberFunctionNative.SizeF result = PlatformDefaultMemberFunctionNative.GetSizeFromManaged(&c);
+
+ Assert.AreEqual(c.width, result.width);
+ Assert.AreEqual(c.height, result.height);
+ }
+
+ private static void Test4ByteHFAUnmanagedCallersOnly()
+ {
+ PlatformDefaultMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ PlatformDefaultMemberFunctionNative.Width result = PlatformDefaultMemberFunctionNative.GetWidthFromManaged(&c);
+
+ Assert.AreEqual(c.width, result.width);
+ }
+
+ private static void Test4ByteNonHFAUnmanagedCallersOnly()
+ {
+ PlatformDefaultMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ PlatformDefaultMemberFunctionNative.IntWrapper result = PlatformDefaultMemberFunctionNative.GetHeightAsIntFromManaged(&c);
+
+ Assert.AreEqual((int)c.height, result.i);
+ }
+
+ private static void TestEnumUnmanagedCallersOnly()
+ {
+ PlatformDefaultMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ PlatformDefaultMemberFunctionNative.E result = PlatformDefaultMemberFunctionNative.GetEFromManaged(&c);
+
+ Assert.AreEqual(c.dummy, result);
+ }
+
+ private static void TestCLongUnmanagedCallersOnly()
+ {
+ PlatformDefaultMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ CLong result = PlatformDefaultMemberFunctionNative.GetWidthAsLongFromManaged(&c);
+
+ Assert.AreEqual((nint)c.width, result.Value);
+ }
+
+ private static PlatformDefaultMemberFunctionNative.C CreateCWithUnmanagedCallersOnlyVTable(float width, float height)
+ {
+ return new PlatformDefaultMemberFunctionNative.C
+ {
+ vtable = UnmanagedCallersOnlyVtable,
+ dummy = PlatformDefaultMemberFunctionNative.E.Value,
+ width = width,
+ height = height
+ };
+ }
+
+ private static PlatformDefaultMemberFunctionNative.C.VtableLayout* unmanagedCallersOnlyVtable;
+
+ private static PlatformDefaultMemberFunctionNative.C.VtableLayout* UnmanagedCallersOnlyVtable
+ {
+ get
+ {
+ if (unmanagedCallersOnlyVtable == null)
+ {
+ unmanagedCallersOnlyVtable = (PlatformDefaultMemberFunctionNative.C.VtableLayout*)Marshal.AllocHGlobal(sizeof(PlatformDefaultMemberFunctionNative.C.VtableLayout));
+ unmanagedCallersOnlyVtable->getSize = &GetSize;
+ unmanagedCallersOnlyVtable->getWidth = &GetWidth;
+ unmanagedCallersOnlyVtable->getHeightAsInt = &GetHeightAsInt;
+ unmanagedCallersOnlyVtable->getE = &GetE;
+ unmanagedCallersOnlyVtable->getWidthAsLong = &GetWidthAsLong;
+ }
+ return unmanagedCallersOnlyVtable;
+ }
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvMemberFunction)})]
+ private static PlatformDefaultMemberFunctionNative.SizeF GetSize(PlatformDefaultMemberFunctionNative.C* c, int unused)
+ {
+ return new PlatformDefaultMemberFunctionNative.SizeF
+ {
+ width = c->width,
+ height = c->height
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvMemberFunction)})]
+ private static PlatformDefaultMemberFunctionNative.Width GetWidth(PlatformDefaultMemberFunctionNative.C* c)
+ {
+ return new PlatformDefaultMemberFunctionNative.Width
+ {
+ width = c->width
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvMemberFunction)})]
+ private static PlatformDefaultMemberFunctionNative.IntWrapper GetHeightAsInt(PlatformDefaultMemberFunctionNative.C* c)
+ {
+ return new PlatformDefaultMemberFunctionNative.IntWrapper
+ {
+ i = (int)c->height
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvMemberFunction)})]
+ private static PlatformDefaultMemberFunctionNative.E GetE(PlatformDefaultMemberFunctionNative.C* c)
+ {
+ return c->dummy;
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvMemberFunction)})]
+ private static CLong GetWidthAsLong(PlatformDefaultMemberFunctionNative.C* c)
+ {
+ return new CLong((nint)c->width);
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="../CMakeLists.txt" />
+ <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+ </ItemGroup>
+</Project>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Reflection;
+using System.Text;
+using TestLibrary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+unsafe class StdCallMemberFunctionNative
+{
+ public struct C
+ {
+ public struct VtableLayout
+ {
+ public delegate* unmanaged[Stdcall, MemberFunction]<C*, int, SizeF> getSize;
+ public delegate* unmanaged[Stdcall, MemberFunction]<C*, Width> getWidth;
+ public delegate* unmanaged[Stdcall, MemberFunction]<C*, IntWrapper> getHeightAsInt;
+ public delegate* unmanaged[Stdcall, MemberFunction]<C*, E> getE;
+ public delegate* unmanaged[Stdcall, MemberFunction]<C*, CLong> getWidthAsLong;
+ }
+
+ public VtableLayout* vtable;
+ public E dummy;
+ public float width;
+ public float height;
+ }
+
+ public struct SizeF
+ {
+ public float width;
+ public float height;
+ }
+
+ public struct Width
+ {
+ public float width;
+ }
+
+ public struct IntWrapper
+ {
+ public int i;
+ }
+
+ public enum E : uint
+ {
+ Value = 42
+ }
+
+ [DllImport(nameof(StdCallMemberFunctionNative))]
+ public static extern C* CreateInstanceOfC(float width, float height);
+
+ [DllImport(nameof(StdCallMemberFunctionNative))]
+ public static extern SizeF GetSizeFromManaged(C* c);
+ [DllImport(nameof(StdCallMemberFunctionNative))]
+ public static extern Width GetWidthFromManaged(C* c);
+ [DllImport(nameof(StdCallMemberFunctionNative))]
+ public static extern IntWrapper GetHeightAsIntFromManaged(C* c);
+ [DllImport(nameof(StdCallMemberFunctionNative))]
+ public static extern E GetEFromManaged(C* c);
+ [DllImport(nameof(StdCallMemberFunctionNative))]
+ public static extern CLong GetWidthAsLongFromManaged(C* c);
+}
+
+unsafe class StdCallMemberFunctionTest
+{
+ public static int Main(string[] args)
+ {
+ try
+ {
+ float width = 1.0f;
+ float height = 2.0f;
+ StdCallMemberFunctionNative.C* instance = StdCallMemberFunctionNative.CreateInstanceOfC(width, height);
+ Test8ByteHFA(instance);
+ Test4ByteHFA(instance);
+ Test4ByteNonHFA(instance);
+ TestEnum(instance);
+ TestCLong(instance);
+ Test8ByteHFAUnmanagedCallersOnly();
+ Test4ByteHFAUnmanagedCallersOnly();
+ Test4ByteNonHFAUnmanagedCallersOnly();
+ TestEnumUnmanagedCallersOnly();
+ TestCLongUnmanagedCallersOnly();
+ }
+ catch (System.Exception ex)
+ {
+ Console.WriteLine(ex);
+ return 101;
+ }
+ return 100;
+ }
+
+ private static void Test8ByteHFA(StdCallMemberFunctionNative.C* instance)
+ {
+ StdCallMemberFunctionNative.SizeF result = instance->vtable->getSize(instance, 1234);
+
+ Assert.AreEqual(instance->width, result.width);
+ Assert.AreEqual(instance->height, result.height);
+ }
+
+ private static void Test4ByteHFA(StdCallMemberFunctionNative.C* instance)
+ {
+ StdCallMemberFunctionNative.Width result = instance->vtable->getWidth(instance);
+
+ Assert.AreEqual(instance->width, result.width);
+ }
+
+ private static void Test4ByteNonHFA(StdCallMemberFunctionNative.C* instance)
+ {
+ StdCallMemberFunctionNative.IntWrapper result = instance->vtable->getHeightAsInt(instance);
+
+ Assert.AreEqual((int)instance->height, result.i);
+ }
+
+ private static void TestEnum(StdCallMemberFunctionNative.C* instance)
+ {
+ StdCallMemberFunctionNative.E result = instance->vtable->getE(instance);
+
+ Assert.AreEqual(instance->dummy, result);
+ }
+
+ private static void TestCLong(StdCallMemberFunctionNative.C* instance)
+ {
+ CLong result = instance->vtable->getWidthAsLong(instance);
+
+ Assert.AreEqual((nint)instance->width, result.Value);
+ }
+ private static void Test8ByteHFAUnmanagedCallersOnly()
+ {
+ StdCallMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ StdCallMemberFunctionNative.SizeF result = StdCallMemberFunctionNative.GetSizeFromManaged(&c);
+
+ Assert.AreEqual(c.width, result.width);
+ Assert.AreEqual(c.height, result.height);
+ }
+
+ private static void Test4ByteHFAUnmanagedCallersOnly()
+ {
+ StdCallMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ StdCallMemberFunctionNative.Width result = StdCallMemberFunctionNative.GetWidthFromManaged(&c);
+
+ Assert.AreEqual(c.width, result.width);
+ }
+
+ private static void Test4ByteNonHFAUnmanagedCallersOnly()
+ {
+ StdCallMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ StdCallMemberFunctionNative.IntWrapper result = StdCallMemberFunctionNative.GetHeightAsIntFromManaged(&c);
+
+ Assert.AreEqual((int)c.height, result.i);
+ }
+
+ private static void TestEnumUnmanagedCallersOnly()
+ {
+ StdCallMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ StdCallMemberFunctionNative.E result = StdCallMemberFunctionNative.GetEFromManaged(&c);
+
+ Assert.AreEqual(c.dummy, result);
+ }
+
+ private static void TestCLongUnmanagedCallersOnly()
+ {
+ StdCallMemberFunctionNative.C c = CreateCWithUnmanagedCallersOnlyVTable(2.0f, 3.0f);
+ CLong result = StdCallMemberFunctionNative.GetWidthAsLongFromManaged(&c);
+
+ Assert.AreEqual((nint)c.width, result.Value);
+ }
+
+ private static StdCallMemberFunctionNative.C CreateCWithUnmanagedCallersOnlyVTable(float width, float height)
+ {
+ return new StdCallMemberFunctionNative.C
+ {
+ vtable = UnmanagedCallersOnlyVtable,
+ dummy = StdCallMemberFunctionNative.E.Value,
+ width = width,
+ height = height
+ };
+ }
+
+ private static StdCallMemberFunctionNative.C.VtableLayout* unmanagedCallersOnlyVtable;
+
+ private static StdCallMemberFunctionNative.C.VtableLayout* UnmanagedCallersOnlyVtable
+ {
+ get
+ {
+ if (unmanagedCallersOnlyVtable == null)
+ {
+ unmanagedCallersOnlyVtable = (StdCallMemberFunctionNative.C.VtableLayout*)Marshal.AllocHGlobal(sizeof(StdCallMemberFunctionNative.C.VtableLayout));
+ unmanagedCallersOnlyVtable->getSize = &GetSize;
+ unmanagedCallersOnlyVtable->getWidth = &GetWidth;
+ unmanagedCallersOnlyVtable->getHeightAsInt = &GetHeightAsInt;
+ unmanagedCallersOnlyVtable->getE = &GetE;
+ unmanagedCallersOnlyVtable->getWidthAsLong = &GetWidthAsLong;
+ }
+ return unmanagedCallersOnlyVtable;
+ }
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvStdcall), typeof(CallConvMemberFunction)})]
+ private static StdCallMemberFunctionNative.SizeF GetSize(StdCallMemberFunctionNative.C* c, int unused)
+ {
+ return new StdCallMemberFunctionNative.SizeF
+ {
+ width = c->width,
+ height = c->height
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvStdcall), typeof(CallConvMemberFunction)})]
+ private static StdCallMemberFunctionNative.Width GetWidth(StdCallMemberFunctionNative.C* c)
+ {
+ return new StdCallMemberFunctionNative.Width
+ {
+ width = c->width
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvStdcall), typeof(CallConvMemberFunction)})]
+ private static StdCallMemberFunctionNative.IntWrapper GetHeightAsInt(StdCallMemberFunctionNative.C* c)
+ {
+ return new StdCallMemberFunctionNative.IntWrapper
+ {
+ i = (int)c->height
+ };
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvStdcall), typeof(CallConvMemberFunction)})]
+ private static StdCallMemberFunctionNative.E GetE(StdCallMemberFunctionNative.C* c)
+ {
+ return c->dummy;
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvStdcall), typeof(CallConvMemberFunction)})]
+ private static CLong GetWidthAsLong(StdCallMemberFunctionNative.C* c)
+ {
+ return new CLong((nint)c->width);
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="../CMakeLists.txt" />
+ <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+ </ItemGroup>
+</Project>
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- public delegate SizeF GetSizeFn(C* c);
+ public delegate SizeF GetSizeFn(C* c, int unused);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate Width GetWidthFn(C* c);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
{
ThisCallNative.GetSizeFn callback = Marshal.GetDelegateForFunctionPointer<ThisCallNative.GetSizeFn>(instance->vtable->getSize);
- ThisCallNative.SizeF result = callback(instance);
+ ThisCallNative.SizeF result = callback(instance, 1234);
Assert.AreEqual(instance->width, result.width);
Assert.AreEqual(instance->height, result.height);
{
managedVtable = (ThisCallNative.C.VtableLayout*)Marshal.AllocHGlobal(sizeof(ThisCallNative.C.VtableLayout));
managedVtable->getSize = Marshal.GetFunctionPointerForDelegate(
- (ThisCallNative.GetSizeFn)((ThisCallNative.C* c) => new ThisCallNative.SizeF { width = c->width, height = c->height} ));
+ (ThisCallNative.GetSizeFn)((ThisCallNative.C* c, int unused) => new ThisCallNative.SizeF { width = c->width, height = c->height} ));
managedVtable->getWidth = Marshal.GetFunctionPointerForDelegate(
(ThisCallNative.GetWidthFn)((ThisCallNative.C* c) => new ThisCallNative.Width { width = c->width} ));
managedVtable->getHeightAsInt = Marshal.GetFunctionPointerForDelegate(
if (unmanagedCallersOnlyVtable == null)
{
unmanagedCallersOnlyVtable = (ThisCallNative.C.VtableLayout*)Marshal.AllocHGlobal(sizeof(ThisCallNative.C.VtableLayout));
- unmanagedCallersOnlyVtable->getSize = (IntPtr)(delegate* unmanaged[Thiscall]<ThisCallNative.C*, ThisCallNative.SizeF>)&GetSize;
+ unmanagedCallersOnlyVtable->getSize = (IntPtr)(delegate* unmanaged[Thiscall]<ThisCallNative.C*, int, ThisCallNative.SizeF>)&GetSize;
unmanagedCallersOnlyVtable->getWidth = (IntPtr)(delegate* unmanaged[Thiscall]<ThisCallNative.C*, ThisCallNative.Width>)&GetWidth;
unmanagedCallersOnlyVtable->getHeightAsInt = (IntPtr)(delegate* unmanaged[Thiscall]<ThisCallNative.C*, ThisCallNative.IntWrapper>)&GetHeightAsInt;
unmanagedCallersOnlyVtable->getE = (IntPtr)(delegate* unmanaged[Thiscall]<ThisCallNative.C*, ThisCallNative.E>)&GetE;
}
[UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvThiscall)})]
- private static ThisCallNative.SizeF GetSize(ThisCallNative.C* c)
+ private static ThisCallNative.SizeF GetSize(ThisCallNative.C* c, int unused)
{
return new ThisCallNative.SizeF
{
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
- <Compile Include="ThisCallTest.cs" />
+ <Compile Include="*.cs" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="CMakeLists.txt" />
+ <ProjectReference Include="../CMakeLists.txt" />
+ <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
</ItemGroup>
</Project>
<ExcludeList Include="$(XunitTestBinBase)/Interop/IJW/NativeCallingManaged/NativeCallingManaged/*">
<Issue>https://github.com/dotnet/runtime/issues/12299</Issue>
</ExcludeList>
- <ExcludeList Include="$(XUnitTestBinBase)/Interop/PInvoke/Miscellaneous/ThisCall/ThisCallTest/*">
- <Issue>Thiscall not supported on Windows ARM32.</Issue>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/ThisCall/ThisCallTest/*">
+ <Issue>Native member function calling conventions not supported on Windows ARM32.</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest/*">
+ <Issue>Native member function calling conventions not supported on Windows ARM32.</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest/*">
+ <Issue>Native member function calling conventions not supported on Windows ARM32.</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest/*">
+ <Issue>Native member function calling conventions not supported on Windows ARM32.</Issue>
</ExcludeList>
<ExcludeList Include="$(XunitTestBinBase)/JIT/Regression/CLR-x86-JIT/V1-M13-RTM/b88793/b88793/*">
<Issue>https://github.com/dotnet/runtime/issues/12979</Issue>
<ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Miscellaneous/MultipleAssembliesWithSamePInvoke/MAWSPITest/**">
<Issue>https://github.com/dotnet/runtime/issues/41519</Issue>
</ExcludeList>
- <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Miscellaneous/ThisCall/ThisCallTest/**">
- <Issue>https://github.com/dotnet/runtime/issues/41519</Issue>
- </ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Primitives/Pointer/NonBlittablePointer/**">
<Issue>https://github.com/dotnet/runtime/issues/41519</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/JIT/CodeGenBringUpTests/LocallocLarge_r/**">
<Issue>https://github.com/dotnet/runtime/issues/41472</Issue>
</ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/ThisCall/ThisCallTest/*">
+ <Issue>https://github.com/dotnet/runtime/issues/41519</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest/*">
+ <Issue>https://github.com/dotnet/runtime/issues/41519</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest/*">
+ <Issue>https://github.com/dotnet/runtime/issues/41519</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest/*">
+ <Issue>https://github.com/dotnet/runtime/issues/41519</Issue>
+ </ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Loader/classloader/TypeInitialization/CircularCctors/CircularCctorFourThreadsBFI/**">
<Issue>https://github.com/dotnet/runtime/issues/41472</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Miscellaneous/HandleRef/HandleRefTest/**">
<Issue>needs triage</Issue>
</ExcludeList>
- <ExcludeList Include = "$(XunitTestBinBase)/Interop/PInvoke/Miscellaneous/ThisCall/ThisCallTest/**">
- <Issue>needs triage</Issue>
- </ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Interop/UnmanagedCallersOnly/UnmanagedCallersOnlyTest/**">
<Issue>needs triage</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/JIT/CheckProjects/CheckProjects/**">
<Issue>needs triage</Issue>
</ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/ThisCall/ThisCallTest/*">
+ <Issue>needs triage</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest/*">
+ <Issue>needs triage</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest/*">
+ <Issue>needs triage</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XUnitTestBinBase)/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest/*">
+ <Issue>needs triage</Issue>
+ </ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/JIT/HardwareIntrinsics/Arm/Aes/Aes_r/**">
<Issue>https://github.com/dotnet/runtime/issues/44648</Issue>
</ExcludeList>