1

I would be grateful for help with a problem I have been stuck on for a couple of days.

I have a native C++ function type declared so:

typedef STATUS (T_TED_AcppBoxDYN_RegisterEventCallback) (
        PEventCallback function, // pointer to the customer callback 
        PVOID param              // custom data (returned in callback)
        );

where PEventCallback and PEVENT are declared like so:

typedef int (*PEventCallback) (PEVENT event, PVOID param); 

typedef struct
{
  int nEventId;
  void* pParam;
} EVENT,*PEVENT;

The C++ code provides a pointer to a function of that type as a global variable:

T_TED_AcppBoxDYN_RegisterEventCallback* TED_AcppBoxDYN_RegisterEventCallback
        = NULL;

which is initialized later, via this code:

#ifdef LOAD_PROC_ADDRESS
#undef LOAD_PROC_ADDRESS
#endif
#define LOAD_PROC_ADDRESS(handle,func) \
  if((func=(T_##func*)GetProcAddress(handle,#func))==NULL) \
  {\
      sMsg.Format( "Error occurs while loading entry point\n'%s'\n"\
                   "from detector DLL '%s'\n", GetName (), #func );\
      MessageBox(NULL, sMsg.GetBuffer(), "Load Proc Error", MB_OK | MB_ICONSTOP);\
      return (false);\
  }

bool P5100EDllManager::LoadProc ()
{
    CString sMsg;

    HMODULE hDllHandle = GetHandle();
    if (hDllHandle == NULL)
    {
        return false;   // cannot load the proc if the dll has not been loaded
    }

    LOAD_PROC_ADDRESS(hDllHandle, TED_AcppBoxDYN_RegisterEventCallback);
    return true;
}

I want to call the pointed-to function from C#. For that purpose, I have defined a C# wrapper:

public delegate void TDICallBack(IntPtr callbackEvent, IntPtr pParam);
[DllImport(DLL, EntryPoint = "TED_AcppBoxDYN_RegisterEventCallback", CallingConvention = CallingConvention.Cdecl)]
private static extern int TED_AcppBoxDYN_RegisterEventCallback(TDICallBack callBack, IntPtr param);
public void RegisterEventCallback(TDICallBack callBack, IntPtr param)
{
  TED_AcppBoxDYN_RegisterEventCallback(callBack, param);
}

I am using it like this:

TdiapiFacade.RegisterEventCallback(OnTdiCallBack, IntPtr.Zero);

public void OnTdiCallBack(IntPtr ptr, IntPtr param)
{
}

The RegisterEventCallback() seems to work successfully, but at the point where the callback function is supposed to be invoked, the app crashes. As you can see, at this stage I am not even unwrapping the parameters provided to the callback function.

What do I need to do to make this work?

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Contrary to your assertion, the C code does not declare any function. If it's valid C at all (which seems doubtful to me and to CDECL) then it declares `T_TED_AcppBoxDYN_RegisterEventCallback` as an *alias* for a function *type*. Perhaps it's valid C++, but even in that case it must declare a type alias, not an actual function or function pointer of that type. – John Bollinger Dec 05 '14 at 16:29
  • How `STATUS` is defined? – Dennis Dec 05 '14 at 16:47
  • You can't call an unmanaged C++ instance function directly from C#. Because you don't have the `this` pointer. Also, your `TDICallback` delegate has to be Cdecl. You'll need the `[UnmanagedFunctionPointer(CallingConvention.Cdecl)]` attribute. See http://stackoverflow.com/questions/5155180/changing-a-c-sharp-delegates-calling-convention-to-cdecl – Jim Mischel Dec 05 '14 at 16:50
  • typedef int STATUS; //!< Error code type – user3760423 Dec 05 '14 at 16:58
  • @Jim: The function he's trying to call doesn't appear to be a member function. – Ben Voigt Dec 05 '14 at 16:59
  • So the ultimate objective is to register a C# function as a callback to be invoked later from unmanaged C++? – John Bollinger Dec 05 '14 at 17:04
  • @BenVoigt: The edit makes that clear, and also makes it clear that your answer is most likely correct. – Jim Mischel Dec 05 '14 at 17:04

2 Answers2

2

P/invoke doesn't allow access to exported data (variables) such as your function pointer. You'll need a helper inside the DLL, either an exported function which wraps the function pointer, or one that returns it (the return type will be seen as a delegate inside C#).

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
0

Thanks for your input

I've resolved the issue, by modifying the registration of my callback from:

void RegisterCB()
{
    var ret = TdiapiFacade.RegisterEventCallback(OnTdiCallBack, new IntPtr());
    HandleError(ret);
}

to this:

private TDIAPI.TDICallBack callback;
void RegisterCB()
{
    callback = new TDIAPI.TDICallBack(OnTdiCallBack);
    var ret = TdiapiFacade.RegisterEventCallback(callback , new IntPtr());
    HandleError(ret);
}

This fixes the issue, now my callback is properly invoked.