这一章介绍脚本插件的编写,现在才是划重点的时候了。angelscrript的作者提供了很完整的文档,由于本人英语有限,我错误恳请指出。angelscript简直就是给C++程序量身订造的脚本语言,和C++语言基本无差别,还是那句话,可惜没有流行起来。不说闲话,开始上代码。
先定义两个导出函数。供duilib回调创建脚本引擎和删除脚本引擎。
extern "C" __declspec(dllexport) IScriptEngine* __stdcall CreateScriptEngine()
{
return new CScriptEngine;
}
extern "C" __declspec(dllexport) void __stdcall DeleteScriptEngine(IScriptEngine *pEngine)
{
if(pEngine)
{
delete (CScriptEngine *)pEngine;
pEngine = NULL;
}
}
增加一个CScriptEngine类,继承接口IScriptEngine,嗯,其实就那个纯虚类。
ScriptEngine.h
class CScriptEngine : public IScriptEngine
{
public:
CScriptEngine(void);
virtual ~CScriptEngine(void);
virtual bool AddScriptCode(LPCTSTR pScriptCode);
virtual bool AddScriptFile(LPCTSTR pstrFileName);
virtual bool ExecuteScript(LPCTSTR funName, CControlUI *pControl);
virtual bool ExecuteScript(LPCTSTR funName, CControlUI *pControl, TEventUI *ev);
protected:
void MessageCallback(const asSMessageInfo &msg);
private:
asIScriptEngine *engine;
asIScriptContext *ctx;
int m_nModuleCount;
CStdStringPtrMap m_mapContent;
};
//ScriptEngine.cpp
static void ScriptLineCallback(asIScriptContext *ctx, DWORD *timeOut)
{
// If the time out is reached we abort the script
if( *timeOut < timeGetTime() )
ctx->Abort();
}
//
//CScriptEngine
CScriptEngine::CScriptEngine(void) : m_nModuleCount(0)
{
ctx = NULL;
engine = asCreateScriptEngine();
int r = 0;
r = engine->SetMessageCallback(asMETHOD(CScriptEngine, MessageCallback), this, asCALL_THISCALL); assert( r >= 0 );
//脚本代码的字符编码 0 - ASCII, 1 - UTF8. Default: 1 (UTF8).
r = engine->SetEngineProperty(asEP_SCRIPT_SCANNER, 0); assert( r >= 0 );
#ifdef _UNICODE
//脚本内部字符串的字符编码 0 - UTF8/ASCII, 1 - UTF16. Default: 0 (UTF8)
r = engine->SetEngineProperty(asEP_STRING_ENCODING, 1); assert( r >= 0 );
#endif
CScriptRegister reg(engine);
reg.RegisterAll();
}
CScriptEngine::~CScriptEngine(void)
{
if(ctx)
{
ctx->Release();
ctx = NULL;
}
}
void CScriptEngine::MessageCallback(const asSMessageInfo &msg)
{
if( msg.type == asMSGTYPE_ERROR )
{
ATL::CStringA temp;
temp.Format("row = %d\r\ncol = %d\r\nsection=%s \r\nmessage = %s\r\n",
msg.row, msg.col, msg.section, msg.message);
MessageBoxA(NULL, temp, "complie error", MB_OK);
}
}
bool CScriptEngine::AddScriptCode(LPCTSTR pScriptCode)
{
m_nModuleCount++;
char module[255];
sprintf(module, "module%d", m_nModuleCount);
int r = 0;
CScriptBuilder builder;
r = builder.StartNewModule(engine, module);
if( r < 0 )
return false;
LSSTRING_CONVERSION;
builder.AddSectionFromMemory("section1", LST2A(pScriptCode));
r = builder.BuildModule();
if( r < 0 ) return false;
//保存脚本函数地址
asIScriptModule *pModule = builder.GetModule();
int funCount = pModule->GetFunctionCount();
for (int i=0; i<funCount; i++)
{
asIScriptFunction *pFun = pModule->GetFunctionByIndex(i);
USES_CONVERSION;
m_mapContent.Set( A2T((LPSTR)pFun->GetName()), pFun);
}
return true;
}
bool CScriptEngine::AddScriptFile(LPCTSTR pstrFileName)
{
LPBYTE pData = NULL;
DWORD dwSize = CRenderEngine::LoadImage2Memory(STRINGorID(pstrFileName), 0, pData);
if(dwSize == 0U || !pData)
return false;
bool rbool = false;
rbool = AddScriptCode((LPCTSTR)pData);
CRenderEngine::FreeMemory(pData);
return rbool;
}
bool CScriptEngine::ExecuteScript(LPCTSTR funName, CControlUI *pControl)
{
asIScriptFunction *pFun = static_cast<asIScriptFunction *>(m_mapContent.Find(funName));
if(!pFun) return false;
if(!ctx) ctx = engine->CreateContext();
DWORD dwTime = timeGetTime() + 5000; //设置脚本运行超时时间
int r = ctx->SetLineCallback(asFUNCTION(ScriptLineCallback), &dwTime, asCALL_CDECL); if( r < 0 ) return false;
r = ctx->Prepare(pFun); if( r < 0 ) return false;
//传入入口参数
r = ctx->SetArgObject(0, pControl); if( r < 0 ) return false;
r = ctx->Execute();
if(r == asEXECUTION_FINISHED)
{
return true;
}
else if( r == asEXECUTION_EXCEPTION )
{
CStringA temp;
temp.Format("Exception:%s \r\nFunction: %s \r\nLine: %d",
ctx->GetExceptionString(),
ctx->GetExceptionFunction()->GetDeclaration(),
ctx->GetExceptionLineNumber());
MessageBoxA(NULL, temp, "script error", MB_OK);
return false;
}
return true;
}
bool CScriptEngine::ExecuteScript(LPCTSTR funName, CControlUI *pControl, TEventUI *ev)
{
asIScriptFunction *pFun = static_cast<asIScriptFunction *>(m_mapContent.Find(funName));
if(!pFun) return false;
if(!ctx) ctx = engine->CreateContext();
DWORD dwTime = timeGetTime() + 5000; //设置脚本运行超时时间
int r = ctx->SetLineCallback(asFUNCTION(ScriptLineCallback), &dwTime, asCALL_CDECL); if( r < 0 ) return false;
r = ctx->Prepare(pFun); if( r < 0 ) return false;
//传入入口参数
r = ctx->SetArgObject(0, pControl); if( r < 0 ) return false;
r = ctx->SetArgObject(1, ev); if( r < 0 ) return false;
r = ctx->Execute();
if(r == asEXECUTION_FINISHED)
{
return true;
}
else if( r == asEXECUTION_EXCEPTION )
{
CStringA temp;
temp.Format("Exception:%s \r\nFunction: %s \r\nLine: %d",
ctx->GetExceptionString(),
ctx->GetExceptionFunction()->GetDeclaration(),
ctx->GetExceptionLineNumber());
MessageBoxA(NULL, temp, "script error", MB_OK);
return false;
}
return true;
}
这里面用到了一个类CScriptRegister,注册duilib的各种控件到脚本引擎中,我还没有找到angelscript注册父类的方法,所以需要把脚本中需要调用的控件成员函数都注册进去,包括它的父类函数,要用到的函数都要注册进去。不仅如此,还需要注册比如RECT,SIZE等结构体,define的常量,枚举类型enum,这里面将会有巨大的烦躁的重复性的工作。有这精力,我不如直接在程序中自定义控件了是不是???唉,话不是这么说的。打个比方,如果duilib要做到语言无关化呢?如果可以在脚本中自定义控件,是不是很爽?
我们先把CControlUI注册进去,只注册一个成员函数SetBkColor()。
//
CControlUI *CControlUI_Ref_Factory()
{
// The class constructor is initializing the reference counter to 1
return new CControlUI();
}
bool CRegCControlUI::Register(asIScriptEngine *engine)
{
int r =0;
//Registering DuiLib Class
r = engine->RegisterObjectType("CControlUI", 0, asOBJ_REF|asOBJ_NOCOUNT);
// Registering the factory behaviour
r = engine->RegisterObjectBehaviour("CControlUI", asBEHAVE_FACTORY, "CControlUI@ f()", asFUNCTION(CControlUI_Ref_Factory), asCALL_CDECL); assert( r >= 0 );
// Register class method functions
r = engine->RegisterObjectMethod("CControlUI", "void SetBkColor(int dwBackColor)", asMETHOD(CControlUI, SetBkColor), asCALL_THISCALL); assert( r >= 0 );
return r >= 0;
}
好了。大功告成!
代码共享地址: