0
votes

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?

1
It might be that _cdecl -- adding a marshalling attribute in C# should fix it if that's the case: stackoverflow.com/questions/5155180/…Iain Ballard
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
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
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
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

1 Answers

0
votes

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.