Introduction
Creating a report or a dialog is a normal part of most applications, but far too convoluted for many developers. If you are a Microsoft Visual Studio user, you are very lucky, you can drag controls from the controls pad to your dialog, and the code is created at the same time. But how about other platforms? How about generating code for your own controls? We can only say: sorry, you must write all the code by hand. Here is where I come to aid with this extendable workshop; you can simply add your own controls without recompiling the program or writing annoying plug-ins.
Background
Using C++ as your script language is a good idea, but it's really a difficult thing, because the C++ syntax is too complex to implement. There are many good C++ compliers in this world, VC++ 6.0 is what I most commonly use. My idea is to first use a C++ complier to change .c or .cpp source into a .asm file, then use an assembler to convert the .asm file to a binary file. You can load this binary file into your program and let it run on a small virtual machine. This program include a small virtual machine that can execute many i386 instructions.
Features
- C++ script language supported.
- Full mouse handling. Draw and drag multiple objects, and resize multiple ones.
- Snap to grid.
- Group or ungroup multiple objects.
- Easy to extend, without recompiling or using plug-ins.
How to add your own controls
Adding your own controls is an easy thing, you don't need to re-compile it, the only thing you need is a text editor. Notepad is OK, but I strongly recommend using an XML editor. I use XmlBluePrint, which is a very good XML editor. You can visit http://www.xmlblueprint.com/ to get it.
Structure of a single node
<Test isnode="true">
<code>
<![CDATA[
</code>
<default>
<![CDATA[
</default>
</Test>
A single control node must be put under AllNode
. Be sure isnode="true"
is a must or you will not see this node in the tree view. When you finish a node, save the XML file then open this program again, and you will see a new node in the tree view.
What's a virtual machine?
A virtual machine is a software computer in my understanding. It can be very simple or very complex. A complex virtual machine can let modern OSs like Windows or Linux run on it. A simple virtual machine can only contain a simple soft CPU and a simple soft memory. The virtual machine in this program belongs to the latter category.
Using a virtual machine
A program can be viewed as a series of instructions and what the CPU does is just fetch the next instruction and interpret it, so a typical skeleton of a CPU emulator in C++ is:
this->vm->LoadFile(pobj->mf_bin); while(!this->vm->end_flag)
{
this->vm->GetNextOper();
this->vm->ExeOper();
}
ExeOper()
is used to interpret each instruction; following is the source code:
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;
}
How to communicate with the host
An important question is how the virtual machine communicates with host. A host can access the registers of a virtual machine CPU and read or write virtual memory directly, because the host handles all the data of the virtual machine. A virtual machine can not access the host directly, as it can cause safety problems, so I use the "int
" instruction for communicating with the host. CVm::do_int()
is a virtual member function, so the derived class can change it easily.
CMyVM::do_int()
{
int int_num;
_RegVal val;
this->GetOpnd1(&val); int_num = val.val_32;
switch(int_num)
{
case 0: this->end_flag = true; break; case 1: this->do_int_1();break;
case 2: this->do_int_2();break;
case 3: this->do_int_3();break;
......
}
return OK;
}
The parameters can be passed via registers or stack; if all the parameters are 4 bytes, the virtual address of the parameter can be simply obtained by the expression 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);
}
Here is an example:
int CMyVM::do_int_2()
{
this->i_canvas->SetPixel(this->GetParam(0),
this->GetParam(1),this->GetParam(2));
return OK;
}
Just declaring this at the host side is not enough. You still can't use this at the virtual machine side, you must declare it at the virtual machine side. The function is written in assembly language.
_set_pixel proc
int 2
ret
_set_pixel endp
and must be declared in a .h file:
int set_pixel(int x,int y,COLORREF color);
Some bugs found
In _bootloader.asm in Workshop.xml, some ret
instructions were forgotten. This might cause unwanted execution sequences.
_get_cookie_size proc
_read_cookie proc
_write_cookie proc
_set_font proc
Use a text editor to open workshop.xml and add ret
at the end of each proc.