2010-09-09 16 views
0

Je suis actuellement impliqué dans un projet qui migre un ancien code VB6 vers C# (.Net Framework 3.5). Mon mandat est de juste faire la migration; toute amélioration fonctionnelle ou refactoring doit être poussée vers une phase ultérieure du projet. Pas idéal, mais voilà.Problème avec la méthode de rappel dans l'API SetTimer Windows appelée à partir du code C#

Ainsi, une partie du code VB6 appelle la fonction SetTimer de Windows API. J'ai migré ceci et je n'arrive pas à le faire fonctionner.

Le projet migré est créé en tant que DLL; J'ai créé un petit faisceau de test WinForms qui lie à la DLL et appelle le code en question. Très simple, juste pour prouver que l'appel peut être fait.

le code correspondant dans la DLL migré est la suivante:

[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 deux appels du 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 au traitement de continuer.

Donc, mon problème est le suivant: si le code est appelé comme indiqué ci-dessus, il échoue. La méthode de rappel AsyncObjectCaller n'est jamais déclenchée et l'appel WaitForSingleObject expire.

Si, cependant, je décommenter l'appel MessageBox.Show dans StartTimer, cela fonctionne comme prévu ... en quelque sorte. La méthode de rappel AsyncObjectCaller est déclenchée immédiatement après l'appel à MessageBox.Show. J'ai essayé de placer MessageBox.Show dans divers endroits dans le code, et c'est la même chose peu importe où je l'ai mis (tant qu'il est appelé après l'appel à SetTimer) - la fonction de rappel ne se déclenche pas jusqu'à ce que la boîte de message affiché.

Je suis complètement perplexe, et aucun n'est trop familier avec le codage API VB6 ou Windows, provenant principalement d'un arrière-plan .Net.

Merci pour toute aide!

Répondre

4

Votre AsyncObjectCallerDelegate est incorrect. Il pourrait fonctionner en code 32 bits, mais échouer lamentablement en 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 géré devrait être:

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

Cela dit, je fais l'écho ce qu'Alex Farber a dit: vous devriez utiliser un des objets timer .NET pour cela. Comme cela ne semble pas être une minuterie d'interface utilisateur (vous passez 0 pour la poignée de la fenêtre), je suggérerais System.Timers.Timer ou System.Threading.Timer. Si vous souhaitez que la minuterie déclenche un événement, utilisez System.Timers.Timer. Si vous souhaitez que la minuterie appelle une fonction de rappel, utilisez 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. Ainsi, si le traitement accède à des données partagées, vous devez garder à l'esprit les problèmes de synchronisation des threads.

+0

Merci pour l'aide ... marquer ce que la réponse puisque c'est la solution que je fini par mettre en œuvre avec succès, et il a donné le plus de détails. – fiveeuros

0
 
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, il enveloppe API native SetTimer, et il n'y a pas besoin dans ces interopérabilité des trucs.

2

Le problème est que votre programme ne pompe pas une boucle de message ou ne laisse pas le thread de l'interface utilisateur inactif. Une fonction API comme SetTimer() nécessite une boucle de message pour fonctionner. Application.Run() dans un projet Windows Forms par exemple. Le rappel ne peut s'exécuter que lorsque votre thread principal est à l'intérieur de la boucle, en envoyant des messages Windows.

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

Vous aurez probablement besoin de restructurer votre programme il est basé sur un Windows Forms modèle de projet. Appeler Application.DoEvents() dans une boucle est une solution de contournement très imparfaite.

+0

Ce n'est pas vrai du tout. SetTimer n'a pas besoin d'une boucle de message si vous utilisez une fonction de rappel comme il est. Vous avez seulement besoin d'une boucle de message si la fonction de rappel de minuterie est NULL. –

+1

@Jim, j'ai peur que vous vous trompiez. Windows ne peut pas exécuter de manière arbitraire une fonction sur un thread lorsqu'il est occupé à faire autre chose. Notez comment l'OP a découvert cela en utilisant MessageBox. J'apprécierais que vous restauriez le vote. –

+0

@Hans: Hmmm. Je vais heureusement restaurer le vote. Et maintenant je dois aller voir où ma mémoire m'a échoué. J'étais sûr d'avoir utilisé une minuterie comme celle-ci sans boucle de message. Merci de m'avoir corrigé. –