I have struggled with coxph's tt subfunction as well. A (easy) workaround is to work with a 'counting process’ format dataset. In such a dataset the data for each individual is cut into periods of time. Each period will have a start time, stop time, and event status, and this is needed for each record/individual. In the resulting dataset you will be able to use an 'actual' time (is not the survival time) to model interactions with time. Finally, for the way I've used this workaround, you'll need to construct the interaction variable before entering it in the final cph code.
For your example/case I have done this as follows:
# original coxph tt fit
fit <- coxph(Surv(time,status) ~ ph.karno + tt(ph.karno) + age, data=lung,x=T,y=T,tt=function(x,t,...) x*log(t))
# Creating a countingprocess-format survival file using survSplit
# constructing the ph.karno and interaction with log-time variable
# some settings required for cph and other rms functions to work
dd <- datadist(lung.long)
# fitting the cph model
fit.long<-cph(Surv(tstart,time,status)~ph.karno + ph.karnologtime + age,x=T,y=T,data=lung.long)
# comparing the resulting coefficients
The only thing I had not encountered before is that there is a slight difference (on the sixth decimal) between these estimates. I am not aware of a (minor) difference between coxph or cph, but I could not find one in the handling of ties or other default settings of these functions. Anyone with insight into these differences, feel free to comment/edit this post.