2015-05-18 118 views
1

好的,我將在此前序言中說我在查看之前查看過很多類似的問題和答案。找不到DLL的C#命名空間

背景信息 使用Visual Studio專業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; 
    }; 
} 

截圖錯誤:

Error

編輯 好了,所以這裏是一個奇怪的事情......我的原始文件剛剛建成的新代碼罰款和產生。 DLL文件。然而,我決定嘗試一個新的項目,因爲名字空間還沒找到。在上方移動代碼,並試圖建立,我碰到的4個錯誤:

錯誤1個錯誤C2955:「二叉樹」:使用類模板需要模板參數列表

錯誤2錯誤C2512:「二叉樹':沒有適當的默認的構造可用

錯誤3錯誤C2662:‘無效二叉樹:: ADDNODE(數據):‘從指針‘二叉樹’不能轉換’這個’到‘二叉樹&’

錯誤4錯誤C2662 :'std :: string BinaryTree :: toString(void)const':無法將'this'指針從'BinaryTre e'到'const BinaryTree &'

我完全複製了代碼,只是將名稱空間更改爲「TreeWrapper」,並將類名更改爲「TreeWrapperClass」。

爲了幫助,我從我的BinaryTree.h文件中加入了一段代碼。還有一些更多的定義了'NODE'類,但我並不想把它弄得比我需要的多。

經過進一步調查,似乎問題在於使用'通用'。如果我將它全部切換到'模板',它的構建就很好,但它不能用作C#中的引用(獲取命名空間錯誤)。我使用非常簡單的方法(無模板)構建了一個測試項目,並且能夠使用我在C#中創建的.dll包裝器。所以問題在於模板和泛型。

最後編輯 我,如果我更改代碼以啓動模板「詮釋」它工作得很好,我可以在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

我創建了一個空的類庫項目並編譯它,然後導入到C#項目中,並且不能重複此問題。 Visual Studio立即看到新的名稱空間。我會建議通過構建選項運行,並確保'Common Language Runtime Support'設置爲'/ clr' – ahwm

+0

這非常奇怪。我檢查以確保該選項設置爲/ clr並且是。 我也嘗試創建一個新的庫項目,以及相同的問題。 – Orannis

+1

@ahwm這不是問題在這裏(我會寫一個答案來解釋這個代碼有什麼問題) –

回答

2

你聲明template,但實際上並沒有將其實例化。 C++/CLI模板就像C++模板 - 如果你沒有實例化它們,它們就不存在於編譯單元之外。

您正在尋找仿製藥這裏(是的,C++/CLI有模板和泛型)。這裏是你如何在C++/CLI中聲明一個通用的:

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

但是你會因爲幾個原因而停滯不前。

首先,我將包括作爲OK的部分:

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#,所以讓我們回到一個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允許重新定位任何託管對象(將其移動到另一個內存位置),但您的本機代碼對此毫無頭緒。你需要pin這個對象,以便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,並將其存儲在列表中。釋放一個固定句柄釋放對該對象的引用(因此,如果沒有其他引用的話,它將變爲可收集的)以及其固定狀態。

+0

非常感謝。 System :: String ^很有意義,我不相信我忘了它。我對cli一點都不熟悉,只是腦海中浮現出來。 GCHandle也很有意義。我沒有想到必須固定它,所以土着班會知道它在哪裏。 'auto'關鍵字與native C++中的「var」類似嗎? 再一次,謝謝你的深入和翔實的答案。 – Orannis

+0

在我的C#程序中是否有特定的語法來使用生成的.dll文件?我仍然在「找不到類型或命名空間」。我使用的語法是「使用BinaryTreeWrapper;」。它既是.dll的名稱,也是名稱空間的名稱。這可能導致衝突嗎? – Orannis

+0

在C++(和C++/CLI)中,'auto'與C#中的'var'相同。並沒有特別的語法來使用DLL,它應該工作,如果你正確設置你的參考。您也可以嘗試將C#項目平臺設置爲與C++/CLI項目相同。 –

0

經過深入挖掘,我想我找到了答案,雖然不是明確的,所以如果任何人都可以參加,我會很感激。 (事先對任何詞彙濫用表示歉意,但我認爲我可以將這個想法貫穿始終)。

看來問題在於本地C++和泛型中模板之間的根本區別。模板在編譯時被實例化,並被認爲是一種類型。它們不能在運行時更改,而泛型可以。我不認爲有一個很好的方法來解決這個問題。

至少我完成了我的項目的一個目標,這是我儘可能多的學習哈哈。我能夠在沒有模板的情況下使C++/cli包裝工作,並且如果我在構建.dll之前選擇了模板類型(請參見上文)

如果有其他人有想法,請告訴我。