c++ 的 *&

當函數的 argument 宣告成 *& 時,該怎麼傳呢?
答案是要轉型一下,以下面例子來說,就是要用 (char*&) 轉型過,這樣編譯器就不會吐錯誤訊息給你啦~
[cpp]#include <iostream>
int f( char*& s )
{
printf(“>> %s\n”, s );
}
int main( int argc, char* argv[] )
{
char* s=”Hello world!!”;
f( (char*&)s ); // 要這麼轉!
}[/cpp]

會自殺的類別

坦白說,我從沒想過物件可以自殺這件事情。當我看到這樣寫的時候,實在是很令我驚訝…實驗的結果,還真的是可以。

#include 
class SuicideSample {
public:
SuicideSample() { std::cout << "ctor." << std::endl; }
~SuicideSample() { std::cout << "dtor." << std::endl; }
void DoIt( void ) {
delete this;
}
};
int main( int argc, char* argv[] )
{
SuicideSample* obj=new SuicideSample();
obj->DoIt();
// of course, object can kill itself, but if you try to kill it again
// program will crash.
//delete obj;
return 0;
}

Skia and framebuffer

根據 Jserv 大的淺談 Google Skia 圖形處理引擎,得知 skia 只能畫在 Memory buffer 上,那麼,可以直接畫在 Framebuffer 上嗎??

Jserv 大文章裡的例子,SkBitmap 得先呼叫 allocPixels() 來配置所需要的 Memory buffer,根據 SkBitmap.h 裡的宣告,allocPixels 事實上是使用 allocator 來配置所需要的 Memory buffer,如果未指定,會以 stdalloc(HeapAllocator) 來進行配置。因此如果要直接使用 framebuffer,可以繼承 SkBitmap::Allocator 類別之後,改寫 allocPixelRef() 來達到目的。

大致的代碼就像這樣子:[c]
#include “SkTypes.h”
#include “SkRefCnt.h”
#include “SkBitmap.h”
#include “SkDevice.h”
#include “SkPaint.h”
#include “SkRect.h”
#include “SkMallocPixelRef.h”
class FrameBufferAllocator: public SkBitmap::Allocator
{
public:
FrameBufferAllocator();
virtual ~FrameBufferAllocator();
virtual bool allocPixelRef(SkBitmap*, SkColorTable*);
private:
char* m_addr;
int fd;
};
FrameBufferAllocator::FrameBufferAllocator()
{
}
FrameBufferAllocator::~FrameBufferAllocator()
{
// munmap framebuffer pointer.
// close the framebuffer device we opened
}
bool FrameBufferAllocator::allocPixelRef(SkBitmap* dst, SkColorTable* ctable)
{
size_t size = dst->getSize();
// open framebuffer
fd = open( “/dev/fb0”, O_RDWR );
// setup framebuffer device via ioctl.
// mmap framebuffer
m_addr = (char *)mmap(0, screensize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// call original procedures in HeapAllocator::
dst->setPixelRef(new SkMallocPixelRef(m_addr, size, ctable))->unref();
dst->lockPixels();
return true;
}
[/c]

接著,Jserv 大的範例只要修改一行:[c]
bitmap.allocPixels( new FrameBufferAllocator, NULL );
[/c]
就可以順利運作了。

p.s. 在 link libskia.a 時,遇到很鳥的狀況,skia 的 Makefile 在製作時,是直接以帶有路徑的 .o 去作,而我 toolchain 的 gcc 居然無法處理,必須要以沒有路徑的 .o 去重新製作,這樣才能 link 成功。

C 的 && 與 ||

前一陣子看Javascript 語言精髓與編程實踐這本書的時候,發現可以用 && 來達到 if ,用 || 達到 if not 的效果,所以下面是以 C/C++ 實驗的結果:

#include <stdio.h>
int say_hello( void )
{
printf("Hello world!!\n");
return 0;
}
int main( int argc, char* argv[] )
{
int flag=0;
printf("flag=0\n");
flag && say_hello(); // say_hello() won't be invoked
flag || say_hello(); // say_hello() will be invoked
printf("flag=1\n");
flag=1;
flag && say_hello(); // say_hello() will be invoked
flag || say_hello(); // say_hello() won't be invoked
return 0;
}

不過坦白說,這樣寫的結果是導致可讀性變差,只有自己維護這份 code 時,那是可以用,很多人維護一份 code 時,最好還是避免,或者,加上註解比較好。
我想 c# / java 應該也可以這樣作。
p.s. 如果不是呼叫函數的話,記得要使用 ( ),例如:

i && (j=100);

這就等同於:

if(i) j=100;

Linux I2C 與 AD71471

AD71471 的 Device address 是 0x58,Linux driver 在處理這個時,其實會自行左移一位,因此,在 I2C_SLAVE_FORCE 的 ioctl 裡,應該是要傳 0x2c。這邊因為我暈頭,把 0x58>>1 算成 0x4c,導致我搞了好一陣子,直到 M 同事指正以後,才弄對。

再來,AD71471 在做讀寫時,Register address 與 data 都是 2 bytes,而 i2ctools 裡,處理 Register address 都只傳 1 byte(I2C_SMBUS),因此不適用在 AD71471 上。我把 Linux kernel i2c-core.c 裡的 code 翻出來改寫,改用 I2C_RDWR 來處理:

bool Write( uint16_t address, uint16_t value ) {
int res=0;
struct i2c_rdwr_ioctl_data msg_rdwr;
char msgbuf0[I2C_SMBUS_BLOCK_MAX+4];
char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
struct i2c_msg msg[1] = { { _address, 0, 4, msgbuf0 } };
uint8_t* pAddr = (uint8_t*)&address;
uint8_t* pValue = (uint8_t*)&value;
msg_rdwr.msgs = &msg[0];
msg_rdwr.nmsgs = 1; // write // read = 2
// 因為 little endian,所以要作調整
msgbuf0[0] = *(pAddr+1);
msgbuf0[1] = *(pAddr+0);
msgbuf0[2] = *(pValue+1); // (1)
msgbuf0[3] = *(pValue+0);
res = ioctl( _file, I2C_RDWR, &msg_rdwr );
usleep(10000);
return true;
}
bool Read( uint16_t address, uint16_t& data ) {
int res=0;
struct i2c_rdwr_ioctl_data msg_rdwr;
char msgbuf0[I2C_SMBUS_BLOCK_MAX+2];
char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]={0};
struct i2c_msg msg[2] = { { _address, 0, 2, msgbuf0 },
{ _address, I2C_M_RD, 2, msgbuf1 }
};
uint8_t* pAddr = (uint8_t*)&address;
msg_rdwr.msgs = &msg[0];
msg_rdwr.nmsgs = 1; // read = 2
// 因為 little endian,所以要作調整
msgbuf0[0] = *(pAddr+1);
msgbuf0[1] = *(pAddr+0);
res = ioctl( _file, I2C_RDWR, &msg_rdwr );
usleep(10000);
msg_rdwr.msgs = &msg[1];
msg_rdwr.nmsgs = 1; // read = 2
res = ioctl( _file, I2C_RDWR, &msg_rdwr ); #ifdef DEBUG
data = msgbuf1[0] | (msgbuf1[1] << 8);
return true;
}
int main( int argc, char* argv[] )
{
int data=0;
Read( 0x17, data );
printf("data=%d\n", data );
// 這邊其實不好,實際上寫 0x0052會比較清楚,這邊必須寫 0x5200,因為我在 Write() 裡有作對調,參看(1)
Write( 0x00, 0x5200 );
}

GtkDrawingArea 與 gtk_widget_set_events()

GtkDrawingArea 預設是不收 button_press_event 跟 key_press_event 的,所以要使用 gtk_widget_set_events() 告訴 GtkDrawingArea 要接收才行。

這個,我是去 Google Code Search 找來的,雖然 devhelp 可以查指令,但沒有範例,還是很難猜到怎麼用。

#include <gdk/gdkkeysyms.h> // 定義按鍵值的 header
static gboolean press_event( GtkWidget* widget, GdkEventButton* event, gpointer data )
{
if( debug )
g_print("press_event: x=%f y=%f button=%d\n", event->x, event->y, event->button );
if( event->button==1 )  { // left
}
}
static gboolean key_event( GtkWidget* widget, GdkEventKey* event )
{
if( debug )
g_print( "event->keyval=%d event->state=%d\n", event->keyval, event->state );
switch (event->keyval) {
// 省略...
}
return TRUE;
}
int main( int argc, char* argv[] )
{
// 省略一萬行...
g_signal_connect( drawing_area, "button_press_event", G_CALLBACK( press_event ), NULL );
g_signal_connect( drawing_area, "key_press_event", G_CALLBACK( key_event ), NULL );
// 要接收 button_press_event 跟 key_press_event 喔~
gtk_widget_set_events( drawing_area, gtk_widget_get_events(drawing_area) | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK );
// 省略兩萬行...
}

gtk 載入並顯示圖片

實際上是用 GDK+GtkDrawingArea 來畫,所以在下面的程式片斷,你會看到我宣告了 GtkDrawingArea 並且實作了 GtkDrawingArea 的 expose 事件。
GDK 支援的圖片格式很多,常見的 jpg、png、bmp 都沒問題。

static gboolean expose_event( GtkWidget* widget, GdkEventExpose* event, gpointer data )
{
GError* error=NULL;
int width=widget->allocation.width, height=widget->allocation.height;
GdkPixbuf* buf=gdk_pixbuf_new_from_file_at_scale( "your_photo.jpg", &error );
if( buf==NULL )
g_print("load fail.\n" );
else
{
bufWidth = gdk_pixbuf_get_width( buf );
bufHeight = gdk_pixbuf_get_height( buf );
gdk_draw_pixbuf( widget->window, NULL, buf, 0, 0, 0, 0,
(width>bufWidth?bufWidth:width), (height>bufHeight?bufHeight:height),
GDK_RGB_DITHER_NORMAL, 0, 0 );
g_object_unref( buf );
}
}
int main( int argc, char* argv[])
{
GtkWidget* drawing_area=NULL;
// 省略一萬行
g_signal_connect( G_OBJECT(drawing_area), "expose_event", G_CALLBACK( expose_event ), NULL );
// 再省略兩萬行...
}

new()

昨天有同事問到可不可以讓物件 new 在 share memory 裡面,我跟他說 c++ 的 new 可以像下面例子這樣用,但是他後來沒試。好吧,反正我以前也沒試過,就寫了個小程式試一下:

/**
*       Filename:  test_new.cpp
*    Description:  Test new(storage) Person();
*/
#include <cstdlib>
#include <string>
#include <iostream>
class Person {
public:
Person() {}
~Person() {}
std::string getName() { return _name; };
void setName( std::string name ) { _name=name; }
std::string toString() { return _name; }
private:
std::string _name;
};
int main( int argc, char* argv[] ) {
char* storage = (char*)malloc( 1024 ); // allocate 1K
Person* person = new(storage) Person();
person->setName( "anonymous" );
std::cout << person->toString() << std::endl;
// if just storage, person will be replaced by intArray. (2)
// int* intArray = new(storage) int[10];
int* intArray = new(storage+sizeof(person)) int[10];
int i=0;
// assign value.
for( i=0; i<10; i++ )
intArray[i] = i;
// show the values
for( i=0; i<10; i++ )
printf( "%d ", intArray[i]);
printf("\n");
// dump storage
char* iter=storage;
printf("=== begin dump ===\n");
i=0;
while( i!=512 ) {
printf( "%02x ", (unsigned char)*iter );
iter++;
i++;
if( !(i%16) )
printf( "\n" );
}
printf("=== end dump ===\n");
printf( "person address = %08x intArray address = %08x\n",
(unsigned int)person, (unsigned int)intArray );
// cannot delete, it cause segmentation fault. (1)
//delete person;
//delete[] intArray;
// but we can use free.
free( storage );
}

結論:

  1. new(storage) Person() 實際上是 new 在 storage 這塊空間裡面,所以之後如果呼叫 delete,會出錯。
  2. 如果不累加 storage 的話,會把之前配置的空間覆蓋掉。
  3. malloc() 可以用 shmget()、shmat() 代替,沒有問題。
  4. 以上面的例子,std::string 會配置一塊空間來放字串,這塊空間並不在 storage 裡面,使用時要注意。如果要這樣用,應該要再取代掉 STL allocator 的機制。

CMake + CTest

CMake 內建 CTest,基本使用可以參考:CMake Testing With CTest
大致把要點整理如下:

  1. 在專案根目錄的 CMakeLists.txt 加上 ENABLE_TESTING()
  2. 在你 test 程式所在目錄的 CMakeLists.txt 加上 add_test( 測試名稱 執行檔名字 [參數1] [參數2] … )
  3. 測試程式在錯誤發生時呼叫 exit() 並傳入非 0 值,正常結束的話,則呼叫 exit(0)。
  4. 大功告成以後,先刪除 CMakeCache.txt,然後用 cmake 重新產生 Makefile,接下來就可以用 make && make test 來進行測試了。
  5. make test 是進行所有測試,只想進行某幾項測試的話,可以查看 ctest 的 -R, -E, -I 這幾個選項的說明。-R 是用 regular expression 找特定名稱的測試項目,-E 則是相反,排除掉特定名稱的測試項目,-I 是指定項目號碼,表示進行指定項目的測試。

文件裡面還有提到可以把測試結果自動上傳到網站上等等,不過看來是用不到,就沒嘗試了。

以 Visual Studio 2005 編譯 boost::regex

下載 boost 跟 bjam 以後,第一件事情是編譯。

  1. 打開 visual studio 2005 命令提示字元
  2. 切換到你的 boost目錄 下,這裡假設為 c:\boost_1_36_0
  3. 執行 bjam –build-dir=”c:\boost_1_36_0\build” –toolset=msvc-8.0 –build-type=complete –with-regex stage,就可以只編譯 boost::regex.
  4. 編譯會需要一陣子,編譯好的檔案就會放在 c:\boost_1_36_0\build\boost\bin.v2\libs\regex\ 下

在使用時,在專案屬性裡指定 [組態屬性][C/C++][一般] 的 “其他Include目錄” 為 c:\boost_1_36_0,再指定 [組態屬性][連結器][一般] 的 “其他程式庫目錄” 為 “c:\boost_1_36_0\build\boost\bin.v2\libs\regex\build\msvc-8.0\debug\link-static\threading-multi”,進行編譯即可。