可扩展的报表编辑器





5.00/5 (10投票s)
一个可扩展的报表编辑器。你可以简单地添加你自己的控件,而无需重新编译程序或编写烦人的插件。
引言
创建报表或对话框是大多数应用程序的常见部分,但对于许多开发者来说过于复杂。如果你是 Microsoft Visual Studio 用户,你很幸运,你可以将控件从控件面板拖动到你的对话框,代码同时生成。但是其他平台呢?如何为你自己的控件生成代码?我们只能说:对不起,你必须手动编写所有代码。这就是我推出这个可扩展工具的原因;你可以简单地添加你自己的控件,而无需重新编译程序或编写烦人的插件。
背景
使用 C++ 作为你的脚本语言是个好主意,但它确实很困难,因为 C++ 语法太复杂而难以实现。世界上有很多优秀的 C++ 编译器,VC++ 6.0 是我最常用的。我的想法是首先使用 C++ 编译器将 .c 或 .cpp 源代码转换为 .asm 文件,然后使用汇编器将 .asm 文件转换为二进制文件。你可以将此二进制文件加载到你的程序中,并让它在一个小型虚拟机上运行。这个程序包含一个小型虚拟机,可以执行许多 i386 指令。
特点
- 支持 C++ 脚本语言。
- 完整的鼠标处理。绘制和拖动多个对象,以及调整多个对象的大小。
- 捕捉到网格。
- 组合或取消组合多个对象。
- 易于扩展,无需重新编译或使用插件。
如何添加你自己的控件
添加你自己的控件很容易,你不需要重新编译它,你只需要一个文本编辑器。记事本就可以了,但我强烈建议使用 XML 编辑器。我使用 XmlBluePrint,这是一个非常好的 XML 编辑器。你可以访问 http://www.xmlblueprint.com/ 获取它。
单个节点的结构
<Test isnode="true">
<code>
<![CDATA[
//your can add your global variables here
int global;
int code(int code_num)
{
//when you click 'code' button on tool bar, this function will be called.
//this function will be called 10 times, code_num equals number of times.
printf("test code");
return OK;
}
int before_draw()
{
//this function will be called before draw all contorls
return OK;
}
int draw()
{
//how to draw this contorl.
return OK;
}
]]>
</code>
<default>
<![CDATA[
//code here will be put into _init_attrib() function
global = 100;
]]>
</default>
</Test>
单个控件节点必须放在 AllNode
下。 务必确保 isnode="true"
,否则你将看不到树视图中的此节点。 完成一个节点后,保存 XML 文件,然后再次打开此程序,你将在树视图中看到一个新节点。
什么是虚拟机?
在我看来,虚拟机是一个软件计算机。它可以非常简单或非常复杂。一个复杂的虚拟机可以让像 Windows 或 Linux 这样的现代操作系统在它上面运行。一个简单的虚拟机只能包含一个简单的软 CPU 和一个简单的软内存。此程序中的虚拟机属于后一类。
使用虚拟机
一个程序可以被看作是一系列指令,CPU 所做的只是获取下一个指令并解释它,所以在 C++ 中一个典型的 CPU 模拟器框架是
this->vm->LoadFile(pobj->mf_bin); //Load binary image
//run it
while(!this->vm->end_flag)
{
this->vm->GetNextOper();
this->vm->ExeOper();
}
ExeOper()
用于解释每个指令;以下是源代码
int CVm::ExeOper()
{
int oper = *((WORD*)(this->cur_oper));
switch(oper)
{
case OPER_ADC:do_adc();break;
case OPER_ADD:do_add();break;
case OPER_AND:do_and();break;
case OPER_CALL:do_call();break;
case OPER_CDQ:do_cdq();break;
case OPER_CMP:do_cmp();break;
case OPER_CMPSB:do_cmpsb();break;
case OPER_CMPSD:do_cmpsd();break;
case OPER_CMPSW:do_cmpsw();break;
case OPER_DEC:do_dec();break;
case OPER_DIV:do_div();break;
case OPER_FADD:do_fadd();break;
case OPER_FCOS:do_fcos();break;
case OPER_FILD:do_fild();break;
case OPER_FLD:do_fld();break;
case OPER_FMUL:do_fmul();break;
case OPER_FSIN:do_fsin();break;
case OPER_FST:do_fst();break;
case OPER_FSTP:do_fstp();break;
case OPER_FSUB:do_fsub();break;
case OPER_FXCH:do_fxch();break;
case OPER_IDIV:do_idiv();break;
case OPER_IMUL:do_imul();break;
case OPER_INC:do_inc();break;
case OPER_INT:do_int();break;
case OPER_JA:do_ja();break;
case OPER_JB:do_jb();break;
case OPER_JBE:do_jbe();break;
case OPER_JE:do_je();break;
case OPER_JG:do_jg();break;
case OPER_JGE:do_jge();break;
case OPER_JL:do_jl();break;
case OPER_JLE:do_jle();break;
case OPER_JMP:do_jmp();break;
case OPER_JNE:do_jne();break;
case OPER_JNS:do_jns();break;
case OPER_JS:do_js();break;
case OPER_LEA:do_lea();break;
case OPER_MOV:do_mov();break;
case OPER_MOVSB:do_movsb();break;
case OPER_MOVSD:do_movsd();break;
case OPER_MOVSW:do_movsw();break;
case OPER_MOVSX:do_movsx();break;
case OPER_MUL:do_mul();break;
case OPER_MYADD:do_myadd();break;
case OPER_MYDIV:do_mydiv();break;
case OPER_MYMUL:do_mymul();break;
case OPER_MYSUB:do_mysub();break;
case OPER_NEG:do_neg();break;
case OPER_NOT:do_not();break;
case OPER_NPAD:do_npad();break;
case OPER_OR:do_or();break;
case OPER_POP:do_pop();break;
case OPER_PUSH:do_push();break;
case OPER_REP:do_rep();break;
case OPER_REPNE:do_repne();break;
case OPER_RET:do_ret();break;
case OPER_SAR:do_sar();break;
case OPER_SBB:do_sbb();break;
case OPER_SCASB:do_scasb();break;
case OPER_SCASD:do_scasd();break;
case OPER_SCASW:do_scasw();break;
case OPER_SETE:do_sete();break;
case OPER_SETG:do_setg();break;
case OPER_SETGE:do_setge();break;
case OPER_SETL:do_setl();break;
case OPER_SETLE:do_setle();break;
case OPER_SETNE:do_setne();break;
case OPER_SHL:do_shl();break;
case OPER_SHR:do_shr();break;
case OPER_STOSB:do_stosb();break;
case OPER_STOSD:do_stosd();break;
case OPER_STOSW:do_stosw();break;
case OPER_SUB:do_sub();break;
case OPER_TEST:do_test();break;
case OPER_XOR:do_xor();break;
case OPER_JAE:do_jae();break;
case OPER_FSUBR:do_fsubr();break;
case OPER_FDIV:do_fdiv();break;
case OPER_FADDP:do_faddp();break;
case OPER_FDIVR:do_fdivr();break;
case OPER_FIADD:do_fiadd();break;
case OPER_FMULP:do_fmulp();break;
case OPER_FSUBP:do_fsubp();break;
case OPER_FISUB:do_fisub();break;
case OPER_FISUBR:do_fisubr();break;
case OPER_FIMUL:do_fimul();break;
case OPER_FSUBRP:do_fsubrp();break;
case OPER_FCHS:do_fchs();break;
case OPER_FABS:do_fabs();break;
case OPER_FDIVP:do_fdivp();break;
case OPER_FIDIV:do_fidiv();break;
case OPER_FIDIVR:do_fidivr();break;
case OPER_FDIVRP:do_fdivrp();break;
case OPER_WAIT:do_wait();break;
case OPER_LEAVE:do_leave();break;
case OPER_FNSTCW:do_fnstcw();break;
case OPER_FLDCW:do_fldcw();break;
case OPER_FISTP:do_fistp();break;
case OPER_FCOMP:do_fcomp();break;
case OPER_FCOM:do_fcom();break;
case OPER_FIST:do_fist();break;
case OPER_FNSTSW:do_fnstsw();break;
case OPER_FSTSW:do_fstsw();break;
case OPER_FPTAN:do_fptan();break;
case OPER_FPATAN:do_fpatan();break;
case OPER_FSQRT:do_fsqrt();break;
case OPER_FLD1:do_fld1();break;
case OPER_FLDZ:do_fldz();break;
case OPER_FLDPI:do_fldpi();break;
case OPER_FLDL2E:do_fldl2e();break;
case OPER_FLDL2T:do_fldl2t();break;
case OPER_FLDLG2:do_fldlg2();break;
case OPER_FLDLN2:do_fldln2();break;
case OPER_FRNDINT:do_frndint();break;
case OPER_FSCALE:do_fscale();break;
case OPER_F2XM1:do_f2xm1();break;
case OPER_LAHF:do_lahf();break;
case OPER_SAHF:do_sahf();break;
case OPER_FYL2X:do_fyl2x();break;
case OPER_FXAM:do_fxam();break;
case OPER_FCLEX:do_fclex();break;
case OPER_JZ:do_jz();break;
case OPER_FPREM:do_fprem();break;
case OPER_JNZ:do_jnz();break;
case OPER_FSTCW:do_fstcw();break;
case OPER_FTST:do_ftst();break;
case OPER_FXTRACT:do_fxtract();break;
case OPER_REPE:do_repe();break;
default: this->runtime_error("error oper %d\n",oper);break;
}
return OK;
}
如何与主机通信
一个重要的问题是虚拟机如何与主机通信。主机可以直接访问虚拟机 CPU 的寄存器并读取或写入虚拟内存,因为主机处理虚拟机的所有数据。虚拟机不能直接访问主机,因为它可能导致安全问题,所以我使用 "int
" 指令进行与主机的通信。CVm::do_int()
是一个虚成员函数,因此派生类可以轻松地更改它。
CMyVM::do_int()
{
int int_num;
_RegVal val;
this->GetOpnd1(&val); //get the first opnd, it is INT number
int_num = val.val_32;
switch(int_num)
{
case 0: this->end_flag = true; break; //exit
case 1: this->do_int_1();break;
case 2: this->do_int_2();break;
case 3: this->do_int_3();break;
......
}
return OK;
}
参数可以通过寄存器或堆栈传递;如果所有参数都是 4 字节,则参数的虚拟地址可以通过表达式 esp + 4*i + 4
简单地获得。
DWORD CMyVM::GetParam(int i)
{
long esp = this->reg_index[REG_ESP]->val.val_32;
return this->i_vmem->Read32Mem(esp + 4*i + 4);
}
这是一个例子。
int CMyVM::do_int_2()
{
//set_pixel(int x,int y,COLORREF color)
this->i_canvas->SetPixel(this->GetParam(0),
this->GetParam(1),this->GetParam(2));
return OK;
}
仅仅在主机端声明这个是不够的。你仍然不能在虚拟机端使用这个,你必须在虚拟机端声明它。该函数用汇编语言编写。
_set_pixel proc
int 2
ret
_set_pixel endp
并且必须在 .h 文件中声明
int set_pixel(int x,int y,COLORREF color);
发现了一些错误
在 Workshop.xml 中的 _bootloader.asm 中,一些 ret
指令被遗忘。这可能会导致不需要的执行序列。
_get_cookie_size proc
_read_cookie proc
_write_cookie proc
_set_font proc
使用文本编辑器打开 workshop.xml 并在每个 proc 的末尾添加 ret
。