Again thanks Richard for making me think differently :-D
Here's the solution for anyone else:
On the c++/cli side:
NativeWorkerObject *nativeWorkerObj = new NativeWorkerObject();
IntPtr managedResultsPointer = Marshal::AllocHGlobal( sizeof( NativeResultsObject * ) );
NativeResultsObject *nativeResultsPointer = ( NativeResultsObject * )managedResultsPointer.ToPointer();
nativeWorkerObj->DoSomeWork( &nativeResultsObj );
Marshal::FreeHGlobal( managedResultsPointer );
No change required on the native side.
allocates memory from the unmanaged memory which is locked in place i.e. the garbage collector can't touch it. This gives you the safe memory for your native pointer.
returns a good old
allowing you to cast the pointer accordingly for your needs.
Don't forget to release any resources used after use :)