问题
this is sort of difficult to explain but I'll do my best. I will pose the question as it applies to one method, but know that this solution should be generic enough to apply to any possible method given. Consider the following:
I have a regular old global method:
void MyMethod( int a, int b )
{
cout << a << " , " << b << endl;
}
I have another method that is to call this method.
void Caller( void* method, void* data, int size )
{
// convert method to some function calling convention
// call the method here with the given data
}
This caller should be able to call any method internally without knowing how many parameters it takes and what their data types are. All it really knows about the method is the address to the method and the size, in bytes, of the entire parameter list.
So simply stated, how can I call an arbitrary method and pass it an arbitrary amount of data for it to interpret as parameters?
Essentially, without modifying MyMethod
, how do I push the void* data
into the registers used as parameters from within Caller
? Is this possible? I'm not concerned about safety or portability.
Before Caller
is called, I have an array of void*'s that point to the data that can be passed to the internally called method. I'm not sure if this is even the best approach to this problem.
I'm writing a scripting system, in essence, that can call methods from the script. So the methods are stored in a lookup table, where each is given a string name, and have a void* to the actual method to be called. At execution time I know how many parameters the method expects and what types the parameters are (the types are stored as metadata when the method is given an entry in the lookup table). This allows me to convert the string values that are the parameters in the script to the values they actually should be (using a custom conversion system). But the converter returns a void*, because you call it as such:
string s = "123456";
void* result = Converter::Convert( "string*", "int", &s );
I can guarantee that the value stored in result
is actually of the requested type (if a converter for this type-pair exists), but have no way of casting to this type, as the type name is merely provided as a string. This makes the converter flexible and really type-indifferent. But it makes handling the values that it returns complicated. So in script I would make a call like this:
MyMethod( 111, 222 )
This would then be parsed, the method name would be used to look up the method address, and the converter would then convert the values it finds into the expected datatypes, but return them as void*
. Then a call to Caller would be made, passing in the method address, arguments it has converted, as an array of bytes, and the size of the array of parameter data, in bytes. It is at this point that I need to call that method and pass these parameters. Again, I cannot modify the existing methods it is calling.
I've looked into assembly to pass this data in, but it seems that you have to either make the method naked to read parameters directly in assembly or do something else, and I've never really worked in assembly before. Although if the solution lies in assembly, I'm fine with learning some.
Please don't downvote for ambiguity; if you need more context I can provide it. Just comment. I really appreciate it.
回答1:
Changing the implementation details slightly, here is how to do what you want
#include <iostream>
#include <boost/any.hpp>
#include <vector>
#include <functional>
#include <map>
#include <string>
using namespace std;
template<class F>
struct ConstructCaller{};
template<class T, int i>
struct TypeAndInt
{
enum{idx = i};
typedef T type;
};
template<class... T>
struct TypeList{};
template<class A, class B>
struct CombineTypeList{};
template<class... T1, class... T2>
struct CombineTypeList<TypeList<T1...>, TypeList<T2...>>
{
typedef TypeList<T1..., T2...> type;
};
template<int idx, class... T>
struct ToTypeAndIntList{
};
template<int idx,class T0, class T1, class... T>
struct ToTypeAndIntList<idx, T0,T1,T...>{
typedef typename CombineTypeList<TypeList<TypeAndInt<T0, idx> >, typename ToTypeAndIntList<idx+1,T1,T...>::type>::type type;
};
template<int idx, class T0>
struct ToTypeAndIntList<idx,T0>{
typedef TypeList < TypeAndInt<T0, idx> > type;
};
template<class... P>
struct ConstructCaller<void(*)(P...)>
{
typedef void(*FuncType)(P...);
FuncType f_;
template<class T>
typename T::type Get(const vector<boost::any>& vec){
return boost::any_cast<typename T::type>(vec.at(T::idx));
}
template<class... TI>
void DoCall(TypeList<TI...>, const vector<boost::any>& vec){
return f_(Get<TI>(vec)...);
}
void operator()(const vector<boost::any>& vec){
typedef typename ToTypeAndIntList<0, P...>::type List_t;
return DoCall(List_t{}, vec);
}
};
std::map < std::string, std::function<void(const std::vector<boost::any>&)>> func_map;
template<class F>
void RegisterFunction(std::string name, F f){
ConstructCaller<F> c;
c.f_ = f;
func_map[name] = c;
}
void MyMethod(int a, int b)
{
cout << a << " , " << b << endl;
}
void MyMethod2(std::string a, int b)
{
cout << a << " , " << b << endl;
}
int main(){
RegisterFunction("MyMethod", &MyMethod);
RegisterFunction("MyMethod2", &MyMethod2);
std::vector<boost::any> vec;
vec.push_back(1);
vec.push_back(2);
func_map["MyMethod"](vec);
vec.clear();
vec.push_back(std::string("Hello World"));
vec.push_back(2);
func_map["MyMethod2"](vec);
}
Note as presented here, this only works with global functions with a void return type. This solution also uses boost::any which can store any type, and from which you can extract the type later. Thus to use it register your functions. Then created a vector of boost::any and put in your arbitrary values into the vector. Then look up the function name and call like in the example main.
Let me know if you have any questions.
来源:https://stackoverflow.com/questions/20528100/call-method-without-specifying-parameters