2017年6月28日 星期三

Makefile詳解 (nmake)

2.4.3  描述文件的語法
make工具最主要也是最基本的功能就是通過描述文件來描述源程序之間的相互關係並自動維護編譯工作,而描述文件需要按照某種語法進行編寫,文件中需要說明如何編譯各個源文件並鏈接生成可執行文件,並要求定義源文件之間的依賴關係,為了更方便使用,文件中同時可以用一些宏定義。描述文件一般需要包含以下內容:
● 註釋
● 宏定義
● 顯式規則
● 隱含規則
在這裡,首先為2.4.1節中有關test.exe的例子寫出一個描述文件,再逐步介紹各部分的書寫語法。為了方便使用,一般都把描述文件的文件名取為默認文件名:makefile。這個例子的makefile文件如下(注意前面括號裡的是行號,不是文件的真正內容):
(001)   # nmake工具的描述文件例子
(002)   EXE = Test.exe      #指定輸出文件
(003)   OBJS =  x.obj \
(004)       y.obj       #需要的目標文件
(005)   RES = x.res     #需要的資源文件
(006)
(007)   LINK_FLAG = /subsystem:windows      #鏈接選項
(008)   ML_FLAG = /c /coff      #編譯選項
(009)
(010)   #定義依賴關係和執行命令
(011)   $(EXE): $(OBJS) $(RES)
(012)       Link $(LINK_FLAG) /out:$(EXE) $(OBJS) $(RES)
(013)   $(OBJS): Common.inc
(014)   y.obj: y.inc
(015)
(016)   #定義彙編編譯和資源編譯的默認規則
(017)   .asm.obj:
(018)       ml $(ML_FLAG) $<
(019)   .rc.res: 
(020)       rc $<
(021)    
(022)   #清除臨時文件
(023)   clean:
(024)       del *.obj
(025)       del *.res
1.註釋和換行

makefile中的註釋是以#號開頭一直到行尾的字符,當nmake工具處理到這些字符的時候,它會完全忽略#號及其後面的全部字符。

當一行的內容過長的時候,可以用換行符來繼續,makefile的換行符是\,如例子中的第3行和第4行可以合併為:

  1. OBJS =  x.obj y.obj     #需要的目標文件 

在使用換行符的時候要注意在「\」後面不能再加上其他字符,包括註釋和空格,否則nmake檢測到「\」不在一行的最後,就不會把它當成換行符解釋,從而出現錯誤。

2.宏定義

makefile中允許使用簡單的宏定義指代源文件及其相關編譯信息,可以把宏稱為變量,在整個描述文件中,只要符合下面語法的行就是宏定義:變量名=變量內容

如上面例子文件中的第2到第8行就是宏定義,在引用宏時只需在變量前加$符號,但是要注意的是,如果變量名的長度超過一個字符,在引用時就必須加圓括號(),下面都是有效的宏引用:

  1. $(LINK_FLAG)  
  2. $(EXE)  
  3. $A  
  4. $(A) 

其中最後兩個引用是完全一致的。

宏定義的使用可以使makefile的使用更靈活:首先可以使文件便於修改,比如把第8行和第18行中ml的選項部分寫成宏定義,以後要改變編譯選項的時候,只要直接在makefile文件頭部改變宏定義就可以了,不必閱讀修改整個makefile文件;其次,當不止一個地方用到同一個文件的時候,把文件名定義為宏定義可以減少錯誤,增加可讀性,同時也可以便於修改;最大的好處是可以直接在命令行中用新的宏定義覆蓋,比如在命令行中鍵入:

  1. nmake ML_FLAG="/c /coff /Fl" 

那麼這時就會以新的/c /coff /Fl定義代替makefile中定義的/c /coff,在這種使用中要注意兩個問題:一是宏名稱要區分大小寫,ML_FLAG和ml_flag是不一樣的;二是定義值中有空格的時候要用雙引號引起來(沒有空格時可以不用雙引號,如ML_FLAG=/c),這使臨時使用不同的參數編譯文件時可以不必修改makefile。
3.顯式規則
makefile中包含有一些規則,這些規則定義了文件之間的依賴關係和產生命令,一個規則的格式是這樣的:
目標文件:依賴文件;命令        (方法1)

目標文件:依賴文件               (方法2)
 命令
在規則定義和命令行中,不能包含註釋,例子中的第11和12行把宏定義展開後就是:

  1. test.exe: x.obj y.obj x.res  
  2. Link /subsystem:windows /out:test.exe x.obj y.obj x.res 

這裡的目標文件就是test.exe,它依賴於3個文件x.obj,y.obj和x.res,如果有必要,產生目標文件的命令就是下面的Link命令。規則可以用兩種方法,用方法2的時候,命令可以從第2行開始,第1行的「;」省略,但是這時命令前面必須有一個Tab字符,否則nmake無法區分這究竟是命令還是別的定義。

在同一個規則中,目標文件可以有多個,依賴文件也可以有多個,同時命令也可以由多個命令行組成,當然這時候就必須用第二種方法定義了,否則無法在同一行中寫入多條命令。

我們也可以用和上例中類似的方法定義其他規則,如x.obj或x.res的生成方法,但nmake如何知道哪個是最終要make的文件呢?實際上nmake默認將整個描述文件的第一條規則中的目標文件認為是最終文件,如果我們把第11,12行放到第13行後面,那麼x.obj和y.obj的建立規則就成了第一條規則,nmake建立了x.obj和x.obj之後就不理會test.exe的建立了,所以我們必須把最終需要生成的文件放在第一條規則定義。當然,在nmake的命令行參數中可以指定要make的目標,如我們只需生成x.res文件,那麼不必修改makefile將x.res的描述規則移動到最前面,而是直接在命令行鍵入以下命令即可:

  1. nmake x.res 
參數中也可以同時帶好幾個目標文件名,nmake會一一處理,如果指定的目標文件沒有對應的規則,nmake會返回一個出錯信息:

  1. fatal error U1073: don't know how to make 'xxx文件' 

當用戶要求nmake去建造一個目標時,make會去找到這個目標的依賴規則,這時規則中定義的命令並不會立刻被執行,而是首先要做一些事情:nmake先去檢查依賴文件是否是另一條規則的目標文件,如果是,則先處理這一條規則;如果不是,nmake再檢查各個依賴文件的時間,看這些文件有沒有比目標文件更新的,如果沒有,nmake會決定不再重新建造目標文件,並給出提示:'xxx文件' is up-to-date,如果依賴文件有比目標文件更新的,才執行命令。

所以一個順序下來,所有的目標文件,以及它們的依賴文件,以及依賴文件的依賴文件都會被檢查並更新,總而言之,一個目標文件的建立包含了順序正確的指令鏈接,這個鏈接結構是樹狀的,目標文件是根,一級級擴展到多個文件,我們要求的是nmake去建立鏈接中處於根部的那個文件,nmake會根據鏈接結構從目標開始向初始狀態前進,最後慢慢回來,在這個過程中執行建立每個文件所必需的命令,一直到最終目標建立完成。

目標也可以沒有依賴文件,而且目標也可以不是一個真正存在的文件,如例子第23行到第25行中的clean是一個目標,但我們並不是要生成一個clean文件,而是希望在文件調試完畢後用nmake來清除臨時文件,當我們鍵入nmake clean的時候,工作目錄下並沒有clean這個文件,那麼nmake就會去執行clean定義中的命令,因為nmake把每一個不存在的目標當做是一個過時的目標,如此一來,就會刪除中間過程中的文件*.obj和*.res。

指出了目標文件全名的規則稱為顯式規則,但有些類別的文件的編譯方法可以是雷同 的,如從asm文件產生obj文件的命令總是用ml,從rc文件產生res文件的命令總是用rc,對於每個文件都寫一條規則有些多餘,這時候就要用到隱含規則。

4.隱含規則

隱含規則可以為某一類的文件指出建立的命令,它具體定義了如何將帶一個特定擴展名的文件轉換成具有另一種擴展名的文件,定義的格式是:

  1. .源擴展名.目標擴展名:;命令     (方法1)  
  2.  
  3. 或  
  4. .源擴展名.目標擴展名:            (方法2)  
  5.     命令 

隱含規則的語法和顯式規則相似,也是用「:」隔開,在「;」下面書寫命令,也可以不用「;」而將命令寫在第2行,同理,這時命令之前要加一個Tab字符。

隱含規則不能有依賴文件,所以「:」下面沒有內容,例子中的第17、18行定義了從asm文件建立obj文件的隱含規則,第19和20行定義了從rc文件建立res文件的隱含規則,隱含規則中無法指定確定的輸入文件名,因為輸入文件名是泛指的有相同擴展名的一整類文件,這時候就要用到幾個特殊的內定宏來指定文件名,這些宏是$@,$*,$?和$<,它們的含義如下:

● $@ —— 全路徑的目標文件。

● $* —— 除去擴展名的全路徑的目標文件。

● $? —— 所有源文件名。

● $< —— 源文件名(只能用在隱含規則中)。

所以第19、20行中的rc $< 用於x.rc的時候就是rc x.rc,而用於y.rc時就是rc y.rc了。

讀者可以注意到一些顯式規則沒有命令行,如第13行的「$(OBJS): Common.inc」指出了所有的obj文件都依賴於Common.inc文件,第14行的「y.obj: y.inc」則指出了y.obj除了依賴第13行的規則外,還依賴於y.inc。但是第13行和第14行的兩條規則都沒有指出產生這些obj文件的命令,所以nmake處理的時候會到隱含規則中去找命令行,最後會用第18行的「ml $(ML_FLAG) $<」命令去產生這些obj文件。

2017年6月6日 星期二

[轉] Error: Only the original thread that created a view hierarchy can touch its views

如果我們想要在另一個Thread中操控 UI ,在Swing中我們可以直接使用(可能會有些問題,不過還是可以直接用),不過,在Android就不行了。


如果我們直接使用的話,會丟出一個 android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views 錯誤


我們必須透過Message的傳遞來達到目的。


也蠻簡單的,


在要被操作的UI物件中(如View)宣告一個 android.os.Handler 物件,


然後覆寫public void handleMessage(Message msg),在該方法中便可以對UI進行操作。


而在Thread中則負責送出android.os.Message 物件到該 Handler。


這樣說好像不夠清楚,看一下code就懂了


// 做一個可以利用Thread修改的View
public class MyView extends View implements Runnable
{
    public static final int MEG_INVALIDATE = 9527; //自訂一個號碼
    
    private Thread thread;
    
    // Constructor
    public AnimationView(Context context) 
    {
        super(context);
    }


  public AnimationView(Context context, AttributeSet attrs) 
  {
        super(context, attrs);
  }
      
  Handler handler = new Handler() 
  {
        public void handleMessage(Message msg) 
  {
    switch (msg.what) 
{
// 當收到的Message的代號為我們剛剛訂的代號就做下面的動作。
case MEG_INVALIDATE:
// 重繪UI
invalidate();
break;
            }
    super.handleMessage(msg);
        }
    };
    
    // 建立 Thread,並開始
    public void init()
  {
  thread = new Thread(this);
thread.start();
  }
    // invalidate() 會呼叫到 onDraw
    public void onDraw(Canvas canvas) 
  {
  
  }

  // Runnable 介面
    public void run() 
  {
  Message m = new Message();
// 定義 Message的代號,handler才知道這個號碼是不是自己該處理的。
m.what = MEG_INVALIDATE;
handler.sendMessage(m);
  }

}

2017年6月1日 星期四

Passing struct pointer from C++ dll to C#


#include <stdio.h>
#include <Windows.h>
struct Name
{
char FirstName[100];
char LastName[100];
char *Array[3];
};
extern "C" __declspec(dllexport) void __cdecl GetName(struct Name *name)
{
strncpy_s(name->FirstName, "FirstName", sizeof(name->FirstName));
name->Array[0] = "Foo 0";
name->Array[1] = "Foo 1";
name->Array[2] = "Foo 2";
}
extern "C" __declspec(dllexport) void __cdecl Hello()
{
printf("Hello\n");
}
using System;
using System.Runtime.InteropServices;
namespace TestApp
{
class Program
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Name
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string FirstName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string LastName;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public string[] Array;
};
[DllImport("TestDll.dll")]
public static extern void GetName(ref Name name);
[DllImport("TestDll.dll")]
public static extern void Hello();
static void Main(string[] args)
{
Hello();
var name = new Name();
GetName(ref name);
Console.WriteLine(name.FirstName);
foreach (var s in name.Array)
Console.WriteLine(s);
}
}
}