Question

I have a tricky bug I can't find. I'm doing late-binding on from C# to a native DLL I wrote. The late-binding seem to work fine. The problems started after I added the callbacks.

The callbacks are defined as so (in c)(On a global scale in the DLL):

typedef void (*FinishedDelegate) (ProcessResult a_bResult);

typedef void (*DownloadStatusUpdateDelegate) (int a_iParametersDownloaded, int a_iTotalParameters);
typedef void (*DownloadFinishedDelegate) (char* a_sConfiguration_values, ProcessResult a_bResult);

DownloadStatusUpdateDelegate pfDownloadStatusUpdateDelegate = NULL;
DownloadFinishedDelegate pfDownloadFinishedDelegate = NULL;

This function is exported:

PLUGIN_API BOOL DownloadConfigration(DownloadStatusUpdateDelegate a_pfStatusDelegate, DownloadFinishedDelegate a_pfFinishedDelegate);

And this is the native function implementation:

DWORD WINAPI DownloadThreadFunc(void* a_pParam)
{
    while (g_iParameterDownloaded < PARAMETER_COUNT)
    {
        if (IsEventSignaled(g_hAbortEvent))
        {
            CallDownloadFinished(PROCESS_ABORT);
            return 0;
        }

        Sleep(STATUS_DELAY);
        CallDownloadStatusUpdate();
        g_iParameterDownloaded += STATUS_PARAMS_JUMP;
    }

    CallDownloadFinished(PROCESS_SUCCESS);

    return 0;
}

PLUGIN_API BOOL DownloadConfigration(DownloadStatusUpdateDelegate a_pfStatusDelegate, DownloadFinishedDelegate a_pfFinishedDelegate)
{
    if (IsEventSignaled(g_hInProcessEvent))
        return false;


    pfDownloadStatusUpdateDelegate = a_pfStatusDelegate;
    pfDownloadFinishedDelegate = a_pfFinishedDelegate;

    g_iParameterDownloaded = 0;

    DWORD l_dwResult = WaitForSingleObject(g_hThreadsStructGuardian, INFINITE);
    if (l_dwResult == WAIT_OBJECT_0)
    {
        g_ahThreads[PLUGIN_THREAD_DOWNLOAD] = CreateThread(NULL, 0, DownloadThreadFunc, 0, 0, NULL);
        ReleaseMutex(g_hThreadsStructGuardian);
        return true;
    }

    return false;
}

On the managed side, the function is called here:

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void DownloadStatusUpdateDelegate(int a_iParametersDownloaded, int a_iTotalParameters);
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate void DownloadFinishedDelegate_Native(StringBuilder a_sConfigurationValues, EPluginProcessResult a_eResult);

        private void OnDownloadStatusUpdate(int a_iParametersDownloaded, int a_iTotalParameters)
        {
            if (DownloadStatusUpdate != null)
            {
                DownloadStatusUpdate(a_iParametersDownloaded, a_iTotalParameters);
            }
        }

        private void OnDownloadFinished(StringBuilder a_sConfigurationValues, EPluginProcessResult a_eResult)
        {
            if (DownloadFinished != null)
            {
                DownloadFinished(a_sConfigurationValues.ToString(), a_eResult);
            }
        }

        public bool DownloadConfiguration()
        {
            bool l_bResult = DLLDownloadConfigration(OnDownloadStatusUpdate, OnDownloadFinished);

            return l_bResult;
        } 

The weird thing is - It works for a while. I get a "Privileged Instruction" exception after some time, but as I lower STATUS_DELAY, it happens less. The exception shows up at the IsEventSignaled function - But there's simply nothing there.

The download thread syncs to the c# GUI thread to update the GUI.

I've been on this problem for way too much time. It looks like a classic calling conventions problem, but I verified it thoroughly!

Any ideas?

Was it helpful?

Solution

 bool l_bResult = DLLDownloadConfigration(OnDownloadStatusUpdate, OnDownloadFinished);

The code isn't very clear, but this is the likely trouble spot. This creates two delegate objects, the callback bombs after the garbage collector runs and deletes the objects. It cannot track managed object references into unmanaged code. You'll need to create the delegates explicitly and store them in a class member so the garbage collector always sees at least one reference.

OTHER TIPS

Have you tried using a lambda? As in:

bool l_bResult = DLLDownloadConfigration((downloaded, totalParams) => OnDownloadStatusUpdate(downloaded, totalParams), (values, result) => OnDownloadFinished(values, result));

My theory on this is that it is failing because your OnDownloadStatusUpdate and OnDownloadFinished methods aren't static. The underlying IL expects the 'this' object as the first invisible arg, but the C-method is not passing it when calling the callback.

EDIT: I think the answerer above me is correct, but can anyone shed some light on how the marshaller actually handles this?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top