QueryPerformanceCounter is a LOT more reliable these days than it was in the earlier days when multicore first appeared. Worst case is that you may have to run a timer on a separate thread, and snoop at it's current time as-required.
You may of course still come across the occasional older machine that still exhibits buggy behaviour though.
The only other real option is timeGetTime. Couple that with a timeBeginPeriod (1) call and you'll get 1ms resolution, but this is a global system-wide change that will affect the performance of Windows' thread scheduler and other apps, so be certain to call timeEndPeriod (1) when done.
Note that it's possible that a timeBeginPeriod call is only effective for the duration of the program that makes the call, but I've never had any joy finding confirmation of this behaviour either way so I'd just play safe and use timeEndPeriod.
If you need better than 1ms resolution, one thing I've tried in the past is a main timer that runs off timeGetTime but using QueryPerformanceCounter calls to "fill in the gaps". I've never been fully confident with this approach though and I've long-since junked it.