2007年6月26日 星期二

(BCB) 覆寫 WndProc 進行視窗訊息處理

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 進階程式設計手冊

2007年6月25日 星期一

將 .NET 函數匯出成一般 DLL 函數的工具
MSN SpaceGoogle DocGoogle Blog
Chui-Wen Chiu
2007.0626

測試環境
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 管理類別
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


(C#)利用視窗訊息控制 WinAmp
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 ()

2007年6月24日 星期日

利用動態 Script 讓 Local 端的 HTML 跨網域存取 Server 上的資料

MSN SpaceGoogle DocGoogle Blog
Chui-Wen Chiu
2007.06.21

測試環境
1. Windows XP SP2
2. IE 6

概念
HTML 中的 <script> 一般用來引入外部 javascript 檔案,當瀏覽器遇到 <script> 會自動下載 src 指定的檔案並且進行解析,如果 HTML 中動態變更 <script> 的 src 屬性或是動態新增 <script> 時,IE 也會下載並解析檔案內容。另外,<script> 引入的外部檔案並沒有限制一定要在同一個網域(domain),因此,可以透過 <script> 的 src 向遠端伺服器送出 Get 請求,並透過產生新的 Javascript 內容來回傳資料。

實作
Client - test.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> new document </title>
<meta http-equiv="Content-Type" content="text/html; charset=big5"/>
<meta name="author" content="Chui-Wen Chiu">
<script type = 'text/javascript'>
<!--//
/**
* Script Callback 用
*
*/

function script_onload(){
// 將資料寫入 Label
var result = xtt();
if (result != null){
document.getElementById('lblTime').innerText = result.now;
document.getElementById('lblGet').innerText = result.get;
}
}

function body_onload(){
setInterval(GetNowDate, 1000);
}

// 測試用的 Script
var url = 'http://192.168.10.120:2527/xtt.js.php';

/**
* 抓取遠端 Server 資料
*
* @remark 加上時間參數是避免 script 被 Cache
*/

function GetNowDate(){
document.getElementById('dataScript').src = url + '?rnd=' + (new Date().valueOf());
}
//-->
</script>
<!-- 接受遠端資料用 -->
<script id = 'dataScript' type = 'text/javascript' src = '' ></script>

</head>
<body onload = 'body_onload();'>
Time: <label id='lblTime'></label><br/>
Get: <label id='lblGet'></label><br/>
<button onclick = 'GetNowDate()'>Refresh</button>
</body>
</html>
Server - xtt.js.php
<?php
$v = date('h:i:s');
$rnd = $_GET['rnd'];
echo <<< BOF
var xtt = function(){

return {
now: '{$v}',
get: '{$rnd}'
};
};

script_onload();
BOF;
?>

解說
Client 端的關鍵在 dataScript 的 <Script>,動態調整 src 屬性來送出新的 Request,且這個 Request 可加入 Get 參數,當 Server 接收到之後,產生一個 xtt 的 Function 並且在其中包裝回傳資料。在 xtt.js.php 中的最後一行 script_onload() 是用來通知 Client 端 Javascript 已經載入完成。這個作法是因為 IE <script> 的 onload 事件沒有辦法運作的權宜作法。當觸動 script_onload() Callback 之後,Client 就知道資料已經處理完成,可以抓取伺服器回傳的資料。

2007年6月15日 星期五

Gmail 可以播放 MP3


很少有機會在 Gmail 夾 mp3 檔案,剛好看到"Developing Google Docs & Spreadsheets"提到 Gmail's MP3 Player,想說附件夾檔有支援 mp3,於是將 MP3 附件在一封信件檔測試,如下
會多出一個 Play 連結,點選後會出現下面播放介面
由 Google 最近的一連串動作看來,以後只要是常用的檔案格式, Google 應該都會提供對應的 Viewer 或 Player,目前就我所知已經可以線上觀看的有
1. Word 文件(*.doc)
2. Excel 文件(*.xls)
3. PowerPoint(*.ppt)
4. MP3
5. PDF
期望他也能支援 *.eml 格式附件,否則每次都要下載用 Outlook 開也挺不方便的。l

Google 書籤支援匯出功能

Google 書籤支援匯出功能

我常用書籤管理服務 Google bookmark 提供書籤匯出功能,這個 Google 服務更新頻率不高,不過這個新功能還是挺實用的,如果能夠在改善一下新增和管理部份就更好了,這個新增功能目前我只在英文介面上看到,如果你的是中文介面,可以在URL中找出 hl=zh-TW 修改為 hl=en 就會發現書籤會下面的功能選單多出一個如下圖的"Export bookmarks"匯出功能,點選該功能之後,可以下載 bookmarks.html,這個檔案可以匯入目前大多數的瀏覽器中。

2007年6月4日 星期一

VS2005 預設的 C# 專案樣板消失
MSN SpaceGoogle DocGoogle Blog
Chui-Wen Chiu(Arick)
2007.06.05 建立

最近用 VS2005 開發 C# 專案時,居然找不到任何專案。可是其他語言如 C++ 是正常。如下圖:
這樣在開發上挺麻煩的,於是在 Microsoft 論壇張貼,感謝得到 LOLOTA 提供下面的解法[1]:

可以透過下列指令重新設定(開始->VS2005->VS Tools->Visual Studio 2005 Command Prompt):

devenv.exe /InstallVSTemplates


可是我測試後還是沒有出現,於是我查了一下這個指令的用途,如下[2]:
註冊位於 <VisualStudioInstallDir>Common7IDEProjectTemplates 或 <VisualStudioInstallDir>Common7IDEItemTemplates 中的專案或項目範本,如此即可透過 [新增專案] 和 [加入新項目] 對話方塊存取它們。

依據說明上述作法應該可以解決我的問題。於是我檢查過該目錄下的檔案和檔案,和另一台正常的進行比對,完全是相同的。後來我發現這些目錄下有 1033 子目錄(如:C:Program FilesMicrosoft Visual Studio 8Common7IDEProjectTemplatesCSharpWindows1033),我猜測應該是語系目錄,於是我將 VS2005 環境語系修改成 English
重新啟動之後,果然我的直覺是正確的, C# 專案樣板還原了,如下圖:
為什麼我的環境有繁體和English兩種語系,因為一開始我安裝的是 English 版本的 VS2005,後來我又安裝繁體中文的 MSDN,所以,VS2005 就可以選用繁體中文的介面,所以才會發生上面的問題。不過,我想既然是語系問題,那在建立一個 1028 (繁體中文)的目錄應該也可以,於是建立 1028 目錄,並將 1033 中的所有檔案複製一份到 1028,並且執行上述的安裝樣板指令,重新啟動,結果.... 繁體環境還是沒有。

所以,可能不單純只是建立語系目錄,應該還有其他設定要調整,因此我就先用 English 環境,以後找到解法再補上吧~

可是,為什麼 C++ 的專案樣板沒有消失呢?可能他並沒有依循 C# 專案樣板的結構。

參考資料

[1] http://forums.microsoft.com/MSDN-CHT/ShowPost.aspx?PostID=1690731&SiteID=14&mode=1
[2] http://msdn2.microsoft.com/zh-tw/library/ms241279(vs.80).aspx

2007年6月3日 星期日

利用 JAJAH 撥打免費市話和行動電話
MSN SpaceGoogle DocGoogle Blog
Chui-Wen Chiu(Arick)
2007.06.03 建立

今天看到[1]介紹 Jajah 才知道有免費打市話和行動電話的方法,但是這個方法目前我所知是有一些限制[2]
1. 必須透過官方網頁播打對方電話
2. 每週撥打上限不得超過 150 分鐘,每月不得超過 500 分鐘
3. "雙方號碼"都必須在登錄在 JAJAH
4. 如果英文介面對你而言是限制
5. 帳戶必須是Active(兩星期內曾經使用JAJAH)

如果你不介意上述的限制,請跟著我的腳步來測試。首先登入 http://www.jajah.com/members/register.aspx 填妥註冊資訊

因為 JaJah 是扮演電話雙方通話的協調者,所以,這步驟要設定你的電話號碼,可輸入市話或行動電話號碼。
測試中的畫面,此時你的電話應該會響,你可以選擇要不要接,不接亦可。

測試完成如下圖:

恭喜你已經完成一半的步驟,接著重複上述步驟再註冊另一個帳號,並且給予另外一組電話號碼。下面假設你已經申請了兩組帳號,且輸入兩組不同的市話號碼或行動電話號碼。進入 http://www.jajah.com/members/ 進行通話測試。

出現下面畫面後,你的電話應該會響起,請注意你的畫面是否有出現FREE 字樣。如果是,請將你的電話接起來,此時應該會聽到"請稍等,jajah會為您接通"。
接著另一隻電話應該也會響起,如果接通應該會出現如下圖的畫面:
接通後,就和你一般電話使用上相同,唯一要注意的是通話間是否超過免費的額度。通話完畢,可以進入 http://www.jajah.com/members/settings.aspx 查詢通話明細:

最後要確認的是下個月帳單是不是真的不會將這筆通話紀錄進去^_^

我想大家應該和我一樣對於他的運作機制很好奇,下圖摘至[3],我並未專研此領域,故不說明免得誤導大家。

Image:How_it_works.png


常見問題
1. jajah一個帳號可以註冊三個號碼,這三個號碼不可互打,否則免費時數會被取消
2. 在台灣透過Jajah打電話的人不用付國際電話費,但是,在國外用手機接電話的人還是要付國際漫遊的費用。
3. 電話裡聽到Jajah的語音詢問你是否要幫你在五分鐘後自動重撥。這項貼心的自動重播服務也是要付費的。
4. 當你第一次當週使用免費分鐘數超過了120分鐘後,下一週開始,你的免費分鐘數就會變成每週只有30分鐘,每月60分鐘了。但是,在你做過一次儲值後,免費分鐘數就會恢復成每週150分鐘

參考資料
[1] http://blog.yam.com/willyopp/article/10192796
[2] http://www.jajah.com/content/freecalls.aspx
[3] http://en.wikipedia.org/wiki/Jajah#Technology
[4] http://jajah.com.tw/
[5]
http://www.wretch.cc/blog/paroquet&article_id=9997031
[6] http://readforjoy.blogspot.com/2007/05/jajah150.html
[7] http://www.9skype.com/e_1271.html
[8] http://readforjoy.blogspot.com/2007/05/jajah_23.html
[9] Jajah網路電話使用常見問題
[10] Jajah在台灣用中華電信手機打市話與手機的帳單結果(6.2更新)
[11] Jajah能否免費講台灣手機的進一步詳細說明
[12] 意別讓你的Jajah免費分鐘數從每週150分鐘變成30分鐘(5.28補充)

搜尋此網誌