编译时推导函数调用约定、常量性、返回值、参数等函数类型特征

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/passFuHao/article/details/78775308

已知调用约定不是类型,也无法在运行时检查。所以,看似无法根据调用约定的不同实现分治,可std::thread和std::invoke是如何处理调用约定的?

存在以下事实;语句:

(void( __stdcall* )())0 == (void( __cdecl* )())0; 


无法被正确编译,cl.exe报告的编译错误为:error C2446: “==”: 没有从“void (__cdecl *)(void)”到“void (__stdcall *)(void)”的转换 
但以下语句可以通过编译:

(void( __stdcall* )())0 == (void( __stdcall* )())0; 

即:说明编译器能根据函数类型中的调用约定不同而产生不同的函数类型,所以,可以根据函数类型的调用约定不同进行特化。(此结论不适用于VS2013,VS2013虽可根据调用约定不同产出不同函数类型,但不可利用之进行特化)

完整实现如下:

#if _MSC_VER >1800 // 大于 VS2013
	/*
		FnSynopsis
			得到函数提要,包括函数返回类型、形参数量和各参数类型、调用约定以及
		函数是否声明为CONST等。

		类型成员:
			_ResultType,函数的返回类型。
			_FormalTypes,用 tuple 表示的形式参数包,可通过 tuple_size、
		tuple_element 等类得到其中包含的形参数量或类型。
			_PtrType,复原后的函数指针类型。

		值成员:
			CallType,调用约定;参见:CallConvention
			DeclareAsConst,函数是否声明为CONST。

		示例,得到函数调用约定:

			int __stdcall global_fun( int, double );

			struct A
			{
				static char __cdecl static_member_fun( unsigned char, char *& );
				float __stdcall member_fun( wchar_t & );
				float const_member_fun( int & ) const;

				void __cdecl cdecl_ellipsis_fun( const char *, ... );
			};
			
			void cdecl_ellipsis_fun( const char *, ... );

			int main( int argc, char ** argv )
			{
				int( __stdcall* p_global_fun )(int, double)  = global_fun;
				FnSynopsis< decltype(global_fun)>::CallType;		// _STD_CALL
				FnSynopsis< remove_reference_t<decltype(*p_global_fun)>>::CallType;	// _STD_CALL

				FnSynopsis< string( int, double ) const> ::CallType;	// 默认为_CDECL_CALL,视编译设置而定
				FnSynopsis< string( int, double ) >::CallType;				// 默认为_CDECL_CALL,视编译设置而定

				FnSynopsis< decltype(cdecl_ellipsis_fun)>::CallType;	// 错误,无法处理 c-style 变参函数;参考备注。

				FnSynopsis< decltype(A::static_member_fun)>::CallType;	// _CDECL_CALL
				FnSynopsis< decltype(&A::member_fun) >::CallType;			// _STD_CALL
				FnSynopsis< decltype(&A::const_member_fun)>::CallType;	// _THIS_CALL
				FnSynopsis< decltype(&A::cdecl_ellipsis_fun)>::CallType;	// 错误,无法处理 c-style 变参函数;参考备注。

				return EXIT_SUCCESS;
			}

		备注:
			1、FnSynopsis无法处理 c-style 变参函数,如果你有此有需求,以下是建议的实现:

				template< typename _Ret, typename ..._Types >
					struct FnSynopsis< _Ret __cdecl( _Types..., ... ) >
				{
					typedef _Ret _ResultType;
					typedef std::tuple< _Types... > _FormalTypes;	// 注意:_FormalTypes只包含函数的非 ... 部分的形参描述。
					typedef _Ret( __cdecl *_PtrType )(_Types...);
					static const CallConvention CallType = _CDECL_CALL;	// 注意:c-style 变参函数的调用约定一定是 __cdecl
					enum { DeclareAsConst = false };
				};

			2、FnSynopsis无法处理未实例化的模板函数。ms c++ 标准库中 bind 函数返回的对象其 operator() 是模板函数。

		< .fuhao >
		< .2017年12月3日17时54分 >
		< [email protected] >
		< Copyright (C.) 2009-2017 fuhao software team., all rights reserved >
	*/	
	template< typename _Tx > struct FnSynopsis;

	enum CallConvention {
		_CDECL_CALL,	// __cdecl
		_STD_CALL,		// __stdcall
		_FAST_CALL,		// __fastcall
		_THIS_CALL,		// __thiscall
		_CLR_CALL			// __clrcall
	};

#define DEFINE_FNSYNOPSIS_IMPL( _CallType, _CallTypeValue ) \
	template< typename _Ret, typename ..._Types >\
		struct FnSynopsis< _Ret _CallType( _Types... ) >\
	{\
		typedef _Ret _ResultType;\
		typedef VOID _ClassType;\
		typedef std::tuple< _Types... > _FormalTypes;\
		typedef _Ret( _CallType *_PtrType )( _Types... );\
		static const CallConvention CallType = _CallTypeValue; \
		enum { DeclareAsConst = false };\
		enum { IsMemberFunction = false };\
	};\
	template< typename _Ret, typename ..._Types >\
		struct FnSynopsis< _Ret _CallType( _Types ... ) const >\
	{\
		typedef _Ret _ResultType;\
		typedef VOID _ClassType;\
		typedef std::tuple< _Types... > _FormalTypes;\
		typedef _Ret( _CallType *const _PtrType )( _Types... );\
		static const CallConvention CallType = _CallTypeValue; \
		enum { DeclareAsConst = true };\
		enum { IsMemberFunction = false };\
	}

#define DEFINE_MEM_FNSYNOPSIS_IMPL( _CallType, _CallTypeValue )\
	template< typename _Ret, typename _Class, typename ..._Types >\
		struct FnSynopsis< _Ret( _CallType _Class::* )(_Types...) >\
	{\
		typedef _Ret _ResultType;\
		typedef _Class _ClassType;\
		typedef std::tuple< _Types... > _FormalTypes;\
		typedef _Ret( _CallType _Class::* _PtrType) (_Types ...);\
		static const CallConvention CallType = _CallTypeValue; \
		enum { DeclareAsConst = false };\
		enum { IsMemberFunction = true };\
	};\
	template< typename _Ret, typename _Class, typename ..._Types >\
		struct FnSynopsis< _Ret( _CallType _Class::* )(_Types...) const >\
	{\
		typedef _Ret _ResultType;\
		typedef _Class _ClassType;\
		typedef std::tuple< _Types... > _FormalTypes;\
		typedef _Ret( _CallType _Class::* _PtrType) (_Types ...) const;\
		static const CallConvention CallType = _CallTypeValue; \
		enum { DeclareAsConst = true };\
		enum { IsMemberFunction = true };\
	}

	DEFINE_FNSYNOPSIS_IMPL( __cdecl, _CDECL_CALL );
	DEFINE_FNSYNOPSIS_IMPL( __stdcall, _STD_CALL );

	DEFINE_MEM_FNSYNOPSIS_IMPL( __cdecl, _CDECL_CALL );
	DEFINE_MEM_FNSYNOPSIS_IMPL( __stdcall, _STD_CALL );
	DEFINE_MEM_FNSYNOPSIS_IMPL( __thiscall, _THIS_CALL );

	#ifdef _M_CEE
	DEFINE_FNSYNOPSIS_IMPL( __clrcall, _CLR_CALL );
	DEFINE_MEM_FNSYNOPSIS_IMPL( __clrcall, _CLR_CALL );
	#else
	DEFINE_FNSYNOPSIS_IMPL( __fastcall, _FAST_CALL );
	DEFINE_MEM_FNSYNOPSIS_IMPL( __fastcall, _FAST_CALL );
	#endif

	// 针对可调用对象的 FnSynopsis,用法和 FnSynopsis 差不多。
	template< typename _Tx, typename = _Tx > struct CallableSynopsis;

	template< typename _Tx >
	struct CallableSynopsis< _Tx, typename std::enable_if< std::is_class< typename std::remove_reference< _Tx >::type >::value, _Tx >::type >
		: public FnSynopsis< decltype(&std::remove_reference<_Tx>::type::operator()) >
	{
		enum : bool { IsCallableObject = true };
	};

	template< typename _Tx >
	struct CallableSynopsis< _Tx, typename std::enable_if< !std::is_class< typename std::remove_reference< _Tx >::type  >::value, _Tx >::type >
		: public FnSynopsis< typename std::remove_pointer< typename std::remove_reference< _Tx >::type >::type >
	{
		enum : bool { IsCallableObject = false };
	};

#endif


有一点值得注意:
FnSynopsis内的_FormalTypes成员是 tuple,不是函数的参数的类型包,若需将此复原为类型包,可参考以下实现:

	void fun( int, string );

	template< typename ..._Types >
	void restore( tuple< _Types ... > && )
	{
		printf( "ArgsNumber:%d\n", sizeof...(_Types) );
	}

使用:

restore( FnSynopsis< decltype(fun) >::_FormalTypes() );


若要利用 _FormalTypes存储参数,并伺机调用,建议实现如下:

void fn1( string, int ){}

template< typename _Fn1, typename ..._Types >
auto Bypass( _Fn1 &&_Func, tuple< _Types ... > Tuple )->result_of_t< _Fn1&(_Types...)>
{
	return ApplyTupleExpand( _Func, std::move( Tuple ) );
}

template< typename _Fn1, typename ..._Types >
auto Call( _Fn1 && _Func, _Types &&... Args )->result_of_t< _Fn1&(_Types...)>
{
	FnSynopsis< remove_reference_t< remove_pointer_t< decltype(_Func)>>>::_FormalTypes Tuple = make_tuple( std::forward< _Types >( Args )... );
	return Bypass( _Func, Tuple );
}


使用:

Call( fn1, string(), 123 );

ApplyTupleExpand 实现如下:

	/*
		ApplyTupleExpand
			展开std::tuple,并将tuple元素作为参数调用_Func。

		参数:
			_Func,函数对象。ApplyTupleExpand调用此函数时会向其传递已展开为参数包的Tuple。
			Tuple,将要被展开为参数包的std::tuple对象。

		返回值:
			返回_Func的调用结果。

		注意:
			如果要处理 move 语义,需要在调用 ApplyTupleExpand 传入右值的 Tuple;

		示例:
			VOID Fun( String _RightString, String &_LRefString, CONST String &_CLRefString, String _String )
			{
			}

			template< typename _Fn1, typename ..._Types >
			VOID Call( _Fn1 &&_Func, _Types &&... Args )
			{
				tuple< _Types&&... > _Tup( std::forward< _Type >( Args )... );
				ApplyTupleExpand( _Func, std::move( _Tup ) );		注意使用std::move,否则向 Fun 函数的_RightString形参传递参数时将采用拷贝方式
			}

			int main( int argc, char ** argv )
			{
				String RightStringArg;
				String LRefStringArg;
				String LCRefStringgArg;
				String StringArg;
				Call( Fun, std::move( RightStringArg ), LRefStringArg, LCRefStringArg, StringArg );

				return NOERROR;
			}
				
		< .fuhao >
		< .2017年2月15日15时33分 >
		< [email protected] >
		< Copyright (C.) 2009-2017 fuhao software team., all rights reserved >
	*/
	template< std::size_t N > struct _TupleExpand{
		template< typename _Fty, typename _Ety, typename... _Remain >
		static inline auto Expand( _Fty &&_Func, _Ety && _Elem, _Remain&&... Args )->\
			decltype(_TupleExpand< N - 1 >::Expand(
			std::forward< _Fty>( _Func ), std::forward< _Ety >( _Elem ),
			std::get< N - 1 >( std::forward< _Ety >( _Elem ) ),
			std::forward< _Remain>( Args )... )){
			return _TupleExpand< N - 1 >::Expand( std::forward< _Fty >( _Func ),
				std::forward<_Ety>( _Elem ),
				std::get<N - 1>( std::forward<_Ety>( _Elem ) ),
				std::forward<_Remain>( Args )... );
		}
	};

	template<> struct _TupleExpand < 0 > {
		template< typename _Fty, typename _Ety, typename... _Remain>
		static inline auto Expand( _Fty && _Func, _Ety &&, _Remain &&... Args )->\
			decltype(::std::forward<_Fty>( _Func ) (::std::forward<_Remain>( Args )...)){
			return ::std::forward< _Fty >( _Func )(::std::forward < _Remain >( Args )...);
		}
	};

	template< typename _Fty, typename _Tuple >
	inline auto ApplyTupleExpand( _Fty && _Func, _Tuple && Tuple )->\
	decltype(_TupleExpand< std::tuple_size< typename std::decay< _Tuple >::type >::value >::Expand( std::forward< _Fty >( _Func ), std::forward< _Tuple >( Tuple ) )){
		return _TupleExpand < std::tuple_size < typename std::decay< _Tuple >::type >::value >::Expand( std::forward< _Fty>( _Func ), std::forward< _Tuple >( Tuple ) );
	}


以及一个扩展版,使用了部分 c++ 14特性的实现,如下:

#if _MSC_VER > 1800 // VS2013以上版本
	/*
		ApplyTupleExpandEx
			同 ApplyTupleExpand 的作用相同。

		参数:
			_Func,要调用的函数。
			Tuple,将要被展开为参数包的std::tuple对象。

		返回值:
			_Func 的返回值。
	*/
	template< typename _Fty, typename _Tuple, size_t ... Index >
	decltype(auto) ApplyTupleExpandImpl(
		_Fty &&_Func, _Tuple &&Tuple, std::index_sequence<Index...> )
	{
		return _Func( std::get< Index>( std::forward<_Tuple>( Tuple ) )... );
	}

	template< typename _Fty, typename _Tuple >
	decltype(auto) ApplyTupleExpandEx( _Fty &&_Func, _Tuple &&Tuple )
	{
		using std::decay;
		using std::tuple_size;
		using std::make_index_sequence;

		return ApplyTupleExpandImpl( std::forward< _Fty>( _Func ),
			std::forward< _Tuple >( Tuple ),
			make_index_sequence< tuple_size< typename decay< _Tuple>::type >::value >{} );
	}
#endif


至此,实现了一个可以推导函数调用约定的基础设施。至于作何用,基础设施的作用是无限大,发挥想像就行了。事实上,这是我的GDI对象调试器项目中一部分核心代码,用来为hook大量的GDI函数编写通用的detour函数所用。

猜你喜欢

转载自blog.csdn.net/passFuHao/article/details/78775308
今日推荐