I've played around with this a few times. Most recently, I wound up using shared variables with our() and string eval'ing the sub. I had this luxury because my callback is usually called a few thousand times at a go, so the string eval doesn't hurt much. This way, I could use as many variables as I want, tied behind the scenes to the functions that did the real work. The ugliness from the user's point of view is that you have set_callback( q{...}, $opts ) instead of set_callback( sub{...}, $opts) or so, but that's necessary unless we want to use fully qualified variables.
In the end, it made my code uglier and slower than I wanted it to be (30 lines of e.g. $var1 = $hr->{var1} gets old), so I wound up scaling it back and just using 2 shared variables, $t and $d. Typing $t->{foo} just isn't that much more work than typing $foo, and it keeps the inner code from being needlessly horrific.
I'm sure there are better solutions, but this is one. HTH.