1
votes

I am trying a very simple implementation of a C++/CLI wrapper to allow legacy C++ code to reference .Net code, as described here. I am getting stuck just trying to get my basic C++/CLI unmanaged (native) objects linked, without even including any managed/IL/.Net code.

My question is, following along with this basic setup and what I describe below, am I right to be very confused about these errors? Are there some considerations that I'm missing? Maybe the answer is that this should work, so it's not clear what's wrong. That's still helpful. A similar working example would be great.

Unresolved Symbol Errors

Error LNK2019 unresolved external symbol "__declspec(dllimport) public: __thiscall Wrapper::Test::Test(void)" (__imp_??0Test@Wrapper@@QAE@XZ) referenced in function _main NativeApp

Error LNK2019 unresolved external symbol "__declspec(dllimport) public: __thiscall Wrapper::Test::~Test(void)" (__imp_??1Test@Wrapper@@QAE@XZ) referenced in function _main NativeApp

I've reviewed related questions on SO without any luck. I've got my dll header included in the client C++ project, my project reference to the C++/CLI wrapper dll, and my define statements for import/export. My very simple code is shown below. I am not using any MFC. I am using VS2017. DumpBin.exe /exports shows export symbols that seem to match what the linker error says are missing.

      1    0 000010D0 ??0Test@Wrapper@@QAE@XZ = ??0Test@Wrapper@@QAE@XZ (public: __thiscall Wrapper::Test::Test(void))
      2    1 000010E0 ??1Test@Wrapper@@QAE@XZ = ??1Test@Wrapper@@QAE@XZ (public: __thiscall Wrapper::Test::~Test(void))
      3    2 000010C0 ??4Test@Wrapper@@QAEAAV01@ABV01@@Z = ??4Test@Wrapper@@QAEAAV01@ABV01@@Z (public: class Wrapper::Test & __thiscall Wrapper::Test::operator=(class Wrapper::Test const &))

Here's the basic code...

NativeApp.exe (project)


NativeApp.cpp (File)

#include "stdafx.h"
#include <iostream>
#include "Wrapper.h" //From additional includes directory

int main()
{
    std::cout << "Program Started" << std::endl;
    Wrapper::Test shell = Wrapper::Test::Test(); //Use dll
    std::cin.get();
    return 0;
}

Reference to Wrapper

enter image description here


Wrapper.dll (Project)


Wrapper.cpp (File)

#include "Wrapper.h"

#pragma unmanaged
namespace Wrapper {
    Test::Test() {
    }
    Test::~Test() {
    }
}

Wrapper.h (File)

#pragma once

#ifdef WRAPPER_EXPORTS  
#define WRAPPER_API __declspec(dllexport)   
#else  
#define WRAPPER_API __declspec(dllimport)   
#endif  

#pragma unmanaged
namespace Wrapper {
    class WRAPPER_API Test {
    public:
        Test();
        ~Test();
    };
}

1
Why the downvote? Seriously, it would be helpful to know why? I see lots of related questions on SO so there is a precedent for the topic and yet this question is unique in its details.u8it
While the underlying idea (use C++/CLI as a layer between .NET and C++ code) is quite solid, basically all the details of your approach are wrong. __declspec(dllexport) is not used by C++/CLI and does not generate the .NET metadata needed by C#. #pragma unmanaged results in things that can only be accessed by other C++ code (it is possible for that other C++ code to be seen from C#). To make C#-usable objects, you need to use public ref class on the C++/CLI side.Ben Voigt
The reverse is also true. #pragma unmanaged code can't use .NET stuff, it can only call other C++ code (that C++ code can use #pragma managed and .NET). For what you describe (wrapping calls to C# inside a main application written in C++) there's no reason to use a DLL. Just link the C++ and C++/CLI files together into the EXE.Ben Voigt
If you remove the __declspec stuff, and change the project reference to "link library dependencies", your sharing of native C++ objects should start working.Ben Voigt
The dumpbin output shows the constructor and destructor of a class named NativeShell. How that turned into "Test" in the code snippet is impossible to guess.Hans Passant

1 Answers

1
votes

I was under the impression that the project reference took care of any additional dependency settings behind the scenes. Apparently, that is not the case. The .lib file needed to be added as an additional dependency, as is described here. However, as described by this Microsoft document, everything worked without additional dependencies when I was using non /clr dlls, so I'm not sure why the additional dependency was only required for my CLR reference. Clearly, I need to read up on this some more.

In any case, my C++ client project requirements are listed below. I was missing the second requirement. Also, here is an example project that helped me diagnose the problem.

1.) Add project reference

enter image description here

2.) Add the .lib file as an `Additional Dependency.

enter image description here

[Optional] Use Additional Library Directories

enter image description here

3.) #include .h file in code where appropriate

enter image description here

[Optional] Use additional include directories

enter image description here

EDIT: Why the additional dependency was required, and alternative option

As noted above, I was getting hung up on why the .lib additional dependency was required for the /clr dll but not for the non-clr dll. The answer is because a /clr project is configured by default to ignore import libraries. So when the project gets referenced by another C++ project, the project reference ignores import libraries. Changing this setting (Linker > General > Ignore Import Libraries) to "No" in the /clr dll project solves the issue so that the additional dependency is not required and the project reference works the same as the non-clr C++ dll.