0

I have a c# managed project that imports an unmanaged c++ dll. What I wanted was to get logging to work using the logging function I wrote in the C# code. So I added the following to my C# side:

public struct API 
{
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void FunctionPointer(string msg);

    [DllImport("mydll.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
    unsafe public static extern void setLoggerPointer(IntPtr fctPointer);

    [DLLImport("mydll.dll", SetLastError = true)]
    [return: MarshalAsAttribute(UnmanagedType.I1)] unsafe public static extern bool init();
}

public unsafe class MyInterface
{
    public MyInterface()
    {
        API.FunctionPointer loggerDelegate;
        loggerDelegate = new API.FunctionPointer(Logger.LogMessage);
        IntPtr loggerPtr = Marshal.GetFunctionPointerForDelegate(loggerDelegate);
        API.setLoggerPointer(loggerPtr);

        if (API.init()) 
        {
           //do stuff
        }
    }
}

Here is my Logger class definition:

public static class Logger
{
    public static void LogMessage(string msg)
    {
        Console.WriteLine(msg);
        fileStream.Write(msg);
    }
}

I have the following on the c++ side header:

#define MY_C_API extern "C" __declspec(dllexport);

MY_C_API __declspec(dllexport) void __stdcall setLoggerPointer( void *fctPointer(LPCTSTR msg) );
MY_C_API __declspec(dllexport) bool __stdcall init();

And in the C++ source:

//global variable
void *(*logger)(LPCTSTR msg);

void __stdcall setLoggerPointer( void *fctPointer(LPCTSTR msg) )
{
    logger = fctPointer;
}

bool __stdcall init()
{
    logger("WOOO");

    return true;  //I'm getting the AccessViolation right here
}

I'm getting a System.AccessViolationException upon returning from the init() function in atlsimpstr.h Release() function inside the mfc100.dll

Anybody know what I'm doing wrong? All the questions I've seen about this sort of thing have been how to do the reverse P/Invoke without access violation but it's working fine for me, it's just when returning from the other invocation it is messed up, as if that section of memory is now considered part of the C# application.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
SSB
  • 349
  • 3
  • 18
  • In the above, init is declared as void, but you're returning true. – Matt Smith Mar 04 '13 at 18:15
  • When you call `logger("WOOO");`, is it actually calling into the C# code successfully? Also, I'm assuming where you have loggerPtr above you really mean loggerDelegate?? – Pete Mar 04 '13 at 18:18
  • Sorry, mistakes when putting it on here. I think I fixed them now. – SSB Mar 04 '13 at 19:21
  • I rolled back your question. Your edit just moved my code into the question and made my answer look stupid. – David Heffernan Mar 04 '13 at 20:16

1 Answers1

2

The problem is that the calling conventions for the callback do not match. Your native code expects the callback to be cdecl, but the managed code declares it to be stdcall. You can fix this either in the managed code or in the native code. For simplicity, I'll show how to fix it in the managed code.

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void FunctionPointer(string msg);

Some other points:

  1. Don't set the SetLastError parameter to true since your functions are not setting the Win32 last error.
  2. Don't use unsafe anywhere in this code. As a general rule you should avoid unsafe and you just don't need it for any of this.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks for the help. Unfortunately I'm still having the problem. I'm thinking it has something to do with the LPCTSTR type or another marshalling issue still. – SSB Mar 04 '13 at 19:51
  • Try the latest update. I spotted another mismatch, in calling convention. – David Heffernan Mar 04 '13 at 20:08
  • I just tested my code. It works. I guess your accept means that you have discovered the same. – David Heffernan Mar 04 '13 at 20:19
  • After reading up a bit on [this question](http://stackoverflow.com/questions/3404372/stdcall-and-cdecl) I understand why the problem was occurring now. "In CDECL arguments are pushed onto the stack in revers order, the caller clears the stack and result is returned via processor registry (later I will call it "register A"). In STDCALL there is one difference, the caller doeasn't clear the stack, the calle do." – SSB Mar 04 '13 at 20:20
  • Yep, the stack cleanup was the problem. – David Heffernan Mar 04 '13 at 20:21