0

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?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Timo
  • 9,269
  • 2
  • 28
  • 58
  • 1
    Standard [c++] problem, unrelated to C++/CLI. Only a declaration for the static member but no definition. [Look here](https://stackoverflow.com/questions/2220975/c-static-template-member-one-instance-for-each-template-type) for example. – Hans Passant Nov 17 '19 at 22:01
  • @HansPassant wow how did I miss that. If you make it an answer I'll be happy to accept it. – Timo Nov 17 '19 at 22:13

0 Answers0