CLR基本原理和如何運用於GOCW


   GOCW的重點和難點就在於Csharp調用OpenCV,其中的橋梁就是CLR,當然我們也有其他方法,但是CLR是一個比較新的、比較可靠的、關鍵是能用的橋梁。這里關於CLR的基本原理知識、如何用於GOCW項目的相關內容加以整理思考,以圖深入:

一、什么是CLR;

1、什么是CLR

CLR(Common Language Runtime)是“公共語言運行時”的縮寫,簡單來說它是和Java虛擬機一樣的一個運行時環境。它負責資源管理(內存分配和垃圾收集),並保證應用和底層操作系統之間必要的分離。

通用語言運行時是.NET 框架應用程序的執行引摯。它提供了許多服務,其中包括:代碼管理(裝入和執行)、類型安全性驗證、元數據(高級類型信息)訪問、為管理對象管理內存、管理代碼,COM對象和預生成的DLLs(非管理代碼和數據)的交互操作性、對開發人員服務的支持等等。

我們GOCW項目中為了能夠使用Csharp調用OpenCV,采用了托管C++;

 

2、什么是托管C++?

       托管是.NET的一個專門概念,它是融於通用語言運行時(CLR)中的一種新的編程理念,使用托管C++意味着,我們的代碼可以被CLR所管理,並能開發出具有最新特性如垃圾自動收集、程序間相互訪問等的.NET框架應用程序。

  由托管概念所引發的C++應用程序包括托管代碼、托管數據和托管類三個組成部分。  

  (1) 托管代碼:. Net環境提供了許多核心的運行(RUNTIME)服務,比如異常處理和安全策略。為了能使用這些服務,必須要給運行環境提供一些信息代碼(元數據),這種代碼就是托管代碼。所有的C#、VB.NET、JScript.NET默認時都是托管的,但Visual C++默認時不是托管的,必須在編譯器中使用命令行選項(/CLR)才能產生托管代碼。

  (2) 托管數據:與托管代碼密切相關的是托管數據。托管數據是由公共語言運行的垃圾回收器進行分配和釋放的數據。默認情況下,C#、Visual Basic 和 JScript.NET 數據是托管數據。不過,通過使用特殊的關鍵字,C# 數據可以被標記為非托管數據。Visual C++數據在默認情況下是非托管數據,即使在使用 /CLR 開關時也不是托管的。

  (3) 托管類: 盡管Visual C++數據在默認情況下是非托管數據,但是在使用C++的托管擴展時,可以使用"__gc"關鍵字將類標記為托管類。就像該名稱所顯示的那樣,它表示類實例的內存由垃圾回收器管理。另外,一個托管類也完全可以成為 .NET 框架的成員,由此可以帶來的好處是,它可以與其他語言編寫的類正確地進行相互操作,如托管的C++類可以從Visual Basic類繼承等。但同時也有一些限制,如托管類只能從一個基類繼承等。需要說明的是,在托管C++應用程序中既可使用托管類也可以使用非托管類。這里的非托管類不是指標准C++類,而是使用托管C++語言中的__nogc關鍵字的類。

3、托管C++與標准C++的主要區別

    盡管托管C++是從標准C++建立而來的,但它與標准C++有着本質上的區別,這主要體現在以下幾個方面:
  (1) 廣泛采用"名稱空間"(namespace)
  名稱空間是類型的一種邏輯命名方案,.NET使用該命名方案用於將類型按相關功能的邏輯類別進行分組,利用名稱空間可以使開發人員更容易在代碼中瀏覽和引用類型。當然,我們也可將名稱空間理解成是一個"類庫名"。

       (2) 基本數據類型的變化

  我們知道,標准C++語言的數據類型是非常豐富的。而托管C++的數據類型更加豐富,不僅包含了標准C++中的數據類型,而且新增了__int64 (64位整型)、Decimal(96位十進制數)、String*(字符串類型)和Object*(對象類型)等類型,表1-1列出它們各自數據類型。

      (3) 新增三個托管C++類型:__gc class、__value class和__gc interface

  一個__gc類或結構意味着該類或結構的生命周期是由.NET開發平台自動管理及垃圾自動收集,用戶不必自已去調用delete來刪除。定義一個__gc類或結構和標准C++基本相似,所不同的是在class或struct前加上__gc。

 

 
二、CLR為什么能用於Csharp和C++互相調用
 
      基本的思路是將C++代碼封裝成為托管代碼,而CSharp代碼本來就可以翻譯成CLR語句。在這種情況下,C++實現的效果,能夠直接被CSharp調用,從而達到聯合的目的。
      其中的難點,其實並不是引用,而是參數的傳遞:如何將“圖像”這種本質上較為巨大的數據在兩種系統里面傳遞,所以必然就需要有內存的操作;此外CLR語言的編碼方法和普通CSharp差距較大,也是需要注意。
      一般來說:
      有C#及C++背景的人使用C++/CLI的必備知識:
  1、C++/CLI里的new等於C++里的new, gcnew等於C#里的new
  2、原生指針用*表示,托管引用使用^表示
  如: Stream^ stream = gcnew Stream();
  3、array<unsigned char>^ 等於 System.Byte[]
  4、pin_ptr關鍵字能把托管引用轉換為原生指針:
  如: pin_ptr<BYTE> pBytes = & byteArray[0];
  然后pBytes就可以當作原生的BYTE* 使用了. 
  等代碼執行完pBytes的有效范圍,byteArray就會恢復可被GC處理的狀態
 
三、通過CLR傳遞Mat和Bitmap:
 
      這里應該算是核心代碼的解析,完整代碼可以自己看,主要講接口
Csharp(BitMap)->Mat->Csharp(BitMap) ,幾乎全部的內容都在CLR形式的C++代碼中,其它地方只是實現接口。

     ////////將輸入cli::array<unsigned char>轉換為cv::Mat//////////////////
    pin_ptr <System : :Byte > p1  =  &pCBuf1[ 0];
     unsigned  char * pby1  = p1;
    cv : :Mat img_data1(pCBuf1 - >Length, 1,CV_8U,pby1);
    cv : :Mat img_object  = cv : :imdecode(img_data1,IMREAD_UNCHANGED);
     if ( !img_object.data)
         return nullptr;
這里注意,內存操作其實是在imdecode中實現的,我們相信OpenCV已經做了比較好封裝。

System : :Drawing : :Bitmap ^ MatToBitmap( const cv : :Mat & img)
{
     if (img.type()  != CV_8UC3)
    {
         throw gcnew NotSupportedException( "Only images of type CV_8UC3 are supported for conversion to Bitmap");
    }
     //create the bitmap and get the pointer to the data
    PixelFormat fmt(PixelFormat : :Format24bppRgb);
    Bitmap  ^bmpimg  = gcnew Bitmap(img.cols, img.rows, fmt);
    BitmapData  ^data  = bmpimg - >LockBits(System : :Drawing : :Rectangle( 00, img.cols, img.rows), ImageLockMode : :WriteOnly, fmt);
    Byte  *dstData  =  reinterpret_cast <Byte * >(data - >Scan0.ToPointer());
     unsigned  char  *srcData  = img.data;
     for ( int row  =  0; row  < data - >Height;  ++row)
    {
        memcpy( reinterpret_cast < void * >( &dstData[row *data - >Stride]),  reinterpret_cast < void * >( &srcData[row *img.step]), img.cols *img.channels());
    }
    bmpimg - >UnlockBits(data);
     return bmpimg;
}
bmp是有LocKBits操作的,就是在這里將Bitmap處理的結果固定在內存中的。
Bitmap類使用LockBits和UnLockBits方法來將位圖的數據矩陣保存在內存中、直接對它進行操作,最后用修改后的數據代替位圖中的原始數據。 LockBits返回以各BitmapData的類用已描述數據在已鎖定的矩陣中的位置和分布。
      BitmapData類包括以下幾個重要的屬性:
  • Scan0:數據矩陣在內存中的地址。
  • Stride:數據矩陣中的行寬,以byte為單位。可能會擴展幾個Byte,后面會介紹。
  • PixelFormat:像素格式,這對矩陣中字節的定位很重要。
  • Width:位圖的寬度。
  • Height:位圖的高度。

  具體關系見下圖:

 
   如上圖所示,stride屬性表示位圖數據矩陣的行寬,以byte為單位。出於效率考慮,矩陣的行寬並非剛好是每行像素數的整數倍,系統往往會將其封裝成4的整數倍。舉例來說,對於一幅24位深17像素寬的圖像,其stride屬性為52;每行的數據量為17*3=51,系統將其自動封裝一個字節,所以它的stride為52byte(或13*4byte)。對於一幅17像素寬的4位索引圖,其stride為12,其中9byte(准確地說是8.5個byte)用來記錄數據信息,每行再自動添加3(3.5)個byte保證其為4的整數倍。
   具體數據的分布因其pixelformat而異。24位深的圖像每隔3個byte包含一組RGB信息;32位深的圖像每隔4個byte包含一組RGBA信息。那些每個字節包含多個像素的pixelformat,比如4位索引圖像或1位索引圖像,必須經過仔細處理,從而保證同一字節中的相鄰byte不會混淆。
指針的准確定位
  • 32位RGB:假設X、Y為位圖中像素的坐標,則其在內存中的地址為scan0+Y*stride+X*4。此時指針指向藍色,其后分別是綠色、紅色,alpha分量。
  • 24位RGB:scan0+Y*stride+X*3。此時指針指向藍色,其后分別是綠色和紅色。
  • 8位索引:scan0+Y*stride+X。當前指針指向圖像的調色盤。
  • 4位索引:scan0+Y*stride+(X/2)。當前指針所指的字節包括兩個像素,通過高位和低位索引16色調色盤,其中高位表示左邊的像素,低位表示右邊的像素。
  • 1位索引:scan0+Y*stride+X/8。當前指針所指的字節中的每一位都表示一個像素的索引顏色,調色盤為兩色,最左邊的像素為8,最右邊的像素為0 (TODO EMGUCV ISSUE)
     
四、CLR傳遞int和string
 
這樣的圖像處理程序,肯定不是只傳遞圖像就夠了的:在處理圖像的過程中,可能遇到錯誤和異常,最好是以errorCode也就是一個int的樣式反饋回來;處理的結果可能是一段比較長的數據,這個最好是string類型反饋回來。經過一些實驗,目前得到以下的解決方案。
 
a、傳遞int:
由於int是一個系統默認結構,也就是無論c++還是csharp還是clr中,都有這樣的基本的結構,所以基本上不需要轉化,一方面,我們可以直接將int作為參數傳遞進入函數,也可以作為返回值進行反饋。但是一般來說,我們肯定希望是能夠傳遞參數地址,這樣可以得到”修改參數“目的:
這樣操作,clr中這樣定義
//2.引用傳遞int
         int GOClrClass : :allTest( int a, int b,  int * c);
並實現
int  GOClrClass : :allTest( int a, int b,  int * c)
{
     *c =a +b; 
     return  *c;
}
那么在Csharp調用過程中,最重要的一步,就是需要給這個int c一個固定的內存地址,所以需要這樣操作
unsafe
        {
         int * value  = stackalloc  int[ 1];
        value[ 0=  0;
         int iret  = client.allTest( 23, value);
        }
需要注意, 由於有個int*類型參數,在C#里指針屬於不安全代碼,因此使用unsafe關鍵字將涉及到指針的代碼包括起來,在工程屬性里設置允許使用不安全代碼。定義int指針需要使用stackalloc關鍵字,創建一個int數組,對數組賦值后,將指針傳遞給類函數。
其實,這里就是直接強制地將int的地址傳遞過去,因為它簡單嘛。
 
b、傳遞string :
 
string類型復雜許多,因為它不僅僅是char[]的集合,肯定還包括許多其它的東西。復雜度關系,這里我沒有實現”引用傳值“,但是能夠得到一種解決方法。
基本上來說,c#中的std::string 和clr中的system::String^之間存在直接轉換關系,估計在clr翻譯的過程中就是直接翻譯的,所以可以這樣來做:
 
clr處程序編寫
 
System : :String ^ GOClrClass : :allTestStr(System : :String ^ inputStr)
{
    System : :String ^ retstr  =   "fsdfsdf";
     return retstr;
}
 
c#處程序編寫
 
    string   s  =  client . allTestStr ( "abcdefg" );
 
能夠直接將string傳遞到clr中(比如直接傳遞圖片地址),並且返回string結果。應該還是有一定作用的。
 
感謝閱讀至此,希望有所幫助。
 
 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM