Social Icons

2015年2月13日 星期五

將C/C++程式從Windows (VS)移植到OSX (Xcode) - Platform Header File, Part 2

不同的平台有時候用的library不一樣,最標準的就是GUI的framework,所以移植程式到不同平台很重要的一個部分是讓程式裡可以知道編譯的是什麼平台,這樣就可以呼叫這個平台對映的程式碼。

我的做法很簡單,就是定義一個標頭檔nPlatform.h,讓其他的程式碼一定要include進來,這樣就可以在編譯(compile)知道是哪一個平台了。

#ifndef nPlatform_h
#define nPlatform_h

#if (defined(NS_WIN32) || defined(WIN32))
 #define NS_PLATFORM_WIN32  1
 #define NS_PLATFORM_WINDOWS  1
#elif (defined(_WIN32_WCE) || defined(NS_WINCE))
 #define NS_PLATFORM_WINCE  1
 #define NS_PLATFORM_WINDOWS  1
#elif (defined(__ANDROID__) || defined(NS_ANDROID))
 #define NS_PLATFORM_ANDROID 1
 #define NS_PLATFORM_LINUX 1
#elif (defined(__APPLE__))
    #define NS_APPLE 1
    #include "TargetConditionals.h"
    #if TARGET_IPHONE_SIMULATOR
    // iOS Simulator
        #define NS_PLATFORM_IOS 1
    #elif TARGET_OS_IPHONE
    // iOS device
        #define NS_PLATFORM_IOS 1
    #elif TARGET_OS_MAC
    // Other kinds of Mac OS
        #define NS_PLATFORM_OSX 1
    #else
    // Unsupported platform
        DASSERT(0);
    #endif
#else
 DASSERT(0);
#endif
#endif // nPlatform_h

從上面的程式碼可以知道,在Mac裡頭是使用#elif (defined(__APPLE__))來決定編譯環境的,一旦程式檔include這個標頭檔,就可以知道目前的平台了。

其實為了避免,不同平台的函式呼叫散步在這個項目中,通常會在底層的部分將這些不同函式呼叫集中起來,上層一律使用包裝過的類別或者Api,就可以將跨平台的移植負擔最小化。

舉個例子,我會將檔案存取(file access)的函式特別包裝一個類別,實作的部分會根據不同平台使用不同的函式,上層程式碼就一律呼叫這個特定的類別,這樣發展就可以簡單了一些。

nsFile類別
/*
 * class for file
 */
// define SEEK method.
#ifndef SEEK_SET
#define SEEK_SET        0
#endif
#ifndef SEEK_CUR
#define SEEK_CUR        1
#endif
#ifndef SEEK_END
#define SEEK_END        2
#endif

class nsFile 
{
public:
 enum StateType_E 
 {
  IFEXIST = 0x01,
  IFDIR = 0x02,
  IFREG = 0x04
 };

 static const char DELM;
    static const char* DELM_STR; 
 static const wchar_t DELM_W;
 static const wchar_t* DELM_STR_W; 

    static int stat(const nsUChar_T* filename);
    static bool isAbsPath(const nsUChar_T* full_path);
    static bool isExecutable(const nsUChar_T* filename);
    static long getSize(const nsUChar_T* filename);
    static bool copy(const nsUChar_T* src, const nsUChar_T* dest);
    static bool remove(const nsUChar_T* filename);
    static bool move(const nsUChar_T* src, const nsUChar_T* dest);
    static bool isDir(const nsUChar_T* filename) { return (stat(filename)&IFDIR)?true:false; }
    static bool isRegular(const nsUChar_T* filename) { return (stat(filename)&IFREG)?true:false; }
    static bool isExist(const nsUChar_T* filename) { return (stat(filename)&IFEXIST)?true:false; }

 enum Mode_E
 {
  eREAD = 0x01,
  eWRITE = 0x02,
  //eAPPEND = 0x04
 };

 nsFile();
 ~nsFile();
 bool open(const nsUChar_T* path, int mode);
 void close();
 void flush();
 U64 size();
 U64 seek(U64 offset, int origin);
 U64 tell();
 U32 read(void*, U32, U32);
 U32 write(void*, U32, U32);

private:
 int _mode;
#ifdef NS_PLATFORM_WINDOWS
 FILE* _fd;
#elif NS_APPLE
    FILE* _fd;
#else
 int _fd;
 //FILE* _fd;
#endif
};

nsFile類別實作範例
bool
nsFile::open(const nsUChar_T* path, int mode)
{
 _mode = mode;
#ifdef NS_PLATFORM_WIN32
 if (mode&eREAD) {
  _fd = _wfopen((wchar_t*)path, L"rb+");
 }
 else if (mode&eWRITE) {
  _fd = _wfopen((wchar_t*)path, L"wb+");
 }
#elif NS_PLATFORM_OSX
    static char cpath[1024];
    memset(cpath, 0, 1024);
    nsUStr::toMultiBytes(path, cpath, 1024);
    if (mode&eREAD) {
        _fd = fopen(cpath, "rb+");
    }
    else if (mode&eWRITE) {
        _fd = fopen(cpath, "wb+");
    }

#else
 static char cpath[1024];
 memset(cpath, 0, 1024);
 nsUStr::toMultiBytes(path, cpath, 1024);
 if (mode&eREAD) {
  _fd = ::open(cpath, O_RDONLY);
 }
 else if (mode&eWRITE) {
  _fd = ::open(cpath, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU|S_IRWXG|S_IRWXO);
 }
endif
 return ((_fd)?true:false);
}

void
nsFile::close()
{
 if (_fd) {
#ifdef NS_PLATFORM_WINDOWS
  fclose(_fd);
#elif NS_APPLE
        fclose(_fd);
#else
  ::close(_fd);
#endif
 }
}

其實,上面的範例其實可以在Windows,OSX,Linux(Android)三個平台正常使用的,是我已經驗證過的。

2015年2月12日 星期四

將C/C++程式從Windows (VS)移植到OSX (Xcode) - Part I

最近為了一些經書撰寫了一版資料結構,一開始在Windows下面寫的,用的是Visual Studio 2005這個環境來編譯。因為當初這個資料結構服務的程式是手持設備,所以當初設計的時候有考量到往後可能有移植的需求,這個環境是使用cmake來建立。

為什麼會使用cmake呢?其實以前在建立公司的環境時曾經使用tmake,可是後來發現tmake已經沒有人在維護,如果以後要支援Android,iOS可能會不適用,新建系統的時候就直接放棄了,評估了一陣子就選擇cmake當成跨平台的發展環境,一直到今天還覺得當初有做這樣的決定。

在cmake環境下,重要的是寫好CMakeLists.txt這個描述檔(詳細內容就不在這邊說明了,請參考cmake的網站),一旦寫好,可以透過cmake產生不同的發展環境的project檔。因為我是使用Visual Studio 2005,簡單指令就可以產生sln檔,然後叫起VS來編譯。

cmake -G "Visual Studio 8 2005" ../src


這些日子為了將App從Android移植到iOS來,所以就買了一台MacBook Pro,就希望以前發展的程式可以在OSX下面編譯並執行。研究了一下,就發現cmake的好,在mac check out程式碼後,同樣用cmake來生成Xcode的project檔,接著啟動 Xcode打開程式檔,就可以進行編譯了。

cmake -G Xcode ../src


但是,在利用Xcode編譯的過程,標頭檔(Header files)似乎不會複製到指定的位置去,所以只能手動完成。

cmake -P cmake_install.cmake



Xcode跑起來的截圖


簡單吧,看起來是很簡單,至少環境的部分是這樣。但是程式碼本身才是重點,下回再講講程式碼本身是如何移植的。

2014年10月16日 星期四

App - Locations by GPX (GPX位置產生器)


今天發表了一個App - Locations by GPX (GPX位置產生器)可以利用GPX檔案來模擬GPS的訊號,用來測試會用到GPS訊號的App很有用。

使用前一定要:
1. 『設定』要開啓『開發人員選項』
2. 在『開發人員選項』裡要開啓『允許模擬位置』

使用方法:
1. 匯入GPX檔案

2. 播放GPX檔案


3. 目前播放狀態




下載位置: Locations by GPX (GPX位置產生器)

2014年3月23日 星期日

今天我學到 - 為什麼在Android Studio裡PackageInfo的versionName為null?

將舊的Project改到Android Studio的環境中,遇到了一個問題。原本透過PackageInfo來查詢的versionName變成null,看一下AndroidManifest.xml也沒有錯(如下),為什麼會這樣呢?


    android:versionCode="3300"
    android:versionName="3.3.00">


原來是Gradle的原因,需要在Project裡的build.gradle,加上面描述才行。
android {
    compileSdkVersion 19
    buildToolsVersion "19.0.0"

    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 19
        versionCode 3300
        versionName "3.3.00"
    }

舊Project搬到Android Studio實在有很多『梅軋』,找時間慢慢寫出來。

2014年3月21日 星期五

今天我學到 - startActivityForResult總是收到RESULT_CANCELED的問題

startActivityForResult總是收到RESULT_CANCELED的問題

程式中,啟動相片程式去選取相片,但是相片程式還沒啟動時,Activity就收到RESULT_CANCELED的回傳,而且屢試不爽,仔細查了一下終於發現原因了。

檢查AndriodManifest.xml,找到您的Activity,看看是否有下面這行:
android:launchMode="singleInstance"
如果有的話,找到了,無論時singleInstance或者是singleTask,都沒有辦法收到回傳值的!

2013年7月14日 星期日

今天我學到 - ADT升級到22,libraries在編譯時不會被加入apk的問題。

最近將ADT升級到v22時,發現程式編譯起來怪怪的,一下是library找不到,一下子執行的時候又缺乏核心元件,搞的我心浮氣躁,差一點把電腦給砸了。

後來google了一下,發現已經有很多開發者在討論這個問題了,原文在此
簡單的說,當升級ADT 22之後,在Java Build Path裡頭,原本的應該link進去的library沒有被加進去。檢查和加入的步驟如下:

1.  將滑鼠移到Project上,點擊滑鼠右件,選擇『Properties』,叫出Properties視窗。
2. 點擊『Java Build Path』設定
3. 點擊『Order and Export』標籤
4. 確認『Android Private Library』有被選取
5. 點擊『OK』鍵,完成設定
6. 將滑鼠移到Project上,點擊滑鼠右件,點擊『refresh』,然後清除Project,並重新編譯。

我這樣試過之後就可以了,您也可以試一試。

2013年5月29日 星期三

今天我學到 - 在RelativeLayout中,如果使用include的時候,如何對元件佈局

今天在該程式界面的佈局,其中發生一個問題,我在RelativeLayout中用了2個include,原本想要讓他們上下排排站,卻發現跌在一起。原本的佈局如下:
  <RelativeLayout 
        android:layout_width="130dp"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginTop="4dp"
        android:layout_marginLeft="4dp"
        android:orientation="vertical"
        >
        <include android:id="@+id/distance_panel"
            android:layout_alignParentTop="true"
         android:layout_alignParentLeft="true"
         android:layout_alignParentRight="true"
            layout="@layout/float_panel_name_low" />
        <include android:id="@+id/time_panel" 
            android:layout_below="@id/distance_panel"
         android:layout_alignParentLeft="true"
         android:layout_alignParentRight="true"
            layout="@layout/float_panel_name_low" />

後來查了一下,發現要把layout_width和layout_height也補上才會對,又學了一招。
   <RelativeLayout 
        android:layout_width="130dp"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginTop="4dp"
        android:layout_marginLeft="4dp"
        android:orientation="vertical"
        >
        <include android:id="@+id/distance_panel"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
         android:layout_alignParentLeft="true"
         android:layout_alignParentRight="true"
            layout="@layout/float_panel_name_low" />
        <include android:id="@+id/time_panel" 
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
            android:layout_below="@id/distance_panel"
         android:layout_alignParentLeft="true"
         android:layout_alignParentRight="true"
            layout="@layout/float_panel_name_low" />

2013年5月22日 星期三

今天我學到 - 不同Android版本的Fragment裡的onCreateOptionsMenu和onResume呼叫順序會不一樣


今天在查一個程式崩潰的問題,查到後來是因為當系統呼叫onResume時,一個menu item還未被建立,然後在onResume裡直接對這個null物件操作,所以造成程式崩潰。不過我在我的三台手機和模擬器都沒有問題啊,奇怪!

解決的方法就是多加個判斷就可以了。
  super.onResume();
  if (_new_record_menu!=null)
   _new_record_menu.setVisible(_show_new_record_menu);

今天我學到 - Service onStart程序內不要做loading太重的事

在我的計劃裡面,主程式起來前會呼叫一個service來完成一些上傳的動作,但是常常會讓我的主程式跑出UI freeze的對話框。用Google查了一下,Service的程序是會影響主程序的,如果要操作比較耗時的工作,還是要再另外起一個Thread會比較好啊。

原本的程式:
 @Override
 public void 
 onStart(Intent intent, int startId) {
  super.onCreate();
  showNotification();
  
  trainingId = -1;
  while (isConnect()==true &&          // still connect with internet 
    _error_occurred == false &&        // no error occurred.
    (trainingId=providerUtils.getLastTrainingId())>0) {  // can get any training record.
   
   if (_bg_h!=null) {
    _is_upload = true;
    _start_upload_time = System.currentTimeMillis();
    _bg_h.post(do_uploadTraingRecord);
    while (_is_upload==true && 
     _error_occurred==false) {
     if ((System.currentTimeMillis()-_start_upload_time) > max_waiting_time) {
      _is_upload = false;
      _error_occurred = true;
     }
     else {
      try {
       Thread.sleep(1000);
      } 
      catch (InterruptedException e) {
       e.printStackTrace();
       _error_occurred = true;
      }      
     }
    }
   }
   else {
    // no background handler.
    break;
   }
  }
  stopService();
 }
會發現有一個while迴圈,這就是造成UI凍結的兇手,只要把它搬到thread去就好了。

更改過後:
 @Override
 public void 
 onStart(Intent intent, int startId) {
  super.onCreate();
  showNotification();
  
  if (isConnect()==false || providerUtils.getLastTrainingId() <=0 || _bg_h==null) {
   stopService();
  }
  else {
   _bg_h.post(run_uploadAll);
  }
 }

2013年5月15日 星期三

今天我學到 - 如何讓Google map view (v2),在滾動頁面時不會產生殘影

使用Google map api v2時,發生了一個大問題,程式中的map view不是佔據整個頁面,只是其中的一部分。這樣問題就來了,當滾動(Scrolling)時原本map view的位置就會產生殘影,整個畫面就被破壞掉了。(後來發現如果將Google Map View放在ViewPager,也會有類似的問題)  

2013年5月7日 星期二

今天我學到 - Hardware acceleration和WebView

程式裡因為改用Google Map api V2時,就忘了將Hardware acceleration設成off,就在測試時發現WebView會不定時的crash,google了一下找到這篇,說WebView使用硬體加速會有很多問題,建議取消。但是,因為程式中的Google Map api v2用到openGL,所以如果Hardware acceleration設成on的話是會讓整個速度提升不少的。所以就花了一點時間查詢如何在外部和程式中設定是否使用硬體加速的功能。

在AndroidManifest.xml中設定

a.整個application設定
<application android:hardwareAccelerated="true" ...>
b.對activity
<activity android:hardwareAccelerated="false" />
您可以在application中設定整體是否要加速,然後在activity中對分別activity來設定。但是,如果您想要在程式中設定,或者只針對某一個View來設定呢的話,可以參考下面程式碼:
// 在window中只能設定硬體加速,不能取消哦。
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

// 在view中可以取消硬體加速
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

2013年5月6日 星期一

2013年5月2日 星期四

今天我學到 - Google Map API v2如何Zoom到指定位置和指定區塊

將程式裡的Google Map從原本的v1,移植到v2的版本上面來,發現還不是非常容易哩。原本v1裡面的一些程式碼都不能用了,全部都的改成v2的思維才行。


2013年5月1日 星期三

今天我學到 - 申請Android Google Map API v2

一直以來都使用初始的Google map api,不過這個版本在遇到新的Framework就有些適應不良了。也就是說如果在有Map的時候,要使用MapActivity,就不能和一般的Activity放在一起了,會增加程式的複雜度。另外,如果將Google Map用在Fragment中就必須使用MapFragment才行。

一旦,要將程式相容於Android 2.x時,一切都會變得很複雜了。。。

新的計劃裡要用HoloEverywhere + Google map api V2來完成,所以需要申請新的key,查了一下,摩刻部落已經將申請api key的程序寫的很完整了,放上連結,以便往後查詢。

2013年4月30日 星期二

今天我學到 - 在模擬器裡面測試Google map api V2

Google推出map api V2一段時間了,最近想要把程式中的Google map改成v2的,但是發現新的Api在模擬器是無法顯示出來的,會出現下面訊息:


2013年4月29日 星期一

今天我學到 - PagerAdapter (ViewPager) throwing IndexOutOfBounds exception

今天在重新組織一段程式碼的時候,程式一直發生當機的情形,看了一下 called stack,發現程式中使用了ViewPager,其中PagerAdapter的instantiateItem函數裡的((ViewPager)collection).addView會讓程式當掉,丟出IndexOutOfBounds的例外,查了一下發現不能像下面這樣使用:

  public Object 
  instantiateItem(View collection, int position) 
  {
                        ...
   ((ViewPager)collection).addView(view, position);
   return view;
  }

2013年4月26日 星期五

今天我學到 - 在Eclipse設定NDK編譯的環境

負責的計劃中,需要使用到c/c++的程式碼,但是在Eclipse中如果要讓Android project在編譯時可以同時將c/c++的程式碼一起編譯需要做一些設定才行。
下面就是學到的設定方法:

步驟一:將Android計劃Convert成C/C++的計劃。
您可能會擔心轉換成C/C++計劃會不會原本Android計劃就被改變了。。。。答案是不會!:p
 
(File -> New -> Other...)

2013年2月17日 星期日

讓剪貼簿Clipboard相容於2.x

在寫程式的時候,會需要將文字資料複製到剪貼簿的時候,不同Android版本會產生問題。
原因就是Honeycomb(v3.0)之後,Android使用了新的剪貼簿,所以在程式裡要判斷新舊方法的使用時機,程式片段如下:
        int currentapiVersion = android.os.Build.VERSION.SDK_INT;
     if (currentapiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB){
          android.content.ClipboardManager clipboard =  (android.content.ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 
             ClipData clip = ClipData.newPlainText("Marathon's world", subject);
             clipboard.setPrimaryClip(clip); 
     } 
     else{
         android.text.ClipboardManager clipboard = (android.text.ClipboardManager)getSystemService(CLIPBOARD_SERVICE); 
         clipboard.setText(subject);
     }

2013年2月16日 星期六

啟動螢幕Splash Screen的設計

啟動螢幕Splash Screen常常用在程式一開始的時候,特別是用來顯示公司的logo。 這邊要介紹實作splash screen的兩種方式,第一種是利用兩個不同的xml layout檔案,第二種是寫在同一個xml layout檔案中。



如何判斷float的NaN

在我的程式中為了要顯示先現在為止位置的方向,需要得到compass或GPS的bearing,然後在利用matrix中rotate的功能將方向的圖示圖示做對應的選zhau旋轉。可是有時候程式會無法得到正確bearing的值,如果將NaN的值塞到rotate中會造成程式crash。Google了一下原來在Java中要判斷float的NaN,還是要用到一些技巧的。 

也還蠻簡單的,不過要利用Float object來判斷,程式碼如下:

Float dbear = new Float(this.myloc.bearingTo(oxlocate));
if (dbear.isNaN() == false) {