找不到 DLL 的 C# 命名空间

C# Namespace not found for DLL

好的,我会先说在发布之前我确实做了查找并阅读了很多类似的问题和答案。

背景信息 使用 Visual Studio 专业版 2013

目标: 这个项目是为了我自己的娱乐,并尽可能多地学习。

我有一个名为 BinaryTree.h 的本机 C++ 头文件,它只是一个使用递归逻辑构建二叉搜索树的简单数据结构。它本身工作得很好。

我想在 C# 中构建一个使用它的 GUI。 (它不是真的有用或实用,我只是选择它,因为我想这样做。另外,虽然二叉树 class 内部的逻辑很复杂,但我只需要调用 2 个方法,一个 addNode 方法,以及一个 toString 方法,其中 return 最大深度和节点数)。

我选择使用 c++/cli 包装器来完成此操作。一切似乎都很顺利,构建成功,并在我的项目的调试目录中创建了一个 .dll 文件。

现在,我从 C# 方面着手。我将 .dll 文件添加到引用中。但是,当我输入“using filename.dll;”时我收到一条错误消息 "Type or namespace not found..."。

重申一下,我做了一些研究。我发现(在VS2010中似乎)不同的目标框架可能会导致这个错误。我检查了我的,两者的目标都是 net4.5,所以这不是问题。

这是我的 c++/cli 包装器的代码。也许它与使用模板有关?感谢任何帮助。

#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;
    };
}

错误截图:

编辑 好的,这是一件奇怪的事情……我的原始文件使用新代码构建得很好,并生成了一个 .dll 文件。但是,我决定尝试一个新项目,因为仍未找到命名空间。在移动代码并尝试构建时,我 运行 出现了 4 个错误:

错误 1 ​​错误 C2955:'BinaryTree':使用 class 模板需要模板参数列表

错误 2 错误 C2512:'BinaryTree':没有合适的默认构造函数可用

错误 3 错误 C2662:'void BinaryTree::addNode(Data)':无法将 'this' 指针从 'BinaryTree' 转换为 'BinaryTree &'

错误 4 错误 C2662:'std::string BinaryTree::toString(void) const':无法将 'this' 指针从 'BinaryTree' 转换为 'const BinaryTree &'

我完全复制了代码,只是将命名空间更改为“TreeWrapper”并将 class 名称更改为 'TreeWrapperClass'。

为了提供帮助,我在 BinaryTree.h 文件中包含了一个片段。还有更多定义 'NODE' class,但我不想把它弄得乱七八糟。

经过进一步调查,问题似乎出在使用 'generic' 上。如果我将它全部切换为 'template' 它构建得很好,但它不能用作 C# 中的引用(出现命名空间错误)。我使用非常简单的方法(无模板)构建了一个测试项目,并且能够使用我在 C# 中制作的 .dll 包装器。所以问题出在模板和泛型上。

上次编辑 我发现如果我更改代码以将模板启动为 'int' 它工作得很好,我可以在 C# 中使用它。例如:

...
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;
}

您正在声明一个 template,但实际上并没有实例化它。 C++/CLI 模板就像 C++ 模板一样 - 如果您不实例化它们,它们就不会存在于编译单元之外。

您在此处查找 泛型(是的,C++/CLI 具有两者 模板和泛型)。以下是在 C++/CLI 中声明泛型的方式:

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

但是由于多种原因,您会卡在这一点上。

首先,我将包括好的部分:

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

private:
    BinaryTree* tree;
};

你猜对了。

接下来我们来看:

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

这不好,因为您要 return 一个 std::string - 您不想在 C# 中使用它,所以让我们 return 一个 System::String^相反(使用 marshal_as):

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

在这里,这更适合在 C# 中使用。

最后,还有这个:

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

看...在这里你必须做一些真正的互操作。您想要将托管对象传递给本机对象进行存储。 GC 会妨碍您。

允许 GC 重新定位 任何托管对象(将其移动到另一个内存位置),但您的本机代码对此一无所知。您需要 固定 对象,这样 GC 就不会移动它。

有几种方法可以做到这一点,我不知道 BinaryTree::addNode 是什么样子,但我假设它是 BinaryTree::addNode(void*)

对于长期对象固定,您可以使用 GCHandle

完整代码如下所示:

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;
};

这为每个节点分配一个GCHandle,并将其存储在一个列表中,以便稍后释放它。释放 pinned 句柄会释放对该对象的引用(因此如果没有其他引用它,它就可以收集)及其固定状态。

在四处寻找之后,我想我找到了答案,虽然不是确定的,所以如果有人能插话,我将不胜感激。 (为任何滥用词汇提前道歉,但我想我可以理解这个想法)。

看来问题出在本机 C++ 中的模板与泛型之间的根本区别。模板在编译时实例化,并被视为一种类型。它们不能在运行时更改,而泛型可以。我认为没有一种优雅的方法可以解决这个问题。

至少我完成了项目的一个目标,那就是尽可能多地学习哈哈。我能够让 c++/cli 包装器在没有模板的情况下工作,如果我在构建 .dll 之前为模板选择一个类型(见上文)

如果其他人有任何想法,请告诉我。