在 C/C++ 中实现委托(delegate)
相信使用过C#语言的人们,对于委托应该是记忆深刻,委托具有很多不错的特性,但最令人感到兴奋的是它可以简化很多复杂的开发同时具有低耦合与对象闭包的特性。 委托在C/C++中可以解决最直观的就有问题,一个是相同代码签名的成员函数与全局函数可以被一个委托调用的问题,在C/C++语言中我们在不借助委托的前提下,代码很难在不增加耦合与依赖的情况调用它们(无论这个依赖是隐式的还是显式的都会存在这个..
相信使用过C#语言的人们,对于委托应该是记忆深刻,委托具有很多不错的特性,但最令人感到兴奋的是它可以简化很多复杂的开发同时具有低耦合与对象闭包的特性。
委托在C/C++中可以解决最直观的就有问题,一个是相同代码签名的成员函数与全局函数可以被一个委托调用的问题,在C/C++语言中我们在不借助委托的前提下,代码很难在不增加耦合与依赖的情况调用它们(无论这个依赖是隐式的还是显式的都会存在这个问题),另外一个就是说无法保证其类函数的闭包性的问题,且先不说使用类函数指针的复杂性,绝大多数情况下我们都不需要用到类函数指针,主要是不好用。
在C/C++中实现委托的办法有两个类型,一个是基于运行时汇编另一类是利用C/C++11编译器特性(泛型可变模板)两种各有各自的优势到说不好谁更加友好,当然采取C/C++.NET倒不会这么的麻烦。
委托与回调是类似的,看上去它们似乎是异步的形式,但实际上并不是;它们是同步的,不过为了更加专业化与透彻的描述它们,那么用这句话来描述会更加贴切;“委托与回调对赋值方【申请方】是异步的,对于调用方而言它是同步的!”;我不想为了这种事情在为此与人之间进行无数的争论。
上图中是使用委托的一个例子,我们可以清晰的看到委托可以绑定类成员函数的指针,同时它可以解决“类成员函数”与“静态或全局函数”之间的差异,这保证了代码的通用与重用性。
在不使用委托的情况下,我们要保证函数能够获取到与之绑定的TARGET-PTR则只有两个办法;一个是用类函数指针调用,另一种就是保存指针然后在传入到调用函数的参数上或者被调用函数自己去获取,但是这样都会增加强耦合;它意味着代码并不够通用而是一种比较定制化的代码;但恰恰我们遭遇的这种场景是非常多的。
#include "nvm/object.h"
#include "nvm/string.h"
#include "nvm/delegate.h"
#include <tchar.h>
#include <Windows.h>
void Sget(const nvm::String& s, int index)
{
wprintf(_T("Sget: s=%s, index=%d\n"), const_cast<nvm::String&>(s).GetBuffer(), index);
}
class Foo
{
public:
int Key;
public:
void Sget(const nvm::String& s, int index)
{
wprintf(_T("Foo::Sget: self=%p, key=%d, s=%s, index=%d\n"), this, this->Key, const_cast<nvm::String&>(s).GetBuffer(), index);
}
};
int main(int argc, char* argv[])
{
setlocale(LC_ALL, "chs");
Foo foo;
foo.Key = 100;
nvm::Delegate<void(const nvm::String&, int)>* d =
nvm::Delegate<void(const nvm::String&, int)>::Create(&foo, &Foo::Sget);
nvm::DelegateProxifier* proxifier = d->GetProxifier();
d->Invoke("hello", 0);
proxifier->Invoke<void>((const nvm::String&)"world", 1);
d =
nvm::Delegate<void(const nvm::String&, int)>::Create(NULL, Sget);
proxifier = d->GetProxifier();
d->Invoke("hello", 2);
proxifier->Invoke<void>((const nvm::String&)"china", 3);
return getchar();
}
上述代码是使用委托的示例与测试代码,你可以把这串代码加以调整以符合自己的测试工程用以运行;本文不提供完整基于汇编的委托实现,主要是这个东西的耦合性会比较高而且不好用,举个例子:基于汇编的实现,首先就需要搞搞要支持目标平台的汇编,这是第一点;另外就是手算stack_size是非常恶心的,不同的函数调用协议都需要支持,不然委托的调用就会出问题,等等。但归根结底就是一个东西,需要在程式运行时动态的编译一个委托的Stub,在.NET/CLR之中这个东西叫做JIT/Stub;下述放出一段基于汇编的实现的代码。
一段短小精悍的实现:支持X86/X64,不需要手算stack_size,但是某些情况下会有问题。
template<typename TFunction>
inline static void* CreateDelegate(const void* target_, TFunction function_)
{
if (function_ == NULL)
{
return false;
}
union
{
TFunction class_f_;
nvm::Byte* method_f_;
} u;
u.class_f_ = function_;
nvm::Byte* shellcode_ = NULL;
int codelen_ = 10;
if (sizeof(void*) == sizeof(nvm::Int32))
{
shellcode_ = new nvm::Byte[codelen_];
shellcode_[0] = '\xB9';
shellcode_[5] = '\xE9';
nvm::Byte* rva_ = (nvm::Byte*)(u.method_f_ - &shellcode_[5] - 5);
*(void**)&shellcode_[6] = rva_;
*(void**)&shellcode_[1] = (void*)target_;
}
else
{
shellcode_ = new nvm::Byte[(codelen_ = 22)];
shellcode_[0] = '\x48';
shellcode_[1] = '\xB9';
*(void**)&shellcode_[2] = (void*)target_;
shellcode_[10] = '\x48';
shellcode_[11] = '\xB8';
*(void**)&shellcode_[12] = u.method_f_;
shellcode_[20] = '\xFF';
shellcode_[21] = '\xE0';
}
if (!MiniRTM::MakeShellCode(shellcode_, codelen_))
{
delete[] shellcode_;
}
return shellcode_;
}
一段严格正确的实现:支持多个调用协议
enum CallingConvention
{
kThisCall,
kCdeclCall,
kStdCall
};
template<typename TFunction>
inline static void* CreateDelegate_IA32(const void* target_, CallingConvention convention_, int stack_size_, TFunction function_)
{
if (stack_size_ < 0 || function_ == NULL)
{
return NULL;
}
union
{
TFunction x1_;
void* x2_;
} u;
u.x1_ = function_; //&Foo::a;
char* shellcode_ = NULL;
int shellcodelen_ = 0;
if (convention_ == CallingConvention::kThisCall) // __thiscall
{
static char shellcode_template_[] = "\x83\xEC\x04"
"\x8B\x44\x24\x04"
"\x89\x44\x24\x0C"
"\x89\x4C\x24\x04"
"\xC7\x04\x24\x00\x00\x00\x00"
"\xB9\x00\x00\x00\x00"
"\xE9\x00\x00\x00\x00"
"\x83\xC4\x04"
"\xFF\x64\x24\xFC";
shellcodelen_ = sizeof(shellcode_template_);
shellcode_ = new char[shellcodelen_];
memcpy(shellcode_, shellcode_template_, shellcodelen_);
shellcode_[10] = (4 + stack_size_);
*(const void**)&shellcode_[18] = &shellcode_[32];
*(const void**)&shellcode_[23] = target_;
*(const void**)&shellcode_[28] = (void*)((char*)u.x2_ - (&shellcode_[27] + 5));
}
else
{
static char shellcode_template_[] = "\x89\x4C\x24\x04\x8B\x0C\x24\x89\x4C\x24\x08\xC7\x04\x24\x40\x42\x0F\x00\xB9\x00\x00\x00\x00\xE9\x00\x00\x00\x00\x8B\x4C\x24\x04\xFF\x64\x24\x04";
shellcodelen_ = sizeof(shellcode_template_);
shellcode_ = new char[shellcodelen_];
memcpy(shellcode_, shellcode_template_, shellcodelen_);
*(const void**)&shellcode_[14] = &shellcode_[28];
*(const void**)&shellcode_[19] = target_;
*(const void**)&shellcode_[24] = (void*)((char*)u.x2_ - (&shellcode_[23] + 5));
if (convention_ == CallingConvention::kCdeclCall) // __cdecl
{
shellcode_[3] += stack_size_;
shellcode_[10] += stack_size_;
shellcode_[31] = stack_size_ + 8;
shellcode_[35] = stack_size_ + 4;
}
else if (convention_ == CallingConvention::kStdCall) // __stdcall
{
shellcode_[3] += stack_size_;
shellcode_[10] += stack_size_;
shellcode_[35] = '\x04';
shellcode_[31] = '\x00';
}
else
{
return NULL;
}
}
if (!MiniRTM::MakeShellCode(shellcode_, shellcodelen_))
{
delete[] shellcode_;
shellcode_ = NULL;
}
return shellcode_;
}
好了到了本文这里,此处放出基于C/C++可变模板实现的委托,这个实现是最有好的不需要手算stack_size,把大部分工作都交给了目标平台的编译器.,它不对任何的目标平台具有依赖,它只对编译器具有依赖,但显然这样才是正确的做法。
#ifndef DELEGATE_H
#define DELEGATE_H
#include "nvm/object.h"
namespace nvm
{
template <typename T, typename E> class Delegate_metadata_class;
template <typename T, typename R, typename... A>
class Delegate_metadata_class<T, R(A...)>; // sealed
template <typename E> class Delegate_metadata_method;
template <typename R, typename... A>
class Delegate_metadata_method<R(A...)>;
class DelegateProxifier;
template <typename T> class Delegate;
template <typename R, typename... A>
class Delegate<R(A...)> : public nvm::Object
{
private:
void* m_target;
R(*m_callback)(A...);
const void* m_tag;
bool m_placement;
public:
Delegate()
{
Delegate<R(A...)>& i_ = *this;
i_ = *(Delegate<R(A...)>*)NULL;
}
Delegate(const Delegate<R(A...)>& d_)
{
Delegate<R(A...)>& i_ = *this;
i_ = d_;
}
virtual R Invoke(A... args) { };
virtual void*& GetMethod() { };
virtual void*& GetTarget() { };
virtual void SetTag(const void* value) { };
virtual const void* GetTag() { };
virtual bool IsClass() { };
inline bool IsPlacementNew()
{
return this->m_placement;
};
template<typename T>
inline T*& GetTarget()
{
void** p_ = &this->GetTarget();
return *(T**)p_;
}
inline bool IsNull()
{
return Delegate<R(A...)>::IsNull(*this);
}
inline DelegateProxifier* GetProxifier()
{
return (DelegateProxifier*)this;
}
inline Delegate<R(A...)>* Clone()
{
return this;
}
inline Delegate<R(A...)>& operator=(const Delegate<R(A...)>& right_)
{
if (&right_ == NULL)
{
*(void**)this = NULL;
this->m_callback = NULL;
this->m_tag = NULL;
this->m_target = NULL;
this->m_placement = false;
}
else
{
*(void**)this = *(void**)&right_;
this->m_callback = right_.m_callback;
this->m_tag = right_.m_tag;
this->m_target = right_.m_target;
this->m_placement = right_.m_placement;
}
return *this;
}
public:
inline static bool IsNull(const Delegate<R(A...)>& d_)
{
void* i_ = (void*)&d_;
if (i_ == NULL)
{
return true;
}
void* vfbtl = *(void**)i_;
if (vfbtl == NULL)
{
return true;
}
return false;
}
inline static void* GetMethodAddress(R(*callback_)(A...))
{
return (void*)callback_;
}
template<typename T>
inline static void* GetMethodAddress(R(T::* callback_)(A...))
{
union
{
R(T::* x1_)(A...);
void* x2_;
} u_;
return u_.x2_;
}
template<typename T>
inline static Delegate<R(A...)>* Create(T* target_, R(T::* callback_)(A...), Delegate<R(A...)>* placement_ = NULL)
{
if (placement_ == NULL)
{
Delegate_metadata_class<T, R(A...)>* delegate_ = new Delegate_metadata_class<T, R(A...)>(target_, callback_);
return delegate_->GetPointer();
}
else
{
Delegate_metadata_class<T, R(A...)> delegate_(target_, callback_);
*placement_ = *(delegate_.GetPointer());
placement_->m_placement = true;
}
return placement_;
}
inline static Delegate<R(A...)>* Create(void* target_, R(*callback_)(A...), Delegate<R(A...)>* placement_ = NULL)
{
if (placement_ == NULL)
{
Delegate_metadata_method<R(A...)>* delegate_ = new Delegate_metadata_method<R(A...)>(target_, callback_);
return delegate_->GetPointer();
}
else
{
Delegate_metadata_method<R(A...)> delegate_(target_, callback_);
*placement_ = *(delegate_.GetPointer());
placement_->m_placement = true;
}
return placement_;
}
};
class DelegateProxifier : public nvm::Object
{
private:
virtual void* Invoke() { };
public:
template <typename R, typename... A>
inline R Invoke(A... args)
{
void** vftbl = *(void***)this;
union
{
R(DelegateProxifier::*f)(A...);
void* p;
} u_;
u_.p = vftbl[5];
DelegateProxifier& i_ = *this;
return (i_.*(u_.f))(args...);
};
virtual void*& GetMethod() { };
virtual void*& GetTarget() { };
virtual void SetTag(const void* value) { };
virtual const void* GetTag() { };
virtual bool IsClass() { };
template<typename T>
inline T*& GetTarget()
{
void** p_ = &this->GetTarget();
return *(T**)p_;
}
inline DelegateProxifier* Clone()
{
return this;
}
template <typename R, typename... A>
inline Delegate<R(A...)>* GetDelegate()
{
return (Delegate<R(A...)>*)this;
}
};
template <typename E> class Delegate_metadata_method;
template <typename R, typename... A>
class Delegate_metadata_method<R(A...)> : public nvm::Object
{
private:
void* m_target;
R(*m_callback)(A...);
const void* m_tag;
friend class Delegate<R(A...)>;
Delegate_metadata_method(void* target_, R(*callback_)(A...))
{
this->m_tag = NULL;
this->m_target = target_;
this->m_callback = callback_;
}
public:
inline virtual R Invoke(A... args)
{
return this->m_callback(args...);
}
inline virtual void*& GetMethod()
{
return *(void**)&this->m_callback;
}
inline virtual void*& GetTarget()
{
return this->m_target;
}
inline virtual void SetTag(const void* value)
{
this->m_tag = value;
}
inline virtual const void* GetTag()
{
return this->m_tag;
}
virtual bool IsClass()
{
return false;
}
inline Delegate<R(A...)>* GetPointer()
{
return (Delegate<R(A...)>*)this;
}
};
template <typename T, typename E> class Delegate_metadata_class;
template <typename T, typename R, typename... A>
class Delegate_metadata_class<T, R(A...)> : public nvm::Object
{
private:
T * m_target;
R(T::*m_callback)(A...);
const void* m_tag;
friend class Delegate<R(A...)>;
Delegate_metadata_class(T* target_, R(T::*callback_)(A...))
{
this->m_tag = NULL;
this->m_target = target_;
this->m_callback = callback_;
}
public:
inline virtual R Invoke(A... args)
{
T& i_ = *this->m_target;
return (i_.*this->m_callback)(args...);
}
inline virtual void*& GetMethod()
{
union
{
R(T::**x1_)(A...);
void** x2_;
} u;
u.x1_ = &this->m_callback;
return *u.x2_;
}
inline virtual T*& GetTarget()
{
return this->m_target;
}
inline virtual void SetTag(const void* value)
{
this->m_tag = value;
}
inline virtual const void* GetTag()
{
return this->m_tag;
}
virtual bool IsClass()
{
return true;
}
inline Delegate<R(A...)>* GetPointer()
{
return (Delegate<R(A...)>*)this;
}
};
}
#endif
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)