2009-06-18 6 views

Répondre

3

Le rappel transmis à CreateTimerQueueTimer doit être une fonction non managée qui existera pour la durée de vie des rappels. Le délégué géré peut se déplacer dans la mémoire mais the underlying stub created by the marshalling will not do this so there is no need to pin the delegate. Il y a cependant un besoin d'empêcher le délégué d'être collecté car le pointeur du code non géré n'est pas suffisant pour le maintenir en vie. Le paramètre PVOID transmis à la fonction de rappel doit être fixé en mémoire, car la fonction non gérée attend Dans .Net, cet épinglage se produit (efficacement) automatiquement mais seulement pour la durée de vie de la fonction appelée, donc si vous utilisez une référence à un objet géré (par exemple en lui envoyant un IntPtr) l'objet sous-jacent doit être épinglé (encore une fois le GCHandle peut être utilisé de façon subtilement différente.) Pour voir si c'est le problème, essayez IntPtr.Zero comme paramètre pour tester si cela fonctionne

Si cela résout mince Si vous avez besoin d'allouer votre paramètre en octets bruts dans le tas non géré (et marshal en conséquence), utilisez un type blittable qui est sûr de mettre quelque chose de taille PVOID (comme un Int32) ou utilisez la technique GCHandle ci-dessus pour maintenir un pointeur stable vers une instance gérée, cela aura des conséquences significatives sur les performances si cela est fait à tort.

+0

Vous n'êtes généralement pas obligé d'affecter les délégués passés aux fonctions de l'API, car InteropServices gère ces éléments. Vous * devez * vous assurer que votre application conserve une référence au délégué afin qu'il ne soit pas collecté. L'épinglage est normalement nécessaire uniquement lors du passage de blocs de mémoire du monde géré dans le monde non géré (qui s'attend à ce que les choses restent là où elles sont). – MusiGenesis

+0

@MusiGenesis, c'est ce que je faisais allusion à "Dans .Net, il est presque certain qu'il arrive à tout ce que vous passez.", Je devrais probablement avoir appelé cela comme étant spécifique à PInvoke. De même, je n'ai vu aucune raison de mentionner la nécessité de garder une référence car P/Invoke fait cela pour vous si cela est nécessaire: http://msdn.microsoft.com/en-us/23acw07k.aspx – ShuggyCoUk

+0

PInvoke ne maintient définitivement pas de délégué références pour vous. Le compilateur vous laissera heureusement appeler CreateTimerQueueTimer et passera 'new TimerCallback (...)' en tant que paramètre. Cela fonctionnera même avec succès pendant un certain temps, jusqu'à ce que le délégué que vous avez créé avec 'new' arrive à être ramassé. – MusiGenesis

9

Voici un lien vers une enveloppe C# pour CreateTimerQueueTimer:

http://social.msdn.microsoft.com/Forums/en-CA/csharpgeneral/thread/822aed2d-dca0-4a8e-8130-20fab69557d2

(faites défiler jusqu'au dernier message par Hobz pour la classe d'échantillons)

Je viens d'essayer ce moi-même et fonctionne bien. Une chose que vous aurez besoin d'ajouter, cependant, est un appel à timeBeginPeriod(1) avant de démarrer le minuteur afin de régler votre système à haute résolution. timeSetEvent appelle timeBeginPeriod en interne, ce qui explique pourquoi certaines personnes supposent à tort qu'il crée une minuterie à plus haute résolution.

+1

@ MusiGenesis - J'ai essayé d'utiliser ce code, mais je reçois une exception stackoverlow. et qu'entendez-vous par "un appel à timeBeginPeriod (1)"? Je ne pouvais pas trouver une telle fonction – sura

2

Il est préférable d'utiliser timeSetEvent car ses résultats sont plus cohérents. Sur un matériel moderne moyen, pour de petits intervalles, les écarts de longueur des intervalles sont environ dix fois plus faibles que lors de l'utilisation de CreateTimerQueueTimer. Et c'est en supposant que vous n'avez pas oublié d'augmenter la résolution de minuterie avant d'appeler CreateTimerQueueTimer, sinon la différence serait encore plus grande. Donc, utilisez timeSetEvent à la place.

Questions connexes