Feeds:
文章
留言

Archive for 六月 26th, 2007

MSN SpaceGoogle DocGoogle Blog
Chui-Wen Chiu
2007.06.26

測試環境
1. Windows XP Pro + SP2
2. Borland C++ Builder 6.0

BCB 的訊息機制
所有 C++ Builder 的類別都具有內建的訊息處理機制,簡單的說,當類別收到訊息時會依據收到的訊息類型再分配給特定的成員函數處理,如果訊息沒有對應的處理函數,則會使用預設的處理函數。下面是 BCB 的訊息分派流程:

Windows 訊息 -> MainWndProc -> WndProc  -> Dispatch -> 訊息處理函數

Windows 訊息含有許多有用的資料紀錄,其中最重要的是用來識別訊息的整數。這些識別碼定義在 messages.hpp。當應用程式建立視窗(CreateWindow)時,會將 Windows Procedure 註冊到 Windows 核心(RegisterClass),Windows Procedure 是視窗訊息處理副程式,傳統作法是用一個很大的 switch 來處理每一個訊息。 BCB 在許多方面簡化了訊息分派方式:
1. 每個元件都繼承完整的訊息分派系統
2. 訊息分派系統都具有預設的處理函數。所以,我們只要定義必要的訊息即可。
3. 可以編修訊息處理函數的一小部份,其餘的交給繼承下來的訊息處理函數

BCB 註冊一個 MainWndProc 函數作為應用程式中各類別的 Window Procedure。MainWndProc 包含一個例外處理區塊,把 Windows 訊息資料結構傳給 WndProc 虛擬方法,並呼叫 TApplication 的 HandleException 方法來處理例外狀況。MainWndProc 不是一個虛擬函數,他對任何訊息沒有特定的處理方法。在 WndProc 中才會加以自訂,因為每個元件都可以覆蓋 WndProc 來達到自訂需求。

WndProc 可以檢查特定訊息並加以處理,使得能夠抓住任何訊息。WndProc 最後會呼叫 TObject 繼承來的非虛擬函數 Dispatch 將訊息非派到綴中處理函數。Dispatch 使用訊息資料結構中的 Msg 成員來決定訊息分派,如果元件定義這個訊息的處理函數,Dispatch 會將控制權移交給這個函數,否則會透過 DefaultHandler 處理。

攔截特定訊息
BCB 要修改元件處理某一訊息的方式,只要覆蓋該訊息的訊息處理函數即可,但如果元件預設不提供該訊息處理,此時可以宣告一個自訂訊息處理函數來處理。首先在元件中宣
告新的訊息處理函數,如:

void __fastcall OnMyMove(TMessage& Message);

接著利用下面的巨集將上述處理函數與實際的 Windows 訊息進行關聯

BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(視窗訊息, 訊息資料結構, 訊息處理函數名稱)
END_MESSAGE_MAP(TForm)

上述巨集中可以包含多個 MESSAGE_HANDLER。下面以一個簡單的範例驗證上述的說明。

Unit1.h (視窗類別宣告)

//—————————————————————————

#ifndef Unit1H
#define Unit1H
//—————————————————————————
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//—————————————————————————
// 自訂 Windows 訊息
const DWORD MY_MSG = WM_USER + 1;

class TForm1 : public TForm
{
__published:    // IDE-managed Components
    TStaticText *StaticText1;
    TButton *Button1;
    void __fastcall Button1Click(TObject *Sender);
private:    // User declarations
// 宣告訊息的處理函數
    void __fastcall OnMyMove(TMessage& Message);
    void __fastcall OnMyMsg(TMessage& Message);

// Windows 訊息攔截定義
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_MOVE, TMessage, OnMyMove)
MESSAGE_HANDLER(MY_MSG, TMessage, OnMyMsg)
END_MESSAGE_MAP(TForm)

public:        // User declarations
    __fastcall TForm1(TComponent* Owner);
};
//—————————————————————————
extern PACKAGE TForm1 *Form1;
//—————————————————————————
#endif

Unit1.cpp (視窗類別實作)

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//—————————————————————————
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//—————————————————————————
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//—————————————————————————
/**
 * WM_MOVE 訊息處理
 *
 * @see TMessage
 * @see WM_MOVE
 * @param   Message     訊息資訊
 */
void __fastcall TForm1::OnMyMove(TMessage& Message){
    AnsiString s;
    s += (int)(short) LOWORD(Message.LParam); // X 座標
    s += ", ";
    s += (int)(short) HIWORD(Message.LParam); // Y 座標
    StaticText1->Caption = s;

    TForm::Dispatch(&Message);
}

/**
 * 自訂訊息處理
 *
 * @see TMessage
 * @see WM_MOVE
 * @param   Message     訊息資訊
 */
void __fastcall TForm1::OnMyMsg(TMessage& Message){
    StaticText1->Caption = "WM_MYMSG";

    TForm::Dispatch(&Message);
}

/**
 * 送出自訂訊息
 *
 */
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    SendMessage(this->Handle, MY_MSG, 0, 0);
}

執行結果
1. WM_MOVE

2. WM_MYMSG

阻斷特定訊息
在某些情況可能想要讓元件忽略特定訊息,也就是讓訊息不分派到訊息處理函數。可以透過改寫(Override) 虛擬函數 WndProc 來達成。下面是阻斷滑鼠的 WM_LBUTTONUP 和 WM_LBUTTONDOWN 訊息範例,當過濾進行中,FormClick 訊息處理函數就不會進行。

Unit1.h (視窗類別宣告)

//—————————————————————————
#ifndef Unit1H
#define Unit1H
//—————————————————————————
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//—————————————————————————
class TForm1 : public TForm
{
__published:    // IDE-managed Components
    void __fastcall FormClick(TObject *Sender);
private:    // User declarations
protected:
     // Override WndProc
     virtual void __fastcall WndProc(Messages::TMessage &Message);
public:        // User declarations
    __fastcall TForm1(TComponent* Owner);
};
//—————————————————————————
extern PACKAGE TForm1 *Form1;
//—————————————————————————
#endif

Unit1.cpp (視窗類別實作)

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//—————————————————————————
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//—————————————————————————
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//—————————————————————————
void __fastcall TForm1::WndProc(Messages::TMessage &Message){
   // 阻斷訊息
    if(Message.Msg == WM_LBUTTONUP || Message.Msg == WM_LBUTTONDOWN){
        return;
    }

    // 繼續原始訊息的分派
    TForm::WndProc(Message);
}

void __fastcall TForm1::FormClick(TObject *Sender)
{
    Caption = "Hello";
}

參考資料
[1] C++ Builder 研究, 攔截Windows消息
[2] C++ Builder 研究, 在CB中響應消息及自定義消息
[3] C++ Builder 研究,如何捕獲VCL沒有處理的Windows消息
[4] C++ Builder 研究, CB中消息處理過程及應用
[5] Borland C++ Builder 5 進階程式設計手冊

Read Full Post »

MSN SpaceGoogle DocGoogle Blog
Chui-Wen Chiu(Arick)
2007.05.29 建立


測試環境
1. Windows XP Pro
2. Visual Studio 2005
3. Winamp 5.3.5

簡介
本文主要利用 Win32API 的 FindWindow 和 SendMessage 達成控制 Winamp。

內文
[1] 給出幾個 Winamp 介面上常用的命令控制如下:
private const Int32 WINAMP_START            = 40045;
private const Int32 WINAMP_PLAY_OR_PAUSE    = 40046;
private const Int32 WINAMP_NEXT_TRACK       = 40048;
private const Int32 WINAMP_PREVIOUS_TRACK   = 40044;
private const Int32 WINAMP_CLOSE            = 40001;
private const Int32 WINAMP_STOP             = 40047;
private const Int32 WINAMP_RAISE_VOLUME     = 40058;
private const Int32 WINAMP_LOWER_VOLUME     = 40059;
private const Int32 WINAMP_TOGGLE_REPEAT    = 40022;
private const Int32 WINAMP_TOGGLE_SHUFFLE   = 40023;       
private const Int32 WINAMP_FAST_FORWARD     = 40148;
private const Int32 WINAMP_FAST_REWIND      = 40144;

從名稱上可以很清楚知道他的用途就不贅述。再運用上述的命令之前,必須先取得 Winamp 的 Window Handle,這就是使用 FindWindow 目的,透過 SPY++ 可以取得下圖得知 Winamp 的 Class Name 為 "Winamp v1.x"。

將 Class Name 給 FindWindow 換得 Winamp 的 Window Handle,有了 Window Handle 就可以透過 SendMessage 傳送 Window Message 給 Winamp,進而控制他。[1] 所列舉的控制訊息除了 WINAMP_CUSTOM_VOLUME 之外,都是透過 WM_COMMAND 來傳送,不明的控制都放在 wParam 參數中,所以,可以依據這個規則寫出下面的控制命令

private void WCommand(int command) {
    SendMessage(m_handle, WM_COMMAND, command, 0);
}

因此,要播放音樂只需要 WCommand(WINAMP_START),關閉程式只需要 WCommand(WINAMP_CLOSE)。有了上述禀概念之後,我們就可以將程式進行封裝如下:

using System;
using System.Runtime.InteropServices;

namespace Console1 {
   
    public class Winamp {
        public class NotFoundWinamp: Exception {}

        #region native code
        // WM_COMMAND – Commands to send to Winamp Client.
        private const Int32 WINAMP_START            = 40045;
        private const Int32 WINAMP_PLAY_OR_PAUSE    = 40046;
        private const Int32 WINAMP_NEXT_TRACK       = 40048;
        private const Int32 WINAMP_PREVIOUS_TRACK   = 40044;
        private const Int32 WINAMP_CLOSE            = 40001;
        private const Int32 WINAMP_STOP             = 40047;
        private const Int32 WINAMP_RAISE_VOLUME     = 40058;
        private const Int32 WINAMP_LOWER_VOLUME     = 40059;
        private const Int32 WINAMP_TOGGLE_REPEAT    = 40022;
        private const Int32 WINAMP_TOGGLE_SHUFFLE   = 40023;       
        private const Int32 WINAMP_FAST_FORWARD     = 40148;
        private const Int32 WINAMP_FAST_REWIND      = 40144;

        public const string WINAMP_WINDOW_HANDLE = "Winamp v1.x";

        private const Int32 WM_COMMAND              = 0x0111;
        private const Int32 WM_USER                 = 0x0400;

        // WM_USER
        public const int WINAMP_CUSTOM_VOLUME = 122;

        [DllImport("user32.dll")]
        private static extern int FindWindow(
            string lpClassName, // class name
            string lpWindowName // window name
        );

        [DllImport("user32.dll")]
        private static extern int SendMessage(
            int hWnd, // handle to destination window
            uint Msg, // message
            int wParam, // first message parameter
            int lParam // second message parameter
        );
        #endregion

        public Winamp() {
            m_handle = FindWindow(WINAMP_WINDOW_HANDLE, null);

            if (m_handle == 0) {
                throw new NotFoundWinamp();
            }
        }

        #region public method
        /// <summary>
        /// 暫停或繼續播放
        /// </summary>
        public void PlayOrPause() { WCommand(WINAMP_PLAY_OR_PAUSE); }

        /// <summary>
        /// 下一首
        /// </summary>
        public void Next() { WCommand(WINAMP_NEXT_TRACK); }

        /// <summary>
        /// 前一首
        /// </summary>
        public void Previous() { WCommand(WINAMP_PREVIOUS_TRACK); }

        /// <summary>
        /// 關閉 Winamp
        /// </summary>
        public void Close() { WCommand(WINAMP_CLOSE); }

        /// <summary>
        /// 停止播放
        /// </summary>
        public void Stop() { WCommand(WINAMP_STOP); }

        /// <summary>
        /// 開始播放
        /// </summary>
        public void Start() { WCommand(WINAMP_START); }

        /// <summary>
        /// 啟動重複播放
        /// </summary>
        public void ToggleRepeat() { WCommand(WINAMP_TOGGLE_REPEAT); }

        /// <summary>
        /// 啟動隨機播放
        /// </summary>
        public void ToggleShuffle() { WCommand(WINAMP_TOGGLE_SHUFFLE); }

        /// <summary>
        /// 增加音量
        /// </summary>
        public void RaiseVolume() { WCommand(WINAMP_RAISE_VOLUME); }

        /// <summary>
        /// 減少音量
        /// </summary>
        public void LowerVolume() { WCommand(WINAMP_LOWER_VOLUME); }
       
        /// <summary>
        /// 自訂音量
        /// </summary>
        /// <param name="Volume"></param>
        public void CustomVolume(int Volume) { UCommand(Volume, WINAMP_CUSTOM_VOLUME); }

        /// <summary>
        /// 快速向前轉
        /// </summary>
        public void FastForward() { WCommand(WINAMP_FAST_FORWARD); }

        /// <summary>
        /// 快速向後轉
        /// </summary>
        public void FastRewind() { WCommand(WINAMP_FAST_REWIND); }

        #endregion

        #region private mehtod
        private void WCommand(int command) {
            SendMessage(m_handle, WM_COMMAND, command, 0);
        }

        private void UCommand(int input, int command) {
            SendMessage(m_handle, WM_USER, input, command);
        }
        #endregion

        #region private data
        private int m_handle;
        #endregion
    }

    class Program {
        static void Main(string[] args) {
            try {
                Winamp wp = new Winamp();
                wp.Start();
                wp.PlayOrPause();
                wp.Close();
            } catch (Winamp.NotFoundWinamp) {
                Console.WriteLine("Winamp Not Found"); 
            }
        }
    }
}

結語
由本文應該可以瞭解到如何透過 Windows Message 控制 Winamp,如果要擴充控制,也可以自行透過 SPY++ 攔截 Winamp 的 Windows Message。有了本文的介紹,你也可以將這個概念應用在其他程式中,如: 記事本、小算盤…等。

參考資料
[1] Winamp Control Utility via hotkeys
[2] FindWindow Function ()
[3] SendMessage Function ()

Read Full Post »


測試環境
1. Windows XP Pro SP2
2. Visual Studio 2005
3. .NET Framework 2.0

下載
1. ExportDll.exe
2. ExportDllAttribute.dll

[1] 利用 Attribute 標示會出的函數資訊,接著透過 ildasm.exe 將 DLL  反編譯成 IL 碼,然後修改標示為匯出函數的 IL 碼後再重新以 ilasm.exe 編譯成 DLL。下面簡單的示範這個工具的用法,首先建立一個 C# Class Library Project,在 Project 中加入 ExportDllAttribute.dll 參考,並撰寫如下程式產生 ClassLibrary1.dll

using System;
using ExportDllAttribute;

namespace ClassLibrary1 {
    public class Class1 {
        [ExportDllAttribute.ExportDll("ExportNameOfFunction",  System.Runtime.InteropServices.CallingConvention.Cdecl)]
        public static void SomeFunctionName() {
            System.Console.WriteLine("I really do nothing");
        }
    }
}

執行下面命令匯出函數(DLL 需加入路徑)
ExportDll.exe ./ClassLibrary1.dll /Debug

使用 depends.exe 檢測如下:

撰寫 C++ 程式測試,測試程式使用[2] 的 LibraryMrg 類別

#include "LibraryMgr.h"
#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    LibraryMgr_t lm( _T("ClassLibrary1.dll") );
    typedef void (*Test_t)(void);
    Test_t TestFn;
    TestFn = (Test_t) lm.GetProcAddress( "ExportNameOfFunction" );
    if(TestFn){
        TestFn();
    }

    return 0;
}

執行結果

結語
以往要將 .NET 程式給 Unmanaged 程式使用,可能是選擇封裝成 COM 或者透過 C++/CLI 封裝成 DLL,可是透過這個工具,可以將 C# 程式很輕易的公開給 C++ 程式使用,省去層層的封裝,不過這個工具是否有什麼限制或是錯誤還需要一點時間觀察。

補充
1. ExportDll.exe 有使用 ildasm.exe 和 ilasm.exe,這兩個工具的路徑定義在 app.config 中,如果你的這兩個工具不是安裝在預設的

C:Program FilesMicrosoft Visual Studio 8SDKv2.0Bin 和 C:WINDOWSMicrosoft.NETFrameworkv2.0.50727 需要自行修改 app.config。

2. 這個工具目前似乎不能用於 VB.NET 所產生的 DLL

3. 如果要將這個工具與 VS2005 整合,打開 Project Properties |Build Events 的 Post-build event command line 加入下面的命令(請依你的實際路徑修改)
"$(SolutionDir)ExportDllbinDebugExportDll.exe" "$(TargetPath)" /$(ConfigurationName)

當編譯完成後自動執行 ExportDll.exe。

4. 可透過 rundll32.exe 執行 ExportDll.exe 產生的 DLL,如下:
rundll32.exe ClassLibrary1.dll,ExportNameOfFunction

ps. 對於 Console 輸出的資料不會顯示出來,可用 MessageBox 取代 Console 輸出

參考資料
[1] How to automate exporting .NET function to unmanaged
[2] DLL 管理類別

Read Full Post »

(C++) DLL 管理類別

MSN SpaceGoogle DocGoogle Blog
Chui-Wen Chiu
2007.06.23

將動態載入 DLL 並取得函數指標的動作封裝成專屬類別,透過這個類別可以省去許多瑣碎步驟又可避免忘記釋放 DLL 的問題。

原始碼

LibraryMgr.h

#include <windows.h>
#include <string>

#ifndef LIBRARYMGR_H_INCLUDED
#define LIBRARYMGR_H_INCLUDED

class LibraryMgr_t
{
public:
    LibraryMgr_t(void);
    LibraryMgr_t(const TCHAR* ptcFileName);

    ~LibraryMgr_t(void);

    bool LoadLibrary( const TCHAR* ptcFileName );
    bool FreeLibrary();

    FARPROC GetProcAddress( const TCHAR* ptcProcName );

    HMODULE GetModuleHandle();

    bool GetModuleFileName(std::basic_string<TCHAR>& sName);

    bool FreeLibraryAndExitThread( DWORD dwExitCode );

    void SetAutoFree(bool bFree) { m_bAutoFree = bFree; }

private:
    HMODULE m_hModule;
    bool m_bAutoFree;
    std::basic_string<TCHAR> m_sFileNAme;

};

#endif //LIBRARYMGR_H_INCLUDED

LibraryMgr.cpp

#include "LibraryMgr.h"

LibraryMgr_t::LibraryMgr_t(void)
: m_hModule(0), m_bAutoFree(true)
{
}

/**
 * 建構子
 *
  * @param ptcFileName DLL 名稱
 */

LibraryMgr_t::LibraryMgr_t(const TCHAR* ptcFileName)
: m_hModule(0), m_bAutoFree(true)
{
    LoadLibrary( ptcFileName );
}

/**
 * 解構子
 *
  * @param ptcFileName DLL 名稱
 */

LibraryMgr_t::~LibraryMgr_t(void)
{
    if ( m_hModule && m_bAutoFree )
    {
        FreeLibrary();
    }
}

/**
 * 載入 DLL
 *
 * @param ptcFileName DLL 名稱
 * @return true 成功, false = 失敗
 */

bool LibraryMgr_t::LoadLibrary(
                             const TCHAR* ptcFileName
                             )
{
    if (! ptcFileName)
        return false;

    if ( m_hModule )
    {
        FreeLibrary();
    }

    m_sFileNAme.assign( ptcFileName );
    m_hModule = ::LoadLibrary( m_sFileNAme.c_str() );

    return m_hModule != NULL;
}

/**
 * 釋放 DLL
 *
 * @return true 成功, false = 失敗
 */

bool LibraryMgr_t::FreeLibrary()
{
    if( ! m_hModule )
        return false;

    ::FreeLibrary( m_hModule );
    m_hModule = NULL;
    m_sFileNAme.clear();

    return true;
}

/**
 * 取得指定函數名稱的函數指標
 *
 * @param ptcProcName 函數名稱
 * @return 成功回傳函數指標,失敗回傳 NULL
 */

FARPROC LibraryMgr_t::GetProcAddress(
        const TCHAR* ptcProcName
        )
{
    if( ! m_hModule )
        return NULL;

    return ::GetProcAddress( m_hModule, ptcProcName );
}

/**
 * 取得模組識別碼
 *
 * @return 成功回傳模組識別碼,否則回傳 NULL
 */

HMODULE LibraryMgr_t::GetModuleHandle()
{
    if( ! m_hModule )
        return NULL;

    return ::GetModuleHandle( m_sFileNAme.c_str() );
}

/**
 * 取得模組名稱
 *
 * @param sName 回傳模組名稱
 * @return true 成功, false = 失敗
 */

bool LibraryMgr_t::GetModuleFileName(std::basic_string<TCHAR>& sName)
{
    if( ! m_hModule )
        return false;

    sName.assign( m_sFileNAme );
    return true;
}

/**
 * 釋放 DLL 並離開執行緒
 *
 * @param dwExitCode 結束代碼
 * @return true 成功, false = 失敗
 */

bool LibraryMgr_t::FreeLibraryAndExitThread( DWORD dwExitCode )
{
    if( ! m_hModule )
        return false;

    ::FreeLibraryAndExitThread( m_hModule, dwExitCode );

    m_hModule = 0;

    return true;
}

範例

// 引入 LibraryMrg 類別標頭檔
#include "LibraryMgr.h"


int _tmain(int argc, _TCHAR* argv[]){
// 載入指定的 DLL
LibraryMgr_t lm( _T("C:\MyProg\MyLib.dll") );

typedef int (*Test_t)(void);
Test_t TestFn;
// 取得 DLL 中的函數指標
TestFn = (Test_t) lm.GetProcAddress( _T("Test"
) );

if(TestFn){
// 執行 DLL 中的函數
TestFn();
}

return 0;
}


參考資料

[1] Yet another DLL manager class

Read Full Post »

美研究新再生能源技術 可從廢棄熱能產生電能
一種將"熱能 -> 聲音 -> 電能"的轉換技術,透過聲學耦合電能轉換器,運用"熱聲原動力"技術,是由背對背的熱交換器組成,其中介材料會形成共振聲學頻率(resonant acoustic frequency)。當熱能進入時會形成共振聲,然後耦合到壓電式換能器,將聲音轉換成電。

 

Read Full Post »