1.简述
hotfox是程序框架,由插件提供具体的应用入口,登录实现和业务主窗口.具体的应用业务逻辑由相应的插件实现。
除hotfox外,下文所有模块的代码在SVN上的nxclient目录下.以后除供应宝(合适的时候再迁移)以外的新的客户端程序全部采用本文所说的方法.
Frame1是一个常见的应用模式的实现插件.
logina是一个风格简单的登录模块.
AppEntryStyle1是一个动态构造菜单系统的模块,可以作为应用的主界面插件提供者.
GDSN_Supplier是一个具体应用的插件,该插件提供主窗口.
以下用示例描述这些组件如何组合构造不同的应用(未包含实际应用需要的其它模块)
GGOM应用的构造:
.hotfox
.Frame1
.logina
.GDSN_Supplier:GGOM客户端插件
单据监控应用构造:
.hotfox
.Frame1
.logina
.AppEntryStyle1
.dxm_c: 单据监控客户端插件
由于AppEntryStyle1是自绘风格的,通过宏USE_OWNERDRAW的控制保证应用模块风格的一致(如复用本地管理的模块).
如对TFrmOwnerDrawCommon的定义如下:
#ifdef USE_OWNERDRAW
#include "TFrmOwnerDrawBase.h"
class CSDK_FORMPUBLIC_API TFrmOwnerDrawCommon:public TFrmOwnerDrawBase{
public:
__fastcall TFrmOwnerDrawCommon(TComponent* Owner, bool load_res = true);
void __fastcall LoadFormRes();
protected:
void __fastcall WndProc(TMessage& Message);
};
#else
#include "BaseForm1.h"
typedef TfrmBase1 TFrmOwnerDrawCommon;
#endif
2.实现逻辑
2.1 hotfox
hotfox主程序代码如下:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
g_cfg_info_.Load();
Application->HelpFile = ExtractFilePath(Application->ExeName) + g_cfg_info_.help_file_;
///< 设置为非NT服务方式
HOTFOX::instance()->set_service_mode(false);
HTX_LOGGER::instance()->sync_flag(true);
HTX_LOGGER::instance()->hook_verbose(SEVERITY_DEBUG);
HTX_LOGGER::instance()->set_callback(LoggerCallback,g_cfg_info_.show_startup_ ? (void*)1:(void*)2);
if (g_cfg_info_.show_startup_) {
frmStartup = new TfrmStartup(NULL);
frmStartup->Show();
}
else {
wait_form = TfrmWaitForm::NewWaitForm(0,"启动程序");
}
///< 启动hotfox
if (HOTFOX::instance()->svc()) {
if (g_cfg_info_.show_startup_)
delete frmStartup;
if (last_error.IsEmpty()) {
last_error = "程序启动失败,未描述的错误." ;
}
ShowMessage(last_error);
return -1;
}
Application->Title = HOTFOX::instance()->server_info_.app_name_.c_str();
if (g_cfg_info_.show_startup_) {
::PostMessage(frmStartup->Handle,WM_CLOSE,0,0);
Application->ProcessMessages();
}
else {
delete wait_form;
}
HTX_LOGGER::instance()->sync_flag(HOTFOX::instance()->server_info_.sync_log_);
HTX_LOGGER::instance()->reset_callback(); ///< @note 本程序不支持插件日志挂钩
AppEntryFunc app_entry = HOTFOX::instance()->GetAppEntry();
short ok_flag = 1;
CleanupFunc fp = 0;
if (app_entry) {
HTX_LOGGER::instance()->log(LO_STDOUT|LO_FILE,SEVERITY_INFO,"调用应用入口...\n");
if ((*app_entry)(0,&fp)) {
///< 应用入口执行失败或者不需要继续执行(如需要升级), 则程序不进程循环,直接结束退出
ok_flag = 0;
}
HTX_LOGGER::instance()->log(LO_STDOUT|LO_FILE,SEVERITY_INFO,"调用应用入口%s.\n",ok_flag ? "成功":"失败");
}
if (ok_flag)
Application->Run();
if (fp)
(*fp)();
HOTFOX::instance()->stop();
HOTFOX::instance()->wait();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
2.2 Frame1
Frame1是一种应用入口,是应用客户端的常见形式:---用户执行登录操作
---升级检测和启动升级
---登录成功后打开应用主窗口
EZEntry函数是Frame1向hotfox登记的入口函数.
在插件Initialize时登记:
mgr_->SetAppEntry(EZEntry);
Frame1调用运行时初始化,提供释放操作由hotfox在结束前调用.
int EZEntry(void *arg,CleanupFunc *fp) {
///< 在hotfox中设置Application->HelpFile为什么不起作用? ()
// AnsiString FileName = ExtractFilePath(Application->ExeName) + "ggom.chm";
// Application->HelpFile = FileName;
///< 设置应用窗口Icon.
if (!CEZMainPlugin::instance()->app_icon_file_.empty()) {
HICON old_icon = NULL;
TIcon *Icon = new TIcon();
Icon->LoadFromFile(CEZMainPlugin::instance()->app_icon_file_.c_str());
old_icon = CPluginHelper::SetIcon(2,Icon->Handle); ///< id=2是窗口基类用来指定窗口Icon的id
if (old_icon)
FreeResource(old_icon);
Icon->ReleaseHandle();
delete Icon;
///< 如果不执行下行代码,则frmLogin的窗体的ICON为默认的.(为什么会这样?)
Application->Icon->LoadFromFile(CEZMainPlugin::instance()->app_icon_file_.c_str());
}
CPluginHelper::Init();
CBasePlugInModule::sc_->set_callback(SyncCallCallback);
LoginFunc login_fp = CEZMainPlugin::instance()->mgr_->GetLoginFunc();
if (login_fp) {
int ret = (*login_fp)();
if (ret==1) { ///< 检查是否需要升级
CBasePlugInModule::nlogger_->log(LO_FILE|LO_STDOUT,SEVERITY_DEBUG,"开始启动客户端升级程序\r\n");
LJShellExecute("CltLiveUpdate.exe", "1", "open", ExtractFilePath(Application->ExeName).c_str());
CBasePlugInModule::nlogger_->log(LO_FILE|LO_STDOUT,SEVERITY_DEBUG,"启动客户端升级程序结束\r\n");
CPluginHelper::Cleanup();
return 1; ///< 需要升级
}
if (ret==-1) {
CPluginHelper::Cleanup(); ///< @note 如果不执行,则会导致程序退出时异常. CBaseClientModule::g_async_fc_form
return -1;
}
}
INative_Logger_Base *log = CEZMainPlugin::instance()->nlogger_;
bool sync_flag = log->sync_flag();
log->sync_flag(true);
log->hook_verbose(SEVERITY_DEBUG);
log->set_callback(LoggerCallback,CEZMainPlugin::instance()->show_startup_ ? (void*)1:(void*)2);
if (CEZMainPlugin::instance()->show_startup_) {
frmStartup = new TfrmStartup(NULL);
frmStartup->Show();
}
CPluginHelper::ShareResource(CachedData::instance(), CACHEDDATA_INFO, NULL);
///<同步调用获取过时的数据
int ret = CachedData::instance()->GetExpiredDataVersion();
if( ret ){
AnsiString err_str = AnsiString("获取数据版本失败,错误码")+ret;
MessageShow(NULL,err_str.c_str(),"系统提示",MSGERROR);
if (CEZMainPlugin::instance()->show_startup_)
::PostMessage(frmStartup->Handle,WM_CLOSE,0,0);
CPluginHelper::Cleanup();
return -1;
}
///< 登录成功,执行各插件的登录后操作(如从服务器下载数据)
if (CBasePluginModule::plugin_mgr_->OnLogin()) {
MessageShow(NULL,"登录处理失败","系统提示",MSGERROR);
if (CEZMainPlugin::instance()->show_startup_)
::PostMessage(frmStartup->Handle,WM_CLOSE,0,0);
CPluginHelper::Cleanup();
return -1;
}
if (CEZMainPlugin::instance()->show_startup_) {
::PostMessage(frmStartup->Handle,WM_CLOSE,0,0);
Application->ProcessMessages();
}
log->reset_callback();
log->sync_flag(sync_flag); ///< 恢复日志的同步标志
CEZMainPlugin::instance()->NotifyUserReady(); ///< 通知服务器客户端已就绪(可以接收服务器主动推送的消息)
AppMainWindowFunc main_wnd_entry = CEZMainPlugin::instance()->mgr_->GetAppMainWindowEntry();
if (main_wnd_entry==0) { ///< 未指定启动的主窗口
CPluginHelper::Cleanup();
return -1;
}
(*main_wnd_entry)(0);
*fp = &Cleanup;
return 0;
}
2.3 logina
logina插件实现了一种常用的登录模式.
风格简单.支持本地服务器和服务器查找,支持网络设置.
///< 登录函数
int Login() {
TfrmLogin *frmLogin = new TfrmLogin(NULL);
int result = frmLogin->ShowModal();
///< 登录过程包含一个版本比较操作,决定是否需要升级
if (frmLogin->GetUpdateFlag()) { ///< 检查是否需要升级
delete frmLogin;
return 1; ///< 需要升级
}
if (result!=mrOk) {
delete frmLogin;
return -1;
}
CLoginA::instance()->first_logon_ = false; ///< 已经登录过,以后连接断开只需要重新登录(597协议)
CLoginA::instance()->connector_ = frmLogin->GetConnector();
delete frmLogin;
return 0;
}
登录模块的登记是在Prepare中完成的.
int CLoginA::Prepare() {
parent::Prepare();
mgr_->SetLoginFunc(Login); ///< 由本插件提供登录入口
return 0;
}
2.4 AppEntryStyle1
AppEntryStyle1插件从供应宝业务主窗口改造而来,具有以下特性:.从服务器获取菜单信息,动态构造菜单
.具有权限控制
.响应菜单操作启动相应功能
int Main_Wnd(void *arg) {
///< 启动业务窗口
TBusinessMainForm *frmBiz;
Application->CreateForm(__classid(TBusinessMainForm), &frmBiz);
return 0;
}
int CAppEntryStyle1::Initialize() {
parent::Initialize();
mgr_->SetAppMainWindowEntry(Main_Wnd);
return 0;
}
2.5 一个应用:GDSN_Supplier
GDSN_Supplier插件是实现GGOM功能的客户端插件.在GGOM应用中作为主窗口的提供者.
///< 应用主窗口函数
int Main_Wnd(void *arg) {
AnsiString FileName = ExtractFilePath(Application->ExeName) + "ggom.chm";
Application->HelpFile = FileName;
if (UserIsAdmin()) { ///< 管理员模式
TfrmSystemManage *frmMain;
Application->CreateForm(__classid(TfrmSystemManage), &frmMain);
}
else { ///< 非管理员模式
TFrmMainGDSNSup *frmMain;
Application->CreateForm(__classid(TFrmMainGDSNSup), &frmMain);
}
return 0;
}
主窗口函数在Initialize时登记.
int CAppEntryStyle1::Initialize() {
parent::Initialize();
mgr_->SetAppMainWindowEntry(Main_Wnd);
return 0;
}