views:

88

answers:

3

Hi all

I'm currently involved in a project that is migrating some old VB6 code to C# (.Net Framework 3.5). My mandate is to just do the migration; any functional enhancements or refactoring is to be pushed to a later phase of the project. Not ideal, but there you go.

So part of the VB6 code makes a call out to the Windows API SetTimer function. I've migrated this and cannot get it to work.

The migrated project builds as a DLL; I've created a small WinForms test harness that links to the DLL and calls the code in question. Very simple, just to prove that the call can be made.

The relevant code in the migrated 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 the DLL in an outer DoProcessing() method; this creates an event using CreateEvent before calling StartTimer (both Windows Kernel calls), then calls WaitForSingleObject before continuing processing. The AsyncObjectCaller function will set the event as part of its execution to allow processing to continue.

So my issue is this: if the code is called as listed above, it fails. The AsyncObjectCaller callback method never gets triggered and the WaitForSingleObject call times out.

If, however, I uncomment the MessageBox.Show call in StartTimer, it works as expected... sort of. The AsyncObjectCaller callback method gets triggered immediately after the call to MessageBox.Show. I've tried putting MessageBox.Show in various locations in the code, and it's the same no matter where I put it (as long as it's called after the call to SetTimer) - the callback function doesn't get triggered until the messagebox is displayed.

I'm completely stumped, and none too familiar with either VB6 or Windows API coding, coming from a mainly .Net background.

Thanks for any help!

A: 
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 just do the migration, but i any case, it is better to use Windows Forms timer instead of this, it wraps native SetTimer API, and there is no need in these interoperability tricks.

Alex Farber
+1  A: 

Your AsyncObjectCallerDelegate is incorrect. It might work in 32-bit code, but will fail miserably in 64-bit. The Windows API function prototype is:

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

In C#, that would be:

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

Also, your managed prototype should be:

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

That said, I'd echo what Alex Farber said: you should use one of the .NET timer objects for this. Since this doesn't appear to be a UI timer (you're passing 0 for the window handle), I'd 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 a callback function, use System.Threading.Timer.

Note that the event or callback will be executed on a pool thread--NOT the program's main thread. So if the processing will be accessing any shared data, you'll have to keep thread synchronization issues in mind.

Jim Mischel
Thanks for the help... marking this as the answer since this is the solution I ended up successfully implementing, and it gave the most detail.
fiveeuros
+2  A: 

The problem is that your program is not pumping a message loop or is not letting the UI thread go idle. An API function like SetTimer() requires a message loop to work. Application.Run() in a Windows Forms project for example. The callback can only run when the your main thread is inside the loop, dispatching Windows messages.

It works when you use MessageBox.Show(), that's a function that pumps its own message loop. So that the message box can respond to the user clicking the OK button. But of course, that will only work for as long as the box is up.

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

Hans Passant
This is not true at all. SetTimer doesn't need a message loop if you're using a callback function as he is. You only need a message loop if the timer callback function is NULL.
Jim Mischel
@Jim, I'm afraid you are wrong. Windows cannot arbitrary run a function on a thread when it is busy doing something else. Note how the OP discovered that by using MessageBox. I'd appreciate you restoring the vote.
Hans Passant
@Hans: Hmmm. I'll happily restore the vote. And now I have to go see where my memory failed me. I was sure I'd used a timer like this without a message loop. Thanks for setting me straight.
Jim Mischel
@Jim, I edited my answer.
Hans Passant
And I removed the downvote.
Jim Mischel
@Jim, much appreciated, thank you.
Hans Passant