Problème avec la méthode de rappel dans l'API Windows appelé SetTimer de code C #

StackOverflow https://stackoverflow.com/questions/3678788

  •  01-10-2019
  •  | 
  •  

Question

Je participe actuellement à un projet qui émigre un vieux code VB6 à C # (.Net Framework 3.5). Mon mandat est de juste effectuer la migration; tout améliorations fonctionnelles ou refactoring doit être poussé à une phase ultérieure du projet. Pas idéal, mais là vous allez.

Donc, une partie du code VB6 fait un appel vers la fonction Windows API SetTimer. J'ai migré cela et ne le faire au travail.

Le projet migré construit comme une DLL; J'ai créé un petit harnais de test WinForms que les liens vers la DLL et appelle le code en question. Très simple, il suffit de prouver que l'appel peut être fait.

Le code correspondant dans la DLL est migré suit comme:

[DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
public extern static int SetTimer(int hwnd, int nIDEvent, int uElapse, AsyncObjectCallerDelegate lpTimerFunc);

public delegate void AsyncObjectCallerDelegate(int hwnd, int uMsg, int idEvent, int dwTime);



static public int StartTimer( AsyncGeoServer.GeoWrapper AsyncObj)
{
    m_objGeoWrapper = AsyncObj;

    int lngReturn = SetTimer(0, 0, 1, new AsyncObjectCallerDelegate(AsyncObjectCaller));

    // When the line below is removed, the call functions correctly.
    // MessageBox.Show("This is a temp message box!", "Temp Msg Box", MessageBoxButtons.OKCancel);

    return lngReturn;
}

static private void AsyncObjectCaller(int hwnd,  int uMsg,  int idEvent,  int dwTime)
{
    // Perform processing here - details removed for clarity 
}


static public void StopTimer( int TimerID)
{
    try { KillTimer(0, TimerID); }
    catch { }
}

Les appels ci-dessus sont enveloppés par la DLL dans un procédé DoProcessing () extérieure; cela crée un événement en utilisant CreateEvent avant d'appeler startTimer (les appels noyau Windows), puis appelle WaitForSingleObject avant de poursuivre le traitement. La fonction AsyncObjectCaller définira l'événement dans le cadre de son exécution pour permettre le traitement de continuer.

Donc, ma question est la suivante: si le code est appelé comme indiqué ci-dessus, elle échoue. La méthode de rappel de AsyncObjectCaller ne se déclenche et les temps d'appel WaitForSingleObject sur.

Si, cependant, je décommenter l'appel MessageBox.Show à StartTimer, il fonctionne comme prévu ... en quelque sorte. La méthode de rappel de AsyncObjectCaller se déclenche immédiatement après l'appel à MessageBox.Show. Je l'ai essayé de mettre MessageBox.Show dans divers endroits dans le code, et il est le même, peu importe où je l'ai mis (aussi longtemps qu'il est appelé après l'appel à SetTimer) - la fonction de rappel ne soit pas déclenché jusqu'à ce que le messagebox est affiché.

Je suis complètement déconcerté, et pas trop familier avec soit VB6 ou Windows API de codage, provenant d'un fond essentiellement .Net.

Merci pour toute aide!

Était-ce utile?

La solution

Votre AsyncObjectCallerDelegate est incorrect. Il pourrait fonctionner dans le code 32 bits, mais échoue lamentablement dans 64 bits. Le prototype de la fonction API Windows est:

VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);

En C #, ce serait:

delegate void AsyncObjectCallerDelegate(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime);

En outre, votre prototype devrait être géré:

static extern IntPtr SetTimer(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, AsyncObjectCallerDelegate lpTimerFunc);

Cela dit, je fais écho ce que Alex Farber a dit: vous devez utiliser l'un des objets de la minuterie .NET pour cela. Étant donné que cela ne semble pas être une minuterie de l'interface utilisateur (vous passez 0 pour la poignée de fenêtre), je vous suggère System.Timers.Timer ou System.Threading.Timer. Si vous voulez que le minuteur déclencher un événement, l'utilisation System.Timers.Timer. Si vous voulez que la minuterie pour appeler une fonction de rappel, l'utilisation System.Threading.Timer.

Notez que l'événement ou le rappel sera exécuté sur un fil de piscine - pas thread principal est le programme. Donc, si le traitement accédera à des données partagées, vous devez garder les problèmes de synchronisation de fil à l'esprit.

Autres conseils

Le problème est que votre programme ne pompe pas une boucle de message ou de ne pas laisser tourner au ralenti go thread d'interface utilisateur. Une fonction API comme SetTimer () nécessite une boucle de message au travail. Application.Run () dans un Windows Forms projette par exemple. Le rappel ne peut fonctionner lorsque le votre thread principal est à l'intérieur de la boucle, l'envoi des messages Windows.

Il fonctionne lorsque vous utilisez MessageBox.Show (), qui est une fonction qui pompe sa propre boucle de message. Alors que la boîte de message peut répondre à l'utilisateur en cliquant sur le bouton OK. Mais bien sûr, cela ne fonctionne que pour aussi longtemps que la boîte est en place.

Vous aurez probablement besoin de restructurer votre programme pour qu'il repose sur un Windows Forms modèle de projet. Appel Application.DoEvents () dans une boucle existe une solution très imparfaite.

public extern static int SetTimer(int hwnd, int nIDEvent, int uElapse, IntPtr lpTimerFunc);

int lngReturn = SetTimer(0, 0, 1, Marshal.GetFunctionPointerForDelegate(new AsyncObjectCallerDelegate(AsyncObjectCaller)));

Je comprends que votre mandat est de faire exactement la migration, mais je tous les cas, il est préférable d'utiliser Windows Forms minuterie au lieu de cela, elle enveloppe API native SetTimer, et il n'y a pas besoin dans ces astuces d'interopérabilité.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top