最近在玩的一款游戏支持内嵌C#语言完成高级自定义功能,在游戏外编写时需要import指定的DLL。在Visual Studio中试了一下,发现确实能够编写。另外,每当我import之后,Visual Studio就会进行parse,之后便可以获得方法的详细信息进行提示(智能提示窗)。对此我很好奇,首先尝试解析DLL文件,发现不是有效的PE文件(尴尬...)...然后,就没有然后了,愉快的开始了游戏内代码的测试。
一番测试下来,忽然想到为什么C++只能include不能import呢?
折腾一番之后,得到两个宏
#define import(DynamicLibraryName) do{ \
int ret=_cpplib_buildin_namespace::dllhelper.addDLL(DynamicLibraryName);\
if(ret<0)\
throw runtime_error("Cannot Find DynamicLibrary: " DynamicLibraryName); \
}while(0)
#define getfunc(ReturnType,FunctionName,ArgsList...) ReturnType(*FunctionName)(##ArgsList)=NULL;do{ \
FunctionName=(ReturnType(*)(##ArgsList))_cpplib_buildin_namespace::dllhelper.getproc(#FunctionName); \
if(FunctionName==NULL) \
throw runtime_error("Cannot Find Function: " #FunctionName); \
}while(0)
很明显,import用于引进DLL文件,getfunc用于引进方法
测试如下
int main()
{
import("test.dll");
getfunc(int,HelloWorld);
int ret=HelloWorld();
printf("%d\n",ret);
return 0;
}
其中test.dll中方法定义如下
extern "C"{
int DLL_EXPORT HelloWorld()
{
printf("Hello World!\n");
return 5;
}
}
编译-运行成功!
不过这两个宏还是不够灵活,首先import和getfunc不能用在函数外,其次如果import或getfunc失败会抛出异常,而异常是运行时才能检测到的。相比之下C#都是在编译期间完成二者的检查。
==================分割线==================
刚才我忘记了Lambda这个神奇的东西!使用Lambda之后就可以在函数外部进行初始化啦!这样就和C#的import非常类似了。
实现方法如下
#define import(DynamicLibraryName,Label) \
unique_ptr<_cpplib_buildin_namespace::dlibctrl_manager> Label=[]()->unique_ptr<_cpplib_buildin_namespace::dlibctrl_manager>\
{\
dlibctrl* p=new dlibctrl;\
int ret=p->open(DynamicLibraryName);\
if(ret<0)\
{\
delete p;\
return NULL;\
}\
_cpplib_buildin_namespace::dlibctrl_manager* manager=new _cpplib_buildin_namespace::dlibctrl_manager;\
manager->dbptr=p;\
_cpplib_buildin_namespace::dlibctrl_center.add(manager);\
return unique_ptr<_cpplib_buildin_namespace::dlibctrl_manager>(manager);\
}()
#define getfunc(ReturnType,FunctionName,Args...) ReturnType(*FunctionName)(##Args)=[]()->ReturnType(*)(##Args)\
{\
void* p=NULL;\
if((p=_cpplib_buildin_namespace::dlibctrl_center.find(#FunctionName))==NULL)\
{\
return NULL;\
}\
return (ReturnType(*)(##Args))p;\
}()
#define getfuncin(Label,ReturnType,FunctionName,Args...) ReturnType(*FunctionName)(##Args)=[]()->ReturnType(*)(##Args)\
{\
void* p=NULL;\
if((p=_cpplib_buildin_namespace::dlibctrl_center.findin(#FunctionName,Label.get()))==NULL)\
{\
return NULL;\
}\
return (ReturnType(*)(##Args))p;\
}()
相比于之前那种实现方法,import多了一个Label。需要保证Label是全局唯一的。
这种实现方法可以在内部import,这样import便有了作用域。同样的,getfunc和getfuncin也有了作用域。非常棒!
额外需要定义一些东西
namespace _cpplib_buildin_namespace
{
/// DECL
class dlibctrl_manager
{
public:
dlibctrl* dbptr;
~dlibctrl_manager();
};
class _dlibctrl_center_class
{
public:
void add(dlibctrl_manager* ptr)
{
m.lock();
vec.push_back(ptr);
m.unlock();
}
int del(dlibctrl_manager* ptr)
{
m.lock();
int sz=vec.size();
for(int i=0;i<sz;i++)
{
if(vec.at(i)==ptr)
{
vec.erase(vec.begin()+i);
m.unlock();
return 0;
}
}
m.unlock();
return -1;
}
void* find(const char* name)
{
m.lock();
for(auto& ptr:vec)
{
void* p;
if((p=ptr->dbptr->getproc(name))!=NULL)
{
m.unlock();
return p;
}
}
m.unlock();
return NULL;
}
void* findin(const char* name,dlibctrl_manager* incptr)
{
m.lock();
bool found=false;
for(auto& ptr:vec)
{
if(ptr==incptr)
{
found=true;
break;
}
}
if(!found)
{
m.unlock();
return NULL;
}
void* p=incptr->dbptr->getproc(name);
m.unlock();
return p;
}
private:
vector<dlibctrl_manager*> vec;
mutex m;
}dlibctrl_center;
///IMPL
dlibctrl_manager::~dlibctrl_manager()
{
dlibctrl_center.del(this);
delete dbptr;
}
}
dlibctrl_center存储所有manager,并提供基于mutex的线程安全的添加与删除操作
dlibctrl_manager用于包装dlibctrl,并在析构的时候从center中删去自身.测试代码如下:
import("test.dll",x);
getfunc(int,HelloWorld);
int main()
{
int ret=HelloWorld();
printf("%d\n",ret);
return 0;
}
编译~运行 成功。