When writing MicroStation-based code, you should keep the following things in mind:
Event Handlers
Hook the appropriate event and do the absolute minimum in your event handler. Some events happen quite often and in time-critical parts of the system. A poorly written event handler can be a significant drag on performance or worse, creating havoc. Make sure your handler is not registered when it does not need to be registered and make sure you return as early as possible if the event is not relevant. Then, when the event requires action, perform only what you know is necessary and safe.
Avoid calling other high level API functions (yours or MicroStation's) in your event handler because this often leads to unexpected and/or recursive callstacks where something in a handler triggers other events.
ChangeTrack handler must be read-only
A ChangeTrack handler callback must not change the element that is passed to it.
A ChangeTrack handler callback should not change any element. ChangeTrack callbacks are called as tools add, delete, and modify elements. You cannot safely mesh your changes with the changes being made by the tool that is in progress.
If you must make follow-on changes, you should use MicroStation's Dependency Manager. If you cannot use dependencies, you might try queuing a command.
filePos is obsolete
The entire filePos-based API is obsolete -- there is no good reason to use it for new code. It exists for backwards compatibility only. Whenever you see a reference to a filePos in your code, it is a sign that your code can be improved. See elementref.h for an explanation of filePos vs. ElementRef, etc. In general, new code should use the ElemHandle APIs, and it will be simpler, faster, more reliable, and better.
DisplayPaths
Use DisplayPath and ElementRef for locates.
Avoid Timer functions
Timer functions are sometimes used to "fix" problems where something fails due to order-of-execution problems. Usually this indicates a poorly designed, poorly understood, or incorrectly used API. The misguided programmer will say, "If I call this here it fails/crashes, but if I wait and call it later it works. I know, I will 'defer' that part and do it in a timer function." However, what that does is turn a simple synchronous and predictable problem into a complex one that is difficult to debug. That is almost never the right solution. Instead, figure out the real problem and redesign if necessary.
There are some valid reasons to use timer functions, or they would not exist. In most cases timer functions are necessary when some external (e.g. the user or hardware related) time-base is involved.
Avoid "queuing" if possible
Avoid command filtering
The scanner is not magic
In most cases, the scanner is simply an iterator over all of the elements in a model, testing each one against your scan criteria. Do not presume that a scan is fast because your criteria will filter out most elements. The performance of a no-result scan (i.e. a scan whose criteria rejects all elements) is linear with the number of elements in the model (The one exception to this is range-based scanning. MicroStation does maintain a range-based index of every model and if your scan criteria includes a range that is a small subset of the total range of the model, then the scanner will use the range index and find elements in that range faster than it could with a linear search.)
Do not scan in time-critical sections of your code, and be aware that an innocuous-looking scan that works fine on your trivial test cases can be very slow for real users with large models.
Also, if you know the elements you are looking for are in the non-model, control, or graphic sections, limit your search appropriately, especially if you can exclude the graphic section -- that is where most elements reside. Scanning the non-model or control sections is usually fairly fast even if the model itself is large.
mdlSystem_getCfgVar is expensive
Do not check for the existence or value of configuration variables in any time-critical code. This function is very time-consuming, especially when it fails to find the variable. It should never be executed in code where performance is important. Do not think of it as the equivalent of checking a flag. Instead, retrieve the value once and store it in a variable somewhere.