I'm trying to learn a bit more about C++/CLI for interop between C++ and C#. I have 3 projects
- a C# exe with .net 4.7,
- a C++/CLI mixed dll, and
- a native C++ 17 dll.
I'm having trouble compiling the C++/CLI project. I had it working before, so everything should be setup correctly, but since I made some severe code changes it doesn't work anymore.
In my C++/CLI project I have a class that provides some basic function calls:
// ManagedApi.cpp
public ref class ManagedApi
{
public:
static void SayHello()
{
Bindings::SayHello::invokeIfBound();
}
static int Add(int x, int y)
{
return Bindings::Add::invokeIfBound(x, y).value_or(-1);
}
static void Greet(System::String^ name)
{
Bindings::Greet::invokeIfBound(
msclr::interop::marshal_as<std::string>(name)
);
}
static int Accumulate(int x)
{
if (!Bindings::Accumulate::isBound())
throw gcnew System::InvalidOperationException("No accumulator bound");
return Bindings::Accumulate::invoke(x);
}
static void SayGoodbye()
{
Bindings::SayGoodbye::invokeIfBound();
}
};
Those function bindings are type aliases:
// Bindings.hpp
namespace Bindings
{
using SayHello = UnmanagedFunctionBinding<0, void()>;
using Greet = UnmanagedFunctionBinding<1, void(std::string const&)>;
using SayGoodbye = UnmanagedFunctionBinding<2, void()>;
using Add = UnmanagedFunctionBinding<3, int(int, int)>;
using Accumulate = UnmanagedFunctionBinding<4, int(int)>;
}
And the funtion wrapper below is basically a std::function
singleton.
// UnmanagedFunctionBinding.hpp
template <auto ID, typename TReturn, typename... TArgs>
class UnmanagedFunctionBinding<ID, TReturn(TArgs...)>
{
public:
UnmanagedFunctionBinding() = delete;
using FunctionType = std::function<TReturn(TArgs...)>;
GLUE_API static constexpr decltype(ID) Id = ID;
GLUE_API static bool isBound() noexcept
{
return static_cast<bool>(s_func);
}
GLUE_API static TReturn invoke(TArgs... args)
{
return s_func(std::forward<TArgs>(args)...);
}
GLUE_API static auto invokeIfBound(TArgs... args) ->
std::conditional_t<std::is_same_v<TReturn, void>, void, std::optional<TReturn>>
{
if constexpr (std::is_same_v<TReturn, void>)
{
if (isBound())
s_func(std::forward<TArgs>(args)...);
}
else
{
return isBound()
? std::make_optional(s_func(std::forward<TArgs>(args)...))
: std::nullopt;
}
}
GLUE_API static void bind(std::function<TReturn(TArgs...)> func) noexcept
{
s_func = std::move(func);
}
GLUE_API static void unbind() noexcept
{
s_func = nullptr;
}
private:
static FunctionType s_func;
};
I used namespace functions before to mimic a similar behavior (basically accessed a global map that functioned as a lookup table for the different functions) which worked fine. But since I moved to classes instead of functions I get a linker error within the mixed dll. All of the code above lies in the same C++/CLI project.
Compilation seems to work since I reach the linking stage, but apparently my ManagedApi.obj
has some trouble here:
ManagedApi.obj : error LNK2020: unresolved token (0A00022A) "private: static class std::function Glue::UnmanagedFunctionBinding<3,int __cdecl(int,int)>::s_func" (?s_func@?$UnmanagedFunctionBinding@$MH02$$A6AHHH@Z$$V@Glue@@0V?$function@$$A6AHHH@Z@std@@A)
or
ManagedApi.obj : error LNK2001: unresolved external symbol "private: static class std::function Glue::UnmanagedFunctionBinding<2,void __cdecl(void)>::s_func" (?s_func@?$UnmanagedFunctionBinding@$MH01$$A6AXXZ$$V@Glue@@0V?$function@$$A6AXXZ@std@@A)
to name a few. At this point I'm not crossing any DLL boundaries so I don't know why this code shouldn't link. I thought maybe there is some special C++/CLI mechanic to it, so I've added a native source file (Bindings.cpp
) in which I explicitly instantiated all used templates (including std::function
and std::string
). But now this source files throws the same linker errors. Why?
From the error message I get that it has something todo with the UnmanagedFunctionBinding::s_func
member but I have no idea what the problem is. And why are there 2 different errors LNK2020
and LNK2001
when they seem to address the same problem?