wm_Paint
message handler, for
example, you just give your application object a virtual wm_Paint
method.When the window function receives a message, it can tell if you have defined a method to handle it. If you do have a handler for that specific message, the windows function will call it. If you don't, it will pass the message to the object's generic message handler.
Not only does being able to use one window function for every window
save memory and programming time while reducing the chances of error,
doing a REPNE CMPSW
on a table of method indices is more
efficient than stepping through a case
statement, too.
But if you'd rather write your own Windows interface than use OWL, and you would like to use dynamic methods the same way OWL does, you face a bit of a problem: TPW doesn't provide any way to call a method by its dynamic index. Rather, you must call the method by name, just like a normal static or virtual method. While this produces code that does do a call-by-index via a run-time library routine, there doesn't seem to be any defined access to that routine.
This is a shame, because this sort of call-by-index opens up all sorts
of interesting possibilities, like modifying the DMT at runtime and the
ability to `pass messages' to objects rather than just calling a static
name. When you give an object commands by passing numeric (or
enumerated) messages to it, you gain the ability to treat the message as
a variable, not a constant, and the same code can thus tell an object to
do different things at different times. For example, where a
traditionally object-oriented program can tell an object to
Display
itself without regard to how it will do that, its more
interpretive, `doubly polymorphic' cousin can tell an object to `do
something', without knowing or caring how the object will carry out the
command or whether the command is Display
, Print
,
SaveToDisk
, or CopyToClipboard
.
Somewhat similarly, if we can compare the address of an object's dynamic
method to the address of its ancestral type's method, we can do
something that we can't do in TP5.5 or 6: find out if a particular
object has overridden an ancestral method. While most of the time we
don't want to know this, and just want to be able to call the method and
trust that the object will respond appropriately, there are times when
we do care. For example, just as one can do `background
processing' under DOS by using KeyPressed
and ReadKey
instead of Read
, so does one do background processing under
Windows by using PeekMessage
instead of GetMessage
. But
it doesn't make a lot of sense to use PeekMessage
if you don't
need to do background processing, as this slows the whole system down by
forcing Windows to give your task a share of the CPU on every pass
through the task list. If the ancestral application object could tell
before it entered its message loop whether or not the specific
descendant type has a `real' Background
method or just inherits
the ancestor's `stub' method, it could automatically use the appropriate
message loop.
Fortunately, while we can't call the RTL's DMT lookup routine directly,
the DMT and VMT formats are straightforward and thoroughly documented,
and the TypeOf()
operator will return a pointer to the VMT.
The Dynamic
function in Listing 1
takes two arguments: a pointer to
a VMT and a dynamic method index. It returns either a pointer to the
method, or Nil
if there is no method with that index.
Its operation is quite simple. It loads its arguments into registers,
then reads the DMT offset from the VMT. If there is no DMT, it returns
Nil. If there is a DMT, Dynamic
reads the length word, and
calculates the offset of the last index in the table. It then
scans backwards until it finds the desired index entry or until it
reaches the start of the table. (The backwards scan means that a
successful search leaves the match position in CX.) If the search was
successful, it reads and returns the appropriate method pointer;
otherwise it loads a pointer to the object's parent's DMT and jumps back
to the DMT existence check at the top of the function.
Using Dynamic is just as simple. While the manuals explicitly state that Turbo Pascal provides no way to use a pointer to code and that all you can do with such a pointer is to pass it to an assembly language routine, this is just not true. Despite their strange syntax, procedural types are essentially just another sort of typed pointer, so to use a pointer to a piece of code all we have to do is to cast it to a procedural type.
Casting pointers to `normal' procedures and functions is a bit easier than casting pointers to methods: We just use a procedural type with the same syntax as the code we're calling. Thus, if we have
type FnType = function (N: integer): integer; function Foo(N: integer): integer; begin {...} end; const Fn: FnType = Foo; FnPtr: pointer = @ Foo;the three statements "
Foo(N)
", "Fn(N)
", and
"FnType(FnPtr)(N)
" all have the same effect: They push a copy
of N
then call Foo
. What's more, the latter two
statements will generate the exact same call-through-a-pointer code
(which is a bit smaller and a bit slower than the direct
call-32-bit-address code).
Casting pointers to method calls is complicated by the `invisible' Self
pointer. It's only invisible to the called code: The calling code has
to be sure to supply it! For example, "type NiladicMethod =
procedure (var Self)
" is a prototype for a method with no normal
arguments and no result, while "type MsgHandler = procedure (var Msg:
MsgType; var Self)
" is a prototype for a method with one var
parameter.
[See my article "Three Myths About Turbo Pascal" for more examples of using pointers to methods.]Thus, assuming your
export
function can associate a window handle
with an object pointer and can thereby call a method, an object oriented
window function for Windows 3.0 might look like Listing 2
while the application's main message loop might look something like
Listing 3.In conclusion, dynamic methods are an exciting new addition to the Turbo Pascal language, and I hope that they (and the PChar extensions) are part of the next version of Turbo Pascal for DOS. The DMT lookup function I've presented in this article is simple and reasonably efficient, and its use in a few key places allows applications to be simultaneously more flexible and easier to code.
Copyright © 1991, Jon Shemitz, jon@midnightbeach.com. HTML markup, 16 October 1994.