Pages

2011年12月28日 星期三

.NET Framework Note&Environment

MessageBox::Show

<<範例>>##ReadMore##
String ^message = gcnew String(L"Your background is complicated. Pls move to another place!");
String ^caption = gcnew String(L"Check Result");
::DialogResult result;

// Displays the MessageBox
result = MessageBox::Show(this, message, caption, MessageBoxButtons::OK, MessageBoxIcon::Information);

if(result == ::DialogResult::OK)
{
    this->Close();
}



Dialog Used

  • 如果想在按下按鈕後,彈出 Dialog,就需要在按鈕按下的事件加入以下內容
    if(m_OpenImageDialog->ShowDialog() == System::Windows::Forms::DialogResult::OK)
    {
    char *filename = (char*)(void*)Marshal::StringToHGlobalAnsi(m_OpenImageDialog->FileName);
    // Add anything what you want
    }

  • 如果想加入副檔名的 filter,就加在 Dialog::Filter (記得從 Designer 的屬性欄修改)

    bmp files (*.bmp)|*.bmp|jpg files (*.jpg)|*.jpg|All files (*.*)|*.*

  • 也可以設定每次開始的起始目錄 (Dialog::InitialDirectory)


    c:\\
KeyBoard Hook (DllImport)

<<宣告>>
bool isHide;
IntPtr m_HookHandle;
 
// 利用函式物件封裝 Hook Procedure
delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam); 

[DllImport("user32.dll", CharSet = CharSet::Ansi, CallingConvention = CallingConvention::StdCall)]
static IntPtr SetWindowsHookEx(int idHook, HookProc ^lpfn, IntPtr hInstance, int threadId);

[DllImport("user32.dll", CharSet = CharSet::Auto, CallingConvention = CallingConvention::StdCall)]
static int CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", CharSet = CharSet::Auto, CallingConvention = CallingConvention::StdCall)]
static bool UnhookWindowsHookEx(IntPtr idHook);

<<定義>>
int KeyBoardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
     PKBDLLHOOKSTRUCT keyInfo; // KBDLLHOOKSTRUCT Pointer
     keyInfo = (PKBDLLHOOKSTRUCT)lParam.ToPointer();

     DWORD code = keyInfo->scanCode;

     bool isPressed = ((keyInfo->flags & 0x80) == 0);
 
     if(isPressed && code == 0x20) // If D is pressed
    {
         /*
          * do what you want to do
          */
          return CallNextHookEx(IntPtr(0), nCode, wParam, lParam); 
    }
}

<<使用>>
1. 建立Hook
      HookProc ^hook = gcnew HookProc(this, &MainForm::KeyBoardHookProc);
     m_HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, hook, IntPtr(0), 0);
 

2. 釋放Hook
    UnhookWindowsHookEx(m_HookHandle);
 

<<注意事項>> 
  1. 一次押放,會收到兩次相同的 scan code,但 flag 不同 (0x00 & 0x80)。所以,若只想要處理一次,就需要去判斷 flag。 
  2. 若不使用 Hook,記得釋放該資源。 
  3. Hook Keyboard Tool .. (see)。 
  4. 記得在 stdafx.h 補上#define _WIN32_WINNT 0x0600,要寫在其他檔案也行,數值必須大於 0x0400。我寫 0x0600 是 Vista 定義的。

Timer Type
我目前使用的 Timer,是定義在 System::Windows::Forms 的類別,它是 Single-thread;如果要使用 Multi-thread Timer,則要使用 System::Timer。


Convert Class

<<例如>>
int a = Convert::ToInt32(gcnew String(L"123"));   // a = 123


The purpose of  /D

在「Project 右鍵 -> Properties -> Configuration Properties -> C/C++ -> Command Line」可以看到這個「/D」參數,它的目的就是幫你「#define」。如果你有一些 Debug 視窗,希望不要在 Release 版本出現,就需要在編譯的時候告知編譯器。VS2005 C++ Debug 版本的編譯器參數如下:
/Od /D "WIN32" /D "_DEBUG" /D "_UNICODE" /D "UNICODE" /FD /EHa /MDd /Fo"Debug\\" /Fd"Debug\vc80.pdb" /W3 /nologo /c /Zi /clr /TP /errorReport:prompt /FU "c:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll" /FU "c:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll" /FU "c:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll" /FU "c:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll" /FU "c:\Windows\Microsoft.NET\Framework\v2.0.50727\System.XML.dll"


Get Specific Folder Path

<<網址>>
Environment::SpecialFolder 列舉型別

<<例如>>
Environment::GetFolderPath(Environment::SpecialFolder::Desktop);

參考資料


在 Visual C++ .NET 中將 System::String* 轉換為 Char*
<<方法一>>

http://support.microsoft.com/kb/311259

<<方法二>>
//using namespace System::Runtime::InteropServices;System::String * str = S"Hello world\n";
char *str2 = (char*)(void*)Marshal::StringToHGlobalAnsi(str);
printf(str2);
Marshal::FreeHGlobal(str2); => 會有錯誤訊息,改成 Marshal::FreeHGlobal(Intptr(str2));


利用滑鼠事件框出ROI


MyClass(void)
{
   InitializeComponent();
   m_Width = 640;
   m_Height = 480;

   m_Pressed = false;
   m_ROIPoint = System::Drawing::Point(0, 0);
   m_ROIRect = System::Drawing::Rectangle(0, 0, m_Width, m_Height);
   m_OriginBMP = gcnew Bitmap(m_Width, m_Height);
}

int m_Width;

int m_Height;
bool m_Pressed;
System::Drawing::Point m_ROIPoint;
System::Drawing::Rectangle m_ROIRect;
System::Drawing::Bitmap ^m_OriginBMP;

....


void FormMouseDown(System::Object ^sender, System::Windows::Forms::MouseEventArgs ^e)

{
   m_ROIPoint.X = e->X;
   m_ROIPoint.Y = e->Y;
   m_Pressed = true;
}
void FormMouseMove(System::Object ^sender, System::Windows::Forms::MouseEventArgs ^e)
{
  if(m_Pressed)
  {
    System::Drawing::Rectangle cloneRect = System::Drawing::Rectangle(0, 0, m_Width, m_Height);
    System::Drawing::Imaging::PixelFormat format = m_OriginBMP->PixelFormat;
    Bitmap ^clone = m_OriginBMP->Clone(cloneRect, format);

    System::Drawing::Rectangle rect = System::Drawing::Rectangle(0, 0, 0, 0);
    rect.X = min(e->X, m_ROIPoint.X);
    rect.Y = min(e->Y, m_ROIPoint.Y);
    rect.Width = max(e->X, m_ROIPoint.X) - rect.X;
    rect.Height = max(e->Y, m_ROIPoint.Y) - rect.Y;

    Graphics ^bmpGraphics = Graphics::FromImage(clone);

    bmpGraphics->DrawRectangle(Pens::Red, rect);
    delete bmpGraphics;
    Graphics ^g = this->CreateGraphics();
    g->DrawImage(clone, Point(0,0));
    delete clone;
    //g->DrawString(String::Format("x={0}, y={1}", e->X.ToString(), e->Y.ToString()), this->Font,    Brushes::Black, 320, 20);
    //g->DrawString(String::Format("x={0}, y={1}", rect.X.ToString(), rect.Y.ToString()), this->Font, Brushes::Black, 320, 30);
    //g->DrawString(String::Format("w={0}, h={1}", rect.Width.ToString(), rect.Height.ToString()), this->Font, Brushes::Black, 320, 40);
    delete g;
  }
}
void FormMouseUp(System::Object ^sender, System::Windows::Forms::MouseEventArgs ^e)
{
   System::Drawing::Rectangle cloneRect = System::Drawing::Rectangle(0, 0, m_Width, m_Height);
   System::Drawing::Imaging::PixelFormat format = m_OriginBMP->PixelFormat;
   Bitmap ^clone = m_OriginBMP->Clone(cloneRect, format);
   m_ROIRect.X = min(e->X, m_ROIPoint.X);
   m_ROIRect.Y = min(e->Y, m_ROIPoint.Y);
   m_ROIRect.Width = max(e->X, m_ROIPoint.X) - m_ROIRect.X + 1;
   m_ROIRect.Height = max(e->Y, m_ROIPoint.Y) - m_ROIRect.Y + 1;
   m_Pressed = false;

   Graphics ^bmpGraphics = Graphics::FromImage(clone);

   bmpGraphics->DrawRectangle(Pens::Blue, m_ROIRect);
   delete bmpGraphics;

   Graphics ^g = this->CreateGraphics();

   g->DrawImage(clone, Point(0,0));
   delete clone;
   delete g;

如果來源影像和繪圖範圍不一致,那框出來的區域就需要線性調整。


pin_ptr<type>
[註] 會需要 pin,是因為要傳入指標的位址, 而 cli::interior_ptr 是 managed code,非原生,像「cvSetCaptureProperty(m_Capture, CV_CAP_PROP_FRAME_WIDTH, m_Width);」的第一個參數就不用 pin。
  • 另外有一種方式是 Interop(沒用過);如果沒有 source code,可以使用 DllImport。
Bitmap 相關

想使用 1-byte 表示顏色,需要修改調色盤,但這不是真正的灰階(76KB),因為這種做法檔案大小僅(44KB)好像只透過 256 種色彩來表示,另外存檔的檔頭,開頭也不是「BM」


就是要把調色盤的「rgb」各分量設定成一樣,以下是 C# 程式碼:(附帶一提,unsafe 的出現,意味可以讓 C# 使用指標「*」)

static unsafe Bitmap ConvertToMonochrome(Bitmap bmColor)
{
     int cx = bmColor.Width;
     int cy = bmColor.Height;

     // Create a new 8-bit indexed bitmap of the same size 
    Bitmap bmMono = new Bitmap(cx, cy,    PixelFormat.Format8bppIndexed); 
    bmMono.SetResolution(bmColor.HorizontalResolution,  bmColor.VerticalResolution); 
     // Set the palette for gray shades 
     ColorPalette pal = bmMono.Palette; 
     for (int i = 0; i < pal.Entries.Length; i++) 
         pal.Entries[i] = Color.FromArgb(i, i, i); 
     bmMono.Palette = pal; 
     // Because SetPixel won't work with index bitm aps, we need 
     // direct access to the bits
     BitmapData bmd = bmMono.LockBits(new Rectangle(Point.Empty, bmMono.Size), 
         ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);


     // Scan through the pixels, converting color to B&W
     Byte *pPixel = (Byte *) bmd.Scan0;

     for (int y = 0; y < cy; y++)
    {
         for (int x = 0; x < cx; x++)
        {
            Color clr = bmColor.GetPixel(x, y);
            Byte byPixel = (byte) ((30 * clr.R + 59 * clr.G + 11 * clr.B) / 100); 
            pPixel[x] = byPixel; } pPixel += bmd.Stride;
         } 
         bmMono.UnlockBits(bmd);
         return bmMono;
    }

OpenCV 一個 channel、位元深度 8,似乎也是索引色 (由 photoshop 顯示)


Contruct Bitmap via row data
// 需要這樣建構應該和取得的影像正反有關
Bitmap^ GetBitmapObj(void* scan0, int width, int height)
{
int stride = width*3;
Byte *ptr = (Byte*)scan0;
ptr += (height-1)*stride;

Bitmap ^capturedImage = gcnew Bitmap(width, height, -stride,
System::Drawing::Imaging::PixelFormat::Format24bppRgb, (IntPtr)ptr);

return capturedImage;
}

<<注意事項>>

  1. 建構出來的 Bitmap,LockBit 後的 BitmapData->Stride 會是負的。如果用 Graphics::FromImage 把這個 Bitmap 畫到另一個 Bitmap2,則 Bitmap2 的 Stride 就會是正的。而且存取他無法用以下方式:

    // Remember UnlockBits BitmapData ^ProcessImageData =
    ProcessImage->LockBits(
    System::Drawing::Rectangle(0, 0, width, height),
    ImageLockMode::ReadWrite, PixelFormat::Format24bppRgb);
    Byte *ProcessImageDataPtr = (Byte*)ProcessImageData ->Scan0.ToPointer();

    for(i = 0; i < height; ++i)
    {
    for(j = 0; j < width; ++j, ProcessImageDataPtr +=3)
    {
    ....
    }
    }

    會造成非法記憶體存取,所以還是得先畫到另一張 bitmap 上面

    System::Drawing::Rectangle region = System::Drawing::Rectangle(0, 0, width, height);
    Bitmap ^regionImage = (gcnew System::Drawing::Bitmap(width, height));
    Graphics ^regionImageGraphics = Graphics::FromImage(regionImage);
    GraphicsUnit units = GraphicsUnit::Pixel;
    regionImageGraphics->DrawImage(m_ProcessImage, region, *rect, units);

  2. Bitmap 的建構,實際存放像素的記憶體就是第一個參數 void* scan0,也就是直接參考這一塊,如果要釋放 Bitmap,記得先把 scan0 釋放掉。

LockBits && UnlockBits 解釋 (for Bitmap)

LockBits and UnlockBits must be used as a pair. A call to the LockBits method of a Bitmap object establishes a temporary buffer that you can use to read or write pixel data in a specified format. After you write to the temporary buffer, a call to UnlockBits copies the pixel data in the buffer to the Bitmap object. If the pixel format of the temporary buffer is different from the pixel format of the Bitmap object, the pixel data is converted appropriately.


(UnlockBits 後,資料才會寫回去 Bitmap;如果沒做這個動作,還去對 Bitmap 存取,會發生 exception)
 

BitmapData ^imageData = image->LockBits( Rectangle(0, 0, width, height),
ImageLockMode::ReadOnly, PixelFormat::Format24bppRgb);
image->UnlockBits(imageData);


.NET 注意事項

  1. 「ref class」裡頭不能有非「ref class」或者「unmanage type」的成員變數。(基本型別不在此限)
    指標變數代替實體變數的宣告(此時CLR會把它解讀成 cli::interior_ptr),但使用這個還是有限制,就是需要傳指標的位址給函式,就會有錯誤訊息,所以這時必須要使用 pin 的機制。
  2. 「ref class」的成員函式,使用區域變數時,允許使用非「ref class」或者「unmanage type」的成員變數,就連指標也不用 pin。
    無。
  3. 「ref class」的成員函式不能用 const type 做為回傳值。
    ^ 回傳 manage object handle
  4. 利用「Marshal」配置「unmanaged memory」的語法。

    IntPtr m_Scan0 = Marshal::AllocHGlobal(bufSize);

    Marshal::FreeHGlobal(m_Scan0);

  5. Managed class or struct 不能給建構函式的參數預設值。
    無。

  6. 使用「native static library」,「Common Language Runtime support」若維持預設的「/clr:pure」,會造成「link error」。
    參數改成「/clr」即可。

  7. 在「.NET」的環境下,要讓managed & unmanaged code存在同一個專案,可以擺放在不同檔案中:
    a.cpp是一個純c++的檔案;b.cpp「ref class」的檔案
    當「b.cpp
    需要靠a.cpp做某些事情,可以在a.cpp開一個介面給b.cpp」去呼叫,但我猜測不能在介面參數中含有unmanaged type」,不然像 1. 的情況,我想還是會發生
  8. 「ref class」的成員變數,如果當成參數丟給「unmanaged function」,會有錯誤訊息發生,例如:
    MyFun(int &count);

    ref class MyClass {
         ...
         public:
         int m_Count;
    };

    ...;

    MyFunc(count); // 發生 can not convert int to int&

    先用區域變數丟進去接值,爾後再指派給成員變數。
IDE 設定
Precompilered Header 的設定可以讓編譯過程加快,但有時候它會 cache 到舊的東西,反而造成困擾,所以可以選擇關掉它,可以在
Properties -> Configuration Properties -> C/C++ -> Precompilered Headers -> Create/Use Precompilered Header,改成 No Using Precompilered Headers」。

沒有留言:

 
Blogger Templates