用C#做開發已經好幾年了一直用得挺順手的最近有一個項目需要用到DirectShow的相關COM組件也就是想在C#的項目裡面實現一個基於內存流的Filter這個卻讓我著實頭痛了好久
原因就是在C#裡面沒有現成的DirectShow的COM組件的定義雖說在C#中可以使用一些特定的方式來操作COM組件(C#中COM操作(一)——實例化)可是對於DirectShow這樣需要聲明一大批的COM接口類型枚舉卻是一件漫長又枯燥的事件而且搞不好一個不小心中間出個錯誤導致最後調試總不通過而抓狂即便是現在有了DirectShowNET提供的對絕大部分的DirectShow的C#翻譯可是面對網上那麼多的C++開源代碼還有少得可憐的C#操作DirectShow的代碼你會怎麼辦?難道又是一行一行的翻譯成C#嗎反正我是不會這樣干的於是這種方式首先被斃掉了
俗話說最優秀的程序員也是最懶的程序員類和方法能公用的就盡量公用已存在的實現就不需要自己再去寫一次了既然有現成的C++的實現好的代碼為什麼不可以再次拿來用一用呢可是在C#裡面想要使用C++的實現(注意這裡我用的是實現並沒有指定是類或方法)比較常見的是通過 DllImportAttribute特性導入相應的程序集可是這種方式只可以調用方法而且是限定死了的只能是C語言形式的導出方法對於其它的方法是不可以的以前用的最多的就是對Windows API的調用了這樣的使用形式極其不方便C++和C#本都是都面向對象的語言卻要通過面向過程式的方法調用來相互通信感覺很別扭也很不爽對於追求完美的程序員來說這也不是一個上上的選擇於是這種方式也不是最好的暫時作為候選吧再找找看還有沒有其它的方式
後來我想到了以前看過一篇文章說的是在C++裡面通過托管C++作為媒價調用C#裡的代碼把這個反過來就是用C#通過托管C++調用NativeC++ 這樣是否也是可行的呢?不過這都是好幾年前看到的了只怪那時候對於這種跨平台調用的方式研究不深也不怎麼感興趣所以當時就沒有太在意對於文中所說的托管C++的印象也一直停留在中看不中用的程序上如今也只得找來看看雖說信心不大也報著死馬當活馬醫一回的心情試試先
其實前面已經提到過了如果僅僅是對C++的導出方法的調用的話基本不是難事可是C++是面向對象的語言很多情況下都是以類的形式存在的導出的也是 C++的類就拿我找到的C++寫的內存流的Filter來說吧網上的代碼都是基於類的並且我想在C#裡面控制FilterGraph的構建解決怎麼在C#裡面直接或間接使用到這個類才是問題的本質和關鍵所在網上找了幾份資料也查了MSDN裡面都說可以使用托管C++作Wrapper包裝 NativeC++然後編譯成CLI可識別的托管程序集就可以供C#調用了
class MemFilter : public IBaseFilter
{
public:
private:
}
public ref class MemFilterWrapper : IBaseFilter
{
private:
MemFilter* m_pFilter;
public:
// 此處只只需要把MemFilter類的公有方法用托管方工聲明一下在方法的實現中直接調用m_pFilter中相應的成員方法就可以了
}
把這兩個類都編譯到同一個CLI/C++項目中去這樣在C#裡面就可以直接使用MemFilterWrapper這個類間接操作C++類了與其它普通的CLS類沒什麼區別不過需要注意的是對於C++裡面的一些基元類型比如intchar*bool等包括數組需要用CLS裡面相應的類型去替換然後再對外公布成方法也就是說把所有的與C++特性相關的都封裝起來對外只顯示符合公共語言規范的格式不然的話在使用的時候比較麻煩(有可能需要使用C#的不安全代碼)
就像之前提到的那樣我們也可以通過使用托管C++讓C#的類披上一層C++的皮再聲明成導出類這樣就可以供普通C++調用了(前是你必須先安裝Framework)
public class CSharpClass // C# 類
{
public void SayHello(){ ConsoleWriteLine(Hello); }
}
class NativeCPP // C++原生類代理注意這裡的參數返回值什麼的都不能帶有托管類型的痕跡
{
public:
void* m_pCSharpClass;
NativeCPP(void);
SayHello(void);
}
NativeCPP::NativeCPP(void)
{
CSharpClass^ obj = new CSharpClass();
GCHandle^ handle = GCHandleAlloc(obj)
m_pCSharpClass = GCHandle::ToIntPtr(handle)ToPointer();
}
NativeCPP::SayHello(void)
{
GCHandle handle = GCHandle::FromIntPtr(m_pCSharpClass);
CSharpClass^ obj = dynamic_cast<CSharpClass^>(handleTarget);
objSayHello();
}
不過這個時候有個問題普通的C++類用new方式構造的實例可以用C++指針持有在C++代碼中沒有調用delete的時候會一直存在於當前進程的地址空間中這樣在後面的使用中不會出現內存訪問方面的問題可是從C#到C++的過程就不是這的了在C#中對象是由CLI自己管理的當CLI發現一個對象不有直接或間接的引用存在的時候(也就是所謂的無根對象)就會被回收這裡的引用是指的CLI中的引用另外C#的對象如果以引用的形式存在的話那麼在原始的C++裡面中兩個類之間傳送這個對象將會變得不可能因為除了托管C++外其它的代碼根本不能識別C#引用只認識指針因此我們必須把C# 中的引用轉成指針可是如果通過簡單的方式一旦轉成指針後C#中的對象很可能會在沒有引用的時候被GC回收了造成C++代碼再去訪問的時候出內存導常 MS提供了一個名叫GCHandle的結構來解決這樣的問題通過這個結構可以構造一塊內存區域代表這個引用對象GC發現有這麼一塊內存區域存在的時候不管什麼情況下都不會對該對象進行回收用用類似的方法也可以從給定的內存指針上返回所指代的托管對象的引用而這塊內存本身不受托管代碼控制反回的也只是一個指針因此在整個C++的代碼生命周期裡都不用再擔心會引用到無效的C#對象了
這樣的處理訪問方式保留了C#代碼與C++代碼各自的書寫特別真正做到了互不干撓假如我再從C#換到VBNET也是很easy的事情這正是我想要的結果到這裡才發現原來C#與C++互相訪問是如些的簡單其實現實就是這樣你知道的就很簡單不知道的就會覺得很難
From:http://tw.wingwit.com/Article/program/net/201311/12726.html