Problem with callback method in SetTimer Windows API called from C # code

I am currently participating in a project that is porting old VB6 code to C # (.Net Framework 3.5). My mandate is simply to migrate; any functional improvements or refactoring should be carried forward to a later stage of the project. Not perfect, but there you go.

Thus, part of the VB6 code calls the SetTimer function of the Windows API. I have undergone this operation and cannot make it work.

A migrated project is created as a DLL; I created a small WinForms test harness that references a DLL and calls this code. Very simple, just to prove that a challenge can be made.

The corresponding code in the redirected DLL is as follows:

[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 { } } 

The above calls are wrapped by a DLL by an external DoProcessing () method; this creates an event using CreateEvent before calling StartTimer (both calls to Windows Kernel) and then calling WaitForSingleObject before continuing with the processing. The AsyncObjectCaller function will set the event as part of its execution to continue processing.

So my problem is this: if the code is called as above, it fails. The AsyncObjectCaller callback method never starts, and the WaitForSingleObject call is interrupted.

If, however, I uncomment the call to MessageBox.Show in StartTimer, it works as expected ... sort of. The AsyncObjectCaller callback method starts immediately after the MessageBox.Show call. I tried to place MessageBox.Show in different places in the code, and this is the same no matter where I put it (as long as it called after SetTimer was called) - the callback function does not start until it appears message is displayed.

I am completely at a dead end, and none of them are familiar with VB6 or Windows API encoding, based on the main .Net background.

Thanks for any help!

0
source share
3 answers

Your AsyncObjectCallerDelegate incorrect. It can work in 32-bit code, but with an error in the 64-bit version. The prototype of the Windows API function is:

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

In C #, it will be:

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

In addition, your managed prototype should be:

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

However, I would say that Alex Farber said: you should use one of the .NET objects for this. Since this is not a UI timer (you skip 0 for the window handle), I would suggest System.Timers.Timer or System.Threading.Timer . If you want the timer to raise an event, use System.Timers.Timer . If you want the timer to call the callback function, use System.Threading.Timer .

Note that an event or callback will be executed in the pool thread - NOT the main program thread. Therefore, if the processing will have access to any shared data, you will have to consider the problems of thread synchronization.

+4
source

The problem is that your program does not pump up the message loop or skip the user interface stream. An API function, such as SetTimer (), requires the message loop to work. Application.Run () in a Windows Forms project, for example. A callback can only be performed if the main thread is inside the loop, sending Windows messages.

It works when you use MessageBox.Show (), which is a function that pumps out its own message loop. So that the message box can respond to the user by clicking the "OK" button. But, of course, this will only work as long as the field is gone.

You probably need to restructure your program, as it is based on the Windows Forms project template. Calling Application.DoEvents () in a loop is a very imperfect workaround.

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

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

I understand that your mandate is to simply perform the migration, but in any case it is better to use the Windows Forms timer instead, it wraps its own SetTimer API, and there is no need for these interaction tricks.

0
source

All Articles