好的,我会先说这个,然后在发布之前我做了一些查阅并在这里阅读了很多类似的问题和答案。
背景资料 使用Visual Studio Professional 2013
目标: 这个项目是为了我自己的娱乐,并尽可能多地学习。
我有一个名为BinaryTree.h的本机C ++头文件,它只是一个使用递归逻辑构建二叉搜索树的简单数据结构。它本身运作良好。
我想用C#构建一个使用它的GUI。 (它不是真的有用或实用,我只是选择它,因为我想。而且,虽然二叉树类中的逻辑是复杂的(ish),我只需要调用2个方法,addNode方法和toString返回最大深度和节点数的方法)。
我选择使用c ++ / cli包装来完成此任务。一切似乎进展顺利,构建成功,并在我的项目的调试目录中创建.dll文件。
现在,我开始从事C#方面的工作。我将.dll文件添加到引用中。但是,当我输入“using filename.dll;”时我收到错误消息“未找到类型或命名空间......”。
重申我做了一些研究。我发现(似乎在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':使用类模板需要模板参数列表
错误2错误C2512:'BinaryTree':没有合适的默认构造函数
错误3错误C2662:'void BinaryTree :: addNode(Data)':无法将'this'指针从'BinaryTree'转换为'BinaryTree&amp;'
错误4错误C2662:'std :: string BinaryTree :: toString(void)const':无法将'this'指针从'BinaryTree'转换为'const BinaryTree&amp;'
我完全复制了代码,只将命名空间更改为“TreeWrapper”,将类名更改为“TreeWrapperClass”。
为了提供帮助,我在BinaryTree.h文件中添加了一个代码段。还有一些更多的定义'NODE'类,但我不想让它比我需要的更混乱。
经过进一步调查,问题似乎在于使用'通用'。如果我把它全部切换到'模板'它构建得很好,但它不能用作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;
}
答案 0 :(得分:2)
您正在宣布template
,但实际上并未对其进行实例化。 C ++ / CLI模板就像C ++模板一样 - 如果你没有实例化它们,它们就不会存在于编译单元之外。
您在这里寻找 generics (是的,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();
}
这不好,因为你回复std::string
- 你不想使用C#中的那个,所以让我们返回{{1}而是(使用marshal_as
):
System::String^
#include <msclr/marshal_cppstd.h>
在这里,在C#中使用它会好得多。
最后,有这个:
System::String^ wrapperToString()
{
return msclr::interop::marshal_as<System::String^>(tree->toString());
}
请参阅......此处您必须执行一些真正的互操作。您希望将托管对象传递给本地对象进行存储。 GC将阻碍你。
允许GC 重新定位任何托管对象(将其移动到另一个内存位置),但您的本机代码对此无能为力。您需要固定对象,以便GC不会移动它。
有几种方法可以做到这一点,我不知道void wrapperAddNode(Data)
{
tree->addNode(Data);
}
的样子,但我只是假设BinaryTree::addNode
。
对于长期对象固定,您可以使用GCHandle
。
完整代码如下所示:
BinaryTree::addNode(void*)
这为每个节点分配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;
};
,并将其存储在列表中以便以后释放它。释放固定句柄会释放对该对象的引用(因此,如果没有其他内容引用它,它将变为可收集的)以及它的固定状态。
答案 1 :(得分:0)
在挖掘之后,我想我找到了答案,虽然不是最终的,所以如果有人可以插话我会很感激。 (对于任何滥用词汇的事先提前道歉,但我想我可以得到这个想法。)
似乎问题在于本机C ++和泛型中的模板之间的根本区别。模板在编译时实例化,并被视为一种类型。它们不能在运行时更改,而泛型可以。我不认为有一种优雅的方法可以解决这个问题。
至少我完成了我的项目的一个目标,即尽可能多地学习哈哈。我能够让c ++ / cli包装器为没有模板的东西工作,如果我在构建.dll之前为模板选择了一个类型(见上文)
如果有其他人有想法,请告诉我。