2009年4月21日 星期二

VC2008 Express 編譯DLL

建立DLL專案:
檔案->新增->專案

選擇Win32,Win32專案

按"下一步"

選擇DLL,然後按完成

修改testdll.cpp,以及加入testdll.h在標頭檔分類裡,而其內容,如之前的文章
PS: testdll.h的加入,可以按右鍵在標頭檔,然後選擇加入->新增項目,選"標頭檔(.h)",名稱輸入testdll.h
可以在方案總管,看到VC幫我們加進來的檔案

修改屬性,在testdll的c++ project上按右鍵(就是標頭檔的上一個階層,或方案的下一個階層),選擇屬性
組態屬性->C/C++->前置處理器,在前置處理器定義裡加進DLL_EXPORT(用分號相隔),確定

選擇建置->建置方案,就可以建置DLL了,可以在方案下的Debug目錄看到testdll.dll和testdll.lib,以及在testdll的目錄裡看到testdll.h,這三個檔案是我們需要的


建置win32 主控台應用程式:檔案->新增->專案

選擇Win32,Win32主控台應用專案

按"完成"

可以在方案總管,看到vc幫我們加進來的檔案
修改testmain.cpp,如之前的文章 ,關於_tmain(...),覺得很詭異的話,之前有些文章有介紹


有三個檔案要加進來,首先
testdll.lib和testdll.h放到tsetmain目錄裡,(testdll.h可以加入標頭檔,方便修改,不過沒差)
lib的加入,進入屬性頁面,組態屬性->連結器->輸入,其他相依性,輸入testdll.lib,確定

選擇建置->建置方案,就可以建置了,不過執行會找不到dll...
將testdll.dll放到執行檔產生的目錄(例如Debug\),就ok了

問題:如果硬碟是FAT32的格式,會有一些問題,有其他高手有紀錄下來,及解決方式
這裡做個備忘
Step1 於系統功能表中點擊「專案」→「屬性」(或按Alt + F7)
Step2 展開「組態屬性」
Step3 展開「連結器」
Step4 點擊「偵錯」
Step5 把右方視窗裡的「產生對應檔」改為「是(/MAP)」
Step6 點擊「資訊清單工具」
Step7 把右方視窗裡的「使用 FAT32 解決辦法」改為「是」

後記:Visual Studio 2008 Express(包括了VC),她有分方案和專案,一個方案可以包括一個以上的專案,這些專案可以用VC或VB來寫,或是一個DLL的專案,上面的例子,是兩個方案(各擁有一個專案),透過手動的方式來組合,如果一個方案包括兩個專案,一個是testdll,一個是testmain,則這個IDE會幫你處理一些事,但使用者還是要有些設定...
Step1  在方案的階層,將這兩個專案建立或加入
Step2  testdll.h需要放在各自source(.cpp .h放的地方)的目錄,然後testdll專案需要定義DLL_EXPORT
Step3  testmain專案"設定為啟始專案","專案相依性"設定為testmain相依testdll
PS : 上面提到lib的加入(進入屬性頁面,組態屬性->連結器->輸入,其他相依性,輸入testdll.lib,確定),不需要...

寫這篇真的是服務的心態(對於初學者),也許也希望對於DLL的編譯有個完整性的介紹,不過抓這麼多圖,是有點無聊,佔頁面,不過沒辦法,不然就乾脆不寫了

最近"Google 文件"對於Blogger的輸出,一些bug,有改善了(像是之前圖檔會亂跑,增加許多空白行...),所以這篇文章也算是對"Google 文件"的測試,看來應該是可以重回"Google 文件"的懷抱了

相關連結:
http://genewince.blogspot.com/2008/03/visual-c-dll-gene.html
http://www.wretch.cc/blog/MtvBoy/21514680
http://logix4u.net/Programming/vc++/A_Tutorial_on_creating_DLLs_with_VC++.html

2009年4月19日 星期日

用MinGW編譯DLL

testdll.h:
#ifndef TESTDLL_H
#define TESTDLL_H

#ifdef DLL_EXPORT
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C" {
#endif

DLLAPI float funadd(float a, float b);

#ifdef __cplusplus
}
#endif

#endif


testmain.c:
#include <stdio.h>
#include "testdll.h"

int main(void)
{
float a,b,c;

    a=1.0;
    b=2.0;
    c=funadd(a,b);
    
    printf("%f \n",c);
    getchar();    
}


testdll.c:
#include "testdll.h"

float funadd(float a, float b)
{
    return (a+b);
}


gcc -c -DDLL_EXPORT (-fPIC) testdll.c
PS : 會產生testdll.o的檔案...還有x86平台,-fPIC會忽略

gcc -shared -o testdll.dll testdll.o -Wl,--output-def,testdll.def,--out-implib,testdll.lib
PS : 產生三個檔案:
testdll.dll:也就是程式執行時載入的dll檔
testdll.def:若有需要和VC編譯器連結,這個檔會需要
testdll.lib:export library,用來連結主程式,這樣主程式才能知道找哪個dll,那個dll裡有哪些函式跟參數....

gcc -c testmain.c
PS : 用來使用dll的主程式,會產生一個testmain.o的檔案

gcc -static -o test.exe testmain.o -L. -ltestdll
PS : 會讓主程式和export library(testdll.lib)靜態連結起來,產生test.exe的可執行檔


由於dll和主程式都是用MinGW產生的,所以可以簡化上面的步驟,不過上面的解說,可以對執行檔組成了解多一些,以下舉兩個例子

方法一:
gcc -shared -o testdll.dll testdll.c
PS : -shared是linker的參數,這裡直接產生testdll.dll

gcc -c testmain.c

PS : -c:只編譯不連結,產生testmain.o

gcc -o test.exe testmain.o testdll.dll

PS : 我也不知給什麼規則,反正MinGW搞得定這個testdll.dll就對了

方法二:
gcc -shared -o testdll.dll testdll.c
gcc -o test.exe testmain.c -L. -ltestdll

PS : -l:unix環境裡,會找libtestdll.a(-static) 或 libtestdll.so(-shared),在函式庫名稱的命名上,前面一定要有lib

不過MinGW對於這個慣例,放寬許多,來讓熟悉windows的人習慣
for (-static) : 會找libtestdll.a 或 testdll.lib
for (-shared) : 會找libtestdll.so . testdll.dll 或 libtestdll.dll

希望看完之後是感覺"條條道路通羅馬",而不是給它一整個亂...

這裡又牽扯出另一個疑問,那就是MinGW的make,它對於字尾規則和函式庫的處理,是否有加上這些擴充,有空在研究好了

PS:MinGW的官方網站,有提供一個Shell,叫MSYS,之前發現MinGW和MSYS都有自己的make,MinGW的make叫mingw32-make.exe,MSYS的make叫make.exe,其中MSYS的make是unix style,不能分辨'\',組成的路徑字串(windows是用'\',組成路徑字串),也許這兩個make除了這個區別,還包括了字尾規則和函式庫處理的差異...


testdll.h的補充說明:
關於DLL_EXPORT 的定義,應該要在編譯testdll.c時定義,來分別dllimport和dllexport,不過我沒加,也沒錯誤,我不知道MinGW有沒有支援__declspec(dllimport)和__declspec(dllexport) 這些關鍵字...

關於extern "C" {},如果使用gcc,且souce code的附檔名是.c,會有錯誤,不過__cplusplus沒有定義,會避開...
如果附檔名是.cpp,或gcc改g++,則__cplusplus會定義,就會使函數的Name Mangling使用C的形式(簡單的那一種),常看到...

這裡的紀錄,主要都是看了別人的文章,試出來的...我盡量有系統性,不過最好還是參考相關主要說明文件

相關連結:

MinGW官方整理的FAQ
http://www.mingw.org/wiki/FAQ

這裡有幾篇關於如何用MinGW編譯DLL
http://www.adp-gmbh.ch/win/misc/mingw/dll.html
http://www.mingw.org/wiki/MSVC_and_MinGW_DLLs
http://sig9.com/node/35

用cygwin編譯DLL
http://mqjing.blogspot.com/2009/03/c-cygwin-windows-code.html
http://mqjing.blogspot.com/2009/04/c-gcc-library.html 

用gcc處理static and shared library
http://www.adp-gmbh.ch/cpp/gcc/create_lib.html

有關gcc和g++的區別
http://blog.chinaunix.net/u1/52901/showart_480159.html
http://www.yuanma.org/data/2007/0406/article_2498.htm

2009年2月26日 星期四

ARM的例外事件處理和管線的關係

當例外發生時,處理器會負責如下的動作
1. 拷貝CPSR到對應例外處理模式的SPSR
2. 改變CPSR到對應的例外處理模式,以及對應處理模式的暫存器群,
    任何例外處理都會disable IRQ位元,在FIQ和RESET的例外發生時,才會disable FIQ位元
3. 拷貝PC(r15)到對應處理模式的LR(r14)
4. 設定PC(r15)到對應中斷向量的位址



SWI和Undefined instruction的例外事件:
這兩種例外,都是執行指令所引起的,且都是在PC(r15)沒有更新前,去處理例外事件,所以回到原來的執行程序時,需要執行下一個的指令,因為前一個指令,就是呼叫例外處理程式處理
(for undefined instruction:這個例外事件程式可以當作發展自有命令的模擬開發方式)
the return instruction is:
MOVS pc, lr
The handler entry and exit code to stack the return address and pop it on return is:
STMFD sp!,{reglist,lr}
;...
LDMFD sp!,{reglist,pc}^


prefetch abort的例外事件:
若fetch的指令為無效的位址,在fetch的階段就會標記這個指令,不過要等到Execute階段才會去處理例外事件,這時這個例外事件可能透過MMU對應vitrual memory locations到正確的physical memory,也許還有physical memory和儲存設備之間的swap動作...
PS:在Execute階段處理例外事件時,pc(r15)還未更新
然後回到原來程序時,應該跳回到原來的指令,這時就可以正確fetch這個位址的指令,所以
the return instruction is:
SUBS pc,lr, #4
The handler entry and exit code to stack the return address and pop it on return is:
SUB lr,lr,#4
STMFD sp!,{reglist,lr}
;...
LDMFD sp!,{reglist,pc}^



FIQ和IRQ的例外事件:
在執行完指令完且更新PC(r15)後,會檢查IRQ及FIQ的腳位是否為Low,若成立則處理例外事件,所以回到原來的執行程序時,需要執行下一個的指令,因為前一個指令,已經執行過了
the return instruction is:
SUBS  pc, lr, #4
The handler entry and exit code to stack the return address and pop it on return is:
SUB lr,lr,#4
STMFD sp!,{reglist,lr}
;...
LDMFD sp!,{reglist,pc}^

data abort的例外事件:
在執行load或store的相關指令時,執行完且更新PC(r15)後,才會發生data abort的例外事件,這時這個例外事件可能透過MMU對應vitrual memory locations到正確的physical memory,也許還有physical memory和儲存設備之間的swap動作...
所以回到原來程序時,應該跳回到原來的指令,然後就可以正確load或stroe這個位址的資料,所以
the return instruction is:
SUBS pc, lr, #8
The handler entry and exit code to stack the return address and pop it on return is:
SUB lr,lr,#8
STMFD sp!,{reglist,lr}
;...
LDMFD sp!,{reglist,pc}^

    似乎這些例外處理的返回位址計算,應該是CPU的責任,不過當年ARM CPU的其中一個主要訴求,就是很小的晶片面積,所以就不浪費邏輯閘,讓軟體來處理這些動作,或者說讓軟體開發者有更大的彈性...
    目前有接觸過ARM 7,擁有三階的管線(Fetch. Decode. Execute),之後更強大的ARM系列的CPU,有提供5階或更多的管線,不知道例外處理返回位址的計算,是不是不同???(有機會碰到在看看)
    雖然這兩張圖用來代表管線處理指令流的示意,也許有不恰當的地方,不過我喜歡這樣的描述方式來記憶返回位址的計算,重點是例外發生時,PC(r15)是否有更新,以及各種例外對於返回後,處理的需求,就可以推論返回位址的計算

相關連結:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0040d/index.html 第九章相關內容
http://infocenter.arm.com/help/topic/com.arm.doc.dui0040d/DUI0040D.pdf ,pdf文件下載

2008年12月26日 星期五

Qt - QTextEdit自動捲到底

目標:封裝QTextEdit,使其可以在加入文字時,讓垂直捲軸能夠捲到底,方便當作log的需求

DebugLog::DebugLog(QWidget *parent)
    : QTextEdit(parent)
{
    setReadOnly(true); //由於是做log用,所以不需要寫的存取
    connect( this, SIGNAL(textChanged()), this, SLOT(autoScrollDown()));
}

void DebugLog::autoScrollDown()
{
    //方法1
    QTextCursor c = textCursor();
    c.movePosition(QTextCursor::End);
    setTextCursor(c);
   
    /*//方法二
    QScrollBar *sb = verticalScrollBar();
    sb->setValue(sb->maximum());
    */
}

主要是連接textChanged() 和 autoScrollDown(),當QTextEdit內容改變時, 就使用上面兩個方法來捲到底,兩種都已驗證

Qt版本: 4.4.3

參考資料
http://www.qtcentre.org/forum/archive/index.php/t-5983.html 

2008年12月24日 星期三

TRichEdit自動捲到底

C++ Builder 中的 TRichEdit 元件,可以提供比TMemo元件較多的視覺效果
 ,但有一個小缺點,當使用 RichEdit1->Lines->Add("") 或者 RichEdit2->Lines->Append(""),加入文字時,不會自動捲到最底下(TMemo 可以),對於只是當作log的顯示需求,很不方便,以下介紹克服的方法

在OnChange event裡,增加一個handler,加入程式碼如下

void __fastcall TForm1::RichEdit1Change(TObject *Sender)
{
 SendMessage(RichEdit1->Handle,WM_VSCROLL,MAKELONG(SB_BOTTOM,0),0);  //自動換行作業
}

2008年12月19日 星期五

內存屏障(memory barrier)

memory barrier 長這樣 __asm__ __volatile__("": : :"memory")

首先gcc對於匯編語言(組合語言),是使用AT&T的語法,不同於INTEL的組合語言語法

格式 : __asm__(組合語言:輸出:輸入:修飾詞")
__volatile__     代表這行指令(這些組合語言),不和前面的指令一起最佳化
"memory"        告訴GCC這些組合語言會改變所有的RAM的資料
因為沒組合語言,又告訴gcc所有RAM的內容都改變,
所以這個memory barrier的效用,
會讓這行之前被gcc所cache到暫存器的資料通通寫回RAM裡面
也告訴gcc會讓之後讀取RAM的資料,必須再從RAM裡讀取出來,

參考資料:

http://blog.chinaunix.net/u1/55599/showart_1099203.html
http://hi.baidu.com/hilyjiang/blog/item/2254e62afd6db42cd42af180.html
http://lyowu.yourblog.org/logs/135030.html
http://blog.openrays.org/blog.php?do=showone&tid=329  
http://fanqiang.chinaunix.net/a4/b8/20010704/130401784_b.html

Qt - QLineEdit and focusInEvent

需求:想要客制化一個QLineEdit,希望在點選這個widget時,其text裡的內容全部反白(selectAll 的動作),

第一個想到clicked()這個signal,不過沒有...


然後試試 focusInEvent 的 virtual function,大概如下:

void HexEdit::focusInEvent(QFocusEvent * event)
{
    QLineEdit::focusInEvent(event);
    selectAll();    
}

結果還是不行,因為雖然有反白全部的內容,但在處理完focusInEvent後,應該會再處理mouse的event,所以等於在已經反白內容的QLineEdit上,點選,所以結果是沒有反白


最後試試bool eventFilter( QObject *target, QEvent *event ) 這個virtual function,加上installEventFilter(QObject *filterObj) 這個function,來抓 MouseButtonRelease 或 MouseButtonPress  的event,成功

HexEdit::HexEdit()
{
    installEventFilter( this );    
}

bool HexEdit::eventFilter( QObject *target, QEvent *event )
{
    if( event->type() == QEvent::MouseButtonRelease)
    {
        selectAll();
        return true;
    }
    return false;
}

過程中發現,其實用tab來切換focus到QLineEdit,其內容就會全部反白

Qt版本 4.4.3
相關資料
http://lists.trolltech.com/qt-interest/2004-07/thread01290-0.html
http://lists.trolltech.com/qt-interest/2007-01/thread00657-0.html