<< if yes, you'd realize that most of the time, the programmers had NO idea what assembly is and as a result you can circumvent the protection quite easily. I think that the ideal solution in terms of time and efficiency would be to write a protection in a higher level language and then go and tweak it in assembly to make it harder for crackers. >>
You can't write an application in one language, then simply "tweak" it in assembly. Indeed, a binary has absolutely no reference to any programming language whatsoever, including assembly. The only reason you can "disassemble" a binary is because translating the opcodes of a binary into assembly instructions is simple. Each byte in a binary translates to an opcode (or an argument for an opcode), and for each opcode, there is a corresponding instruction in assembly (mov, lea, pop, push, jmp, jnz, whatever).
It's as simple as this; if your application uses *any* form of protection at the binary level, it can, and will (if your application is desirable) be removed. There are ways you can make protection removal more difficult, but still it's inevitable. Sometimes it's as simple as trying to register a shareware program and noting the error message returned. Often these error messages are stored as string literals in the binary (they end up in the .data section of a binary). A little disassembly can locate the address of the string literal in the binary. With that, you simply trace the opcodes preceding the literal finding the jmp instruction for the protection scheme, and NOP (no-op, i.e. ) it.
Protection schemes that limit by time are even easier. The ones that use keycodes are more difficult, but once you reverse engineer the algorithm, you can build a keygen.
The only true way to protect your application is to distribute a limited version. Modern development environments facilitate different builds for things like this.