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

丶灬走出姿态 提交于 2019-12-12 01:57:49

问题


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!


回答1:


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.




回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/3678788/issue-with-callback-method-in-settimer-windows-api-called-from-c-sharp-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!