Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best workaround for compiler error C2158: make_public does not support native template types

I have two c++/cli dlls (i.e. compiled with /clr) where A.dll references B.dll. In assembly B, I have a method, GetMgdClassB, I'd like to call from assembly A. Here is the code in assembly B (B.cpp):

namespace B
{
    public class NativeClassB
    {
    public:
        NativeClassB();
        // ... 
    };

    public ref class MgdClassB
    {
    public:
        static MgdClassB ^ GetMgdClassB(const std::vector<NativeClassB *> & vecNativeBs)
        {
            // ...
            vecNativeBs;
            return gcnew MgdClassB();
        }
    };
}

Notice that the method GetMgdClassB takes a std::vector. In assembly A, I attempt to call this method with the following code (A.cpp):

namespace B
{
    class NativeClassB;
}

#pragma make_public(std::vector<B::NativeClassB *>)

namespace A
{
    void Foo()
    {
        std::vector<B::NativeClassB *> vecNativeBs;
        B::MgdClassB::GetMgdClassB(vecNativeBs);
    }
}

When I compile A.cpp, I get the following error:

error C2158: 'std::vector<_Ty>' : #pragma make_public directive is currently supported for native non-template types only

the reason I wanted to add this pragma is because native types are private to the assembly by default. If I remove the pragma I get the following error (as expected):

error C3767: 'B::MgdClassB::GetMgdClassB': candidate function(s) not accessible

since the template instantiation type std::vector<B::NativeClassB *> is private to the assembly.

Attempted Solutions

1. Use void *, break type safety:

Change the method, GetMgdClassB to take a void * and pass the address of the std::vector<NativeClassB *> to the method. In GetMgdClassB. I can then static_cast the passed in void * to std::vector<NativeClassB *> *. This, of course, works, but breaks type safety.

2. Create a Managed wrapper for NativeClassB, pass a managed container

Create a managed class, say ref class NativeClassBWrapper who's sole purpose is to hang on to a reference to the native NativeClassB. Change GetMgdClassB to take a managed container of NativeClassBWrappers (e.g. List<NativeClassBWrapper ^> ^). This has the downside of having to create and populate a new managed container prior to calling GetMgdClassB, and then within managed class B, I have to repackage it into the the native container std::vector<NativeClassB *> (since the code in B deals with this type.

Currently, I'm leaning toward going with Solution #1, since (a) it doesn't introduce any performance concerns and (b) I'll only be doing this in a few cases. I don't like losing the type safety, but it seems justifiable given the current deficiency in the compiler's ability to make native template instantiation types visible.

Question:

Are there better work arounds?

Related Question:

C++ CLI error C3767: candidate function(s) not accessible

like image 458
Matt Smith Avatar asked Oct 14 '22 21:10

Matt Smith


1 Answers

I'm not aware of any way to export that type. If you have to have that function signature, I would lean in the direction of using a mix of managed and native exports (managed functions using native types can't be consumed by other languages anyway), and maybe use delay loading when calling the native exports so you have a chance to trap errors finding the assembly in the usual .NET way.

But your particular function may be problematic since it uses both managed types and complex native types in the signature.

In general, the best practice is to not pass native C++ classes across DLL boundaries at all, since this sets you up for One Definition Rule violations.

For this particular situation, my suggestion is to make an wrapper that implements ICollection. That cures the problem just like your solution #2, without ever having to actually copy all the elements into a new data structure.

like image 111
Ben Voigt Avatar answered Oct 18 '22 05:10

Ben Voigt