65.9K
CodeProject 正在变化。 阅读更多。
Home

可扩展的报表编辑器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2008年8月23日

CPOL

3分钟阅读

viewsIcon

43560

downloadIcon

2084

一个可扩展的报表编辑器。你可以简单地添加你自己的控件,而无需重新编译程序或编写烦人的插件。

MainWindow.PNG

引言

创建报表或对话框是大多数应用程序的常见部分,但对于许多开发者来说过于复杂。如果你是 Microsoft Visual Studio 用户,你很幸运,你可以将控件从控件面板拖动到你的对话框,代码同时生成。但是其他平台呢?如何为你自己的控件生成代码?我们只能说:对不起,你必须手动编写所有代码。这就是我推出这个可扩展工具的原因;你可以简单地添加你自己的控件,而无需重新编译程序或编写烦人的插件。

背景

使用 C++ 作为你的脚本语言是个好主意,但它确实很困难,因为 C++ 语法太复杂而难以实现。世界上有很多优秀的 C++ 编译器,VC++ 6.0 是我最常用的。我的想法是首先使用 C++ 编译器将 .c.cpp 源代码转换为 .asm 文件,然后使用汇编器将 .asm 文件转换为二进制文件。你可以将此二进制文件加载到你的程序中,并让它在一个小型虚拟机上运行。这个程序包含一个小型虚拟机,可以执行许多 i386 指令。

特点

  1. 支持 C++ 脚本语言。
  2. 完整的鼠标处理。绘制和拖动多个对象,以及调整多个对象的大小。
  3. 捕捉到网格。
  4. 组合或取消组合多个对象。
  5. 易于扩展,无需重新编译或使用插件。

如何添加你自己的控件

添加你自己的控件很容易,你不需要重新编译它,你只需要一个文本编辑器。记事本就可以了,但我强烈建议使用 XML 编辑器。我使用 XmlBluePrint,这是一个非常好的 XML 编辑器。你可以访问 http://www.xmlblueprint.com/ 获取它。

单个节点的结构

<Test isnode=&quot;true&quot;>
<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(&quot;test code&quot;);
    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=&quot;true&quot;,否则你将看不到树视图中的此节点。 完成一个节点后,保存 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(&quot;error oper %d\n&quot;,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

© . All rights reserved.