Here is (I think) a more flexible approach:
1) A new opcode "callback" (or "register_cb") or such, which is working like the current dlfunc opcode:
callback (out Pcb, in Psub, in Sig)
Pcb ... NCI function object for that callback function Psub ... Parrot Sub PMC to be called on behalf of that C callback Sig ... Signature of the C-callback function
Sig allows additionally one special signature char "U" user-data, which is "Z" in pdd16, but I can remember <U>ser-data better ;)
So void (*PQnoticeProcessor)(void *, const char*) would have Sig "vUt" and call a Parrot function f(P, S). Pdd16 type C callback is e.g. "vpU".
2) Actually registering the callback.
dlfunc (out Pfunc, in Plib, "func_with_cb", "vCU") .pcc_begin prototpyed .arg Pcb .arg P_user_data .nci_call Pfunc
That is instead of passing in the callback and the Parrot Sub ("CY" in pdd16) the PMC obtained from 1) is passed with signature "C". The calling signature matches again the C-function which we call.
When now calling this function the action behind the scene is the same: The passed user_data PMC is combined with the callback PMC obtained from 1) and passed on to the C function. When the C function is doing the callback, the NCI-stub generated in 1) is called, which extracts the Parrot subroutine from the passed user data and passes on the original user PMC and finally calls the PASM callback function.
But as the generated NCI stub in 1) knows the callback signature, this scheme should be appropriate for all callback functons, that have at least one "void *" user parameter to be passed on transparently.
Comments welcome, leo