简体   繁体   中英

C# Namespace not found for DLL

Ok, I will preface this by saying I did do a look up and read a lot of similar questions and answers on here before posting this.

Background Info Using Visual Studio Professional 2013

Goal: This project is for my own amusement and to try to learn as much as I can.

I have a native C++ header file called BinaryTree.h which is just a simple data structure that uses recursive logic to build a binary search tree. It works quite well on its own.

I want to build a GUI in C# that uses this. (Its not really useful or practical, I just choose it, well because I wanted to. Also, while the logic inside the binary tree class is complex(ish), I only need to call 2 methods, a addNode method, and a toString method which return the max depth and number of nodes).

I choose using a c++/cli wrapper to accomplish this. Everything seemed to go well, the build was successful and a .dll file was created in the debug directory of my project.

Now, I started in on the C# side of things. I added the .dll file to references. However, when I typed in " using filename.dll;" I got an error saying "Type or namespace not found...".

To reiterate I did some researching. I found (it seemed in VS2010) that different target frameworks could cause this error. I checked mine, targets for both were net4.5, so that is not the problem.

Here is the code from my c++/cli wrapper. Perhaps it has something to do with using templates? Any help is appreciated.

#pragma once

#include "D:\Schoolwork 2015\Test Projects\CPPtoC#WrapperTest\CPPLogic\CPPLogic\BinaryTree.h"

using namespace System;

namespace BinaryTreeWrapper {

    template<class Data>
    public ref class BinaryTreeWrapperClass
    {
    public:
        BinaryTreeWrapperClass(){ tree = new BinaryTree(); }
        ~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }
        !BinaryTreeWrapperClass(){ delete tree; }

        //methods
        void wrapperAddNode(Data)
        {
            tree->addNode(Data);
        }
        std::string wrapperToString()
        {
            return tree->toString();
        }

    private:
        BinaryTree* tree;
    };
}

Screenshot of the error:

错误

EDIT Ok, so here is a weird thing... my original file built just fine with the new code and produced a .dll file. However, I decided to try a fresh project since the namespace was still not being found. Upon moving the code over and trying to build, I've run into 4 errors:

Error 1 error C2955: 'BinaryTree' : use of class template requires template argument list

Error 2 error C2512: 'BinaryTree' : no appropriate default constructor available

Error 3 error C2662: 'void BinaryTree::addNode(Data)' : cannot convert 'this' pointer from 'BinaryTree' to 'BinaryTree &'

Error 4 error C2662: 'std::string BinaryTree::toString(void) const' : cannot convert 'this' pointer from 'BinaryTree' to 'const BinaryTree &'

I copied the code exactly, only changing the namespace to "TreeWrapper' and the class name to 'TreeWrapperClass'.

To help, I've include a snippet from my BinaryTree.h file. There is a bunch more that defines the 'NODE' class, but I didn't want to clutter it up more than i needed.

After further investigation, it appears the problem lies with using 'generic'. If I switch it all to 'template' it builds just fine, but then it can't be used as a reference in C# (getting the namespace error). I built a test project using very simple methods (no templates) and was able to use the .dll wrapper I made in C#. So the problem lies with templates and generics.

Last Edit I've found if I change the code to initiate the template as 'int' it works just fine, and I can use it in C#. For example:

...
BinaryTreeWrapperClass(){ tree = new BinaryTree<int>(); }
....
private:
BinaryTree<int>* tree;

BinaryTree.h

template<class Data>
class BinaryTree
{
private:
    Node<Data>* root;
    unsigned int nNodes;
    unsigned int maxDepth;
    unsigned int currentDepth;
    void traverse(Node<Data>*& node, Data data);
public:
    BinaryTree();
    ~BinaryTree();
    void addNode(Data);
    std::string toString() const 
    {
        std::stringstream sstrm;
        sstrm << "\n\t"
            << "Max Depth:       " << maxDepth << "\n"
            << "Number of Nodes: " << nNodes << "\n";
        return sstrm.str(); // convert the stringstream to a string
    }

};

template<class Data>
BinaryTree<Data>::BinaryTree() //constructor
{
    //this->root = NULL;
    this->root = new Node<Data>();      //we want root to point to a null node.
    maxDepth = 0;
    nNodes = 0;
}

template<class Data>
BinaryTree<Data>::~BinaryTree() //destructor
{

}

template<class Data>
void BinaryTree<Data>::addNode(Data data)
{
    traverse(root, data);   //call traverse to get to the node
    //set currentDepth to 0
    currentDepth = 0;
}

template<class Data>
void BinaryTree<Data>::traverse(Node<Data>*& node, Data data)
{
    //increment current depth
    currentDepth++;

    if (node == NULL)       //adds new node with data
    {
        node = new Node<Data>(data);
        //increment nNode
        nNodes++;
        //increment maxDepth if current depth is greater
        if (maxDepth < currentDepth)
        {
            maxDepth = currentDepth - 1;        //currentDepth counts root as 1, even though its 0;
        }
        return;
    }

    else if (node->getData() >= data)       //case for left, getData must be bigger. The rule is, if a number is equal to getData or greater, it is added to the left node
    {
        Node<Data>* temp = node->getLeftNode();
        traverse(temp, data);               //recursive call, going down left side of tree
        node->setLeftNode(temp);
    }
    else if (node->getData() < data)        //case for right, getData must be less
    {
        Node<Data>* temp = node->getRightNode();
        traverse(temp, data);
        node->setRightNode(temp);
    }
    return;
}

You're declaring a template , but are not actually instantiating it. C++/CLI templates are just like C++ templates - if you don't instantiate them, they just don't exist outside of the compilation unit.

You're looking for generics here (yes, C++/CLI has both templates and generics). And here's how you declare a generic in C++/CLI:

generic<class Data>
public ref class BinaryTreeWrapperClass
{
    // ...
}

But you'll get stuck at this point for several reasons.

First, I'll include the parts which are OK:

generic<class Data>
public ref class BinaryTreeWrapperClass
{
public:
    BinaryTreeWrapperClass(){ tree = new BinaryTree(); }
    ~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }
    !BinaryTreeWrapperClass(){ delete tree; }

private:
    BinaryTree* tree;
};

You've got this right.

Next, let's look at:

std::string wrapperToString()
{
    return tree->toString();
}

That's no good, since you're returning an std::string - you don't want to use that from C#, so let's return a System::String^ instead (using marshal_as ):

#include <msclr/marshal_cppstd.h>
System::String^ wrapperToString()
{
    return msclr::interop::marshal_as<System::String^>(tree->toString());
}

Here, that's much better for use in C#.

And finally, there's this:

void wrapperAddNode(Data)
{
    tree->addNode(Data);
}

See... here you'll have to do some real interop. You want to pass a managed object to a native one for storage. The GC will get in your way.

The GC is allowed to relocate any managed object (move it to another memory location), but your native code is clueless about this. You'll need to pin the object so that the GC won't move it.

There are several ways to do this, and I don't know what BinaryTree::addNode looks like, but I'll just suppose it's BinaryTree::addNode(void*) .

For long-term object pinning, you can use a GCHandle .

The full code looks like this:

generic<class Data>
public ref class BinaryTreeWrapperClass
{
public:
    BinaryTreeWrapperClass()
    {
        tree = new BinaryTree();
        nodeHandles = gcnew System::Collections::Generic::List<System::Runtime::InteropServices::GCHandle>();
    }

    ~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }

    !BinaryTreeWrapperClass()
    {
        delete tree;
        for each (auto handle in nodeHandles)
            handle.Free();
    }

    void wrapperAddNode(Data data)
    {
        auto handle = System::Runtime::InteropServices::GCHandle::Alloc(
            safe_cast<System::Object^>(data),
            System::Runtime::InteropServices::GCHandleType::Pinned);
        nodeHandles->Add(handle);
        tree->addNode(handle.AddrOfPinnedObject().ToPointer());
    }

    System::String^ wrapperToString()
    {
        return msclr::interop::marshal_as<System::String^>(tree->toString());
    }

private:
    BinaryTree* tree;
    System::Collections::Generic::List<System::Runtime::InteropServices::GCHandle>^ nodeHandles;
};

This allocates a GCHandle for each node, and stores it in a list in order to free it later. Freeing a pinned handle releases a reference to the object (so it becomes collectable if nothing else references it) as well as its pinned status.

After digging around I think I found the answer, although not definitively, so if anyone can chime in I would appreciate it. (Apologies in advance for any misuse of vocabulary, but I think I can get the idea across).

It seems the problem lies in the fundamental difference between templates in native C++ and generics. Templates are instantiated at compilation, and are considered a type. They cannot be changed at runtime, whereas generics can. I don't think there is an elegant way to solve that.

At least I accomplished one goal of my project, which was learning as much as I can haha. I was able to get c++/cli wrappers working for things without templates, and if I choose a type for the template before building the .dll (see above)

If anyone else has an idea please let me know.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM