0

I am trying to create a C++/CLI wrapper for passing class objects from unmanaged C++ DLL into managed C# code (which subsequently displays the content of the objects on web pages). I have this function in the unmanaged C++ code:

ProbeState _cdecl ManagerAPI::getProbeState()
{
    ProbeState ps = psdao.getLastProbeStateByProbeId(1);
    return ps;
}

I call the function in the C++/CLI wrapper:

using namespace System;

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include "../ManagerApp/ProbeState.h"

typedef ProbeState(*PSFunc)(void);

public ref class ManagerAPIWrapper
{
private:
    HINSTANCE managerApp;

public:
    ManagerAPIWrapper()
    {
        managerApp = LoadLibrary(L"ManagerApp.dll");
    }

    System::String^ testFunc()
    { 
        PSFunc psFunc = (PSFunc)GetProcAddress(managerApp, "?getProbeState@ManagerAPI@@QAA?AVProbeState@@XZ");

        ProbeState *ps = new ProbeState(psFunc());

        System::String ^s = gcnew System::String(ps->getName().c_str());

        delete ps;

        return s;
    }
};

And finally I call the wrapper from my C# controller:

ManagerAPIWrapper.ManagerAPIWrapper wrapper = new ManagerAPIWrapper.ManagerAPIWrapper();
ViewBag.DllMessage = wrapper.testFunc();

It always throws an exception on the line ProbeState *ps = new ProbeState(psFunc());

Strange thing, though, is when I compile the C++/CLI wrapper as a console application with added main function:

int _tmain(int argc, _TCHAR* argv[])
{
ManagerAPIWrapper::ManagerAPIWrapper wrapper;

System::Console::WriteLine(wrapper.testFunc());

getchar();

return 0;
}

This code works just fine and prints out the name of the state retrieved from the database by the C++ DLL. How come the C++/CLI works in console app and throws an exception when called from C#?

P.S.: The wrapper is compiled with /clr option. When I compiled the wrapper with /clr:pure, the exception was the same as with the C# call. Does it mean that when the wrapper is compiled within and called from C# app, it takes the pure option?

The wrapper is meant to convert the data between C++ and C#, so according to my opinion it should not be compiled with more strict options in the C# app. Is there any way to tell the C# compiler that this assembly contains mixed code?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • It might be that `_cdecl` -- adding a marshalling attribute in C# should fix it if that's the case: http://stackoverflow.com/questions/5155180/changing-a-c-sharp-delegates-calling-convention-to-cdecl – Iain Ballard Mar 13 '14 at 14:17
  • Returning a C++ object across module boundaries is *very* risky. It is absolutely crucial that this DLL was built with the exact same compiler version and the exact same setting and that the CRT is shared (/MD option). The shenanigans with GetProcAddress and the crash strongly indicate that this is not the case. You have to rebuild the DLL. – Hans Passant Mar 13 '14 at 15:37
  • That _cdecl attribute is a result of my futile struggles to get the code to working... So you think I should do without it? – Milan Bartl Mar 13 '14 at 17:48
  • OK, I tried the console output with the explicit `_cdecl` and it failed with AccesViolationEx as well, so this was definitely bad idea. The interface into the C# code still does not work, though. – Milan Bartl Mar 13 '14 at 20:51
  • I did some debugging and found out that the LoadLibrary call fails with the error 126 - module not found. There comes the question, though: why the call works with the console application while it fails within the DLL (loaded by C# code)? – Milan Bartl Mar 15 '14 at 16:26

1 Answers1

0

OK, I finally got through this. After many hours spent with try&fail way of finding a solution, I tried to call a function from the unmanaged DLL directly from the C# code first, and then called a constructor of the wrapper, which succeeded in the LoadLibrary call. Code in the C# controller now looks like this:

 [DllImport("C:\\ManagerApp.dll", CharSet = CharSet.Unicode, 
      EntryPoint = "?initFunc@ManagerAPI@@QAEHXZ")]
    private static extern int initFunc();

    public ActionResult APITest()
    {
        ViewBag.Message = "API output test page.";

        if (initFunc() == 0)
        {
            ViewBag.Error = "Could not initialize the library.";

            return View();
        }

        ManagerAPIWrapper.ManagerAPIWrapper wrapper = new ManagerAPIWrapper.ManagerAPIWrapper();
        ViewBag.DllMessage = wrapper.testFunc();

        return View();
    }

I am thinking it might help to add a dependency to the wrapper DLL on the unmanaged DLL and therefore get rid of the necessity of calling the initFunc.