Gynoid: 移动开发框架






4.97/5 (13投票s)
Gynoid 是一个封装了手机 API (WinCE, symbian, iPhone) 的框架。

介绍
移动开发是一件非常令人兴奋的事情,因为这通常意味着要用到
计算的各个领域。既然是为最终用户开发,你就需要创建漂亮的 GUI,处理数据连接和数据库等等。
我唯一不喜欢的是,每个平台都需要学习一门新语言、新的 API,如果你想将其移植到另一个平台,几乎总是需要重新编写。
像 Sun 这样的公司曾试图推广 Java 来实现“一次编写,随处运行”的美梦,但正如你已经知道的,它并未成功,尤其是在移动领域。
出于政治原因,Java 在 Windows Mobile 和 iPhone 上并不真正受支持,我个人也不认为应该使用托管语言,因为它们速度慢,消耗内存多,等等。
问题在于,开发者现在习惯于只关注他们想要实现的功能,而不愿再管理内存了。我甚至还没有提到那些连指针是什么都不知道的新开发者,但那是另一个问题……
考虑到这些,我很久以来就想写一个具有以下特点的框架(斜体部分需要概念验证,可能会有所更改):
- 原生代码
- 速度快且占用空间小(我们不是在重新发明轮子,我们只是封装原生 API)
- 跨操作系统(wince、symbian、iPhone,如果 Google 允许并且提供具有足够权限的原生工具链,也可能支持 Android)
- 面向对象(通过封装 C API 实现)
- 可以用任何语言调用(Ruby、Python、.NET 等)
- 在 LGPL 许可下开源,允许在商业产品中使用。
iPhone 是一个特殊情况,将使用商业许可,因为 Apple 不允许与第三方库进行动态链接。 - 通用 IDE(我已经在使用 Codelite 来编译 msvc 编译器、cegcc、iPhone 工具链)
- 垃圾回收:将基于 D 语言,但编译器尚未就绪,我希望将来对 gnu 编译器的支持会更好。 我已经有一个针对 Windows CE 的 D 交叉编译器,但当时是用 2.0 语言版本完成的,它不稳定。将来,我希望有人能为 D gnu 编译器做出贡献。
所以,这是 **预览版(PRE-ALPHA PREVIEW)**,因为我才开始了一个月,但正如你将看到的,我们可以用通讯录做一些简单的操作。
最后介绍一下 Gynoid 这个名字,它是一个模仿女性外观的人形机器人。
这是对 Google 的框架 Android 的一个玩笑。如果你想告诉我这个框架很荒谬,无法工作,或者封装手机 API 是大量工作,我已经知道了 ;-)
实际上,在这个预览版中,我只完成了 Windows CE 部分,下一步是为 iPhone 开发。如果成功,我就证明了这是可行的,然后我会继续实现。
背景
为了能够满足以上所有标准,我首先研究了 3 种不同的平台,以下是 Gynoid 框架有趣的地方:
- Windows CE:API 原生支持 Unicode (utf16),可以从 C 语言调用。
- iPhone:API 是 Objective-C,字符串编码为 utf8。
- Symbian:API 是自定义 C++ 语言(见 Symbian_OS_design_faults.aspx),支持 ANSI 和 Unicode (utf16)。
三个不同的平台和三个不兼容的 SDK,所以唯一的选择就是通过 C 语言封装 API。
架构
所以这个框架使用 C API,可以进行静态或动态链接。在这次的第一个发布中,我将解释如何使用通讯录 API 来实现。
那么,我们从一段纯 C 的示例代码开始吧。
ErrorCode err = 0;
int iCount = 0;
GDAddrBook* pAddrBook = NULL;
GDAbItem* pAbItem = NULL;
GDPropVal* pPropVal = NULL;
//GDStream* gdStream; // NOT YET IMPLEMENTED
int nCount;
GDArray* gdArray = NULL;
if (!GDAddrBook_Alloc(&pAddrBook) &&
!GDAddrBook_Init(pAddrBook, 0) )
{
if (GDAddrBook_GetCount(pAddrBook, &iCount) == 0)
{
////////////////////////////////////////////////////////////////
// If there is no item in address book we create one
////////////////////////////////////////////////////////////////
if (iCount == 0)
{
if (GDAddrBook_AddItem(pAddrBook, &pAbItem) == 0)
{
err = GDAbItem_SetProperty
(pAbItem, eAbFirstName, _T("Vincent") );
err = GDAbItem_SetProperty
(pAbItem, eAbLastName,_T("emmohcir") );
err = GDAbItem_SetProperty
(pAbItem, eAbMobileTelNumber, _T("0611000000") );
err = GDAbItem_SetProperty(pAbItem,
eAbEmail1Address, _T("v.emmohcir@gynoid.com") );
err = GDAbItem_SetProperty(pAbItem,
eAbEmail2Address, _T("vemmohcir@gynoid.com") );
//GDStream_Init(&gdStream);
//GDAbItem_SetProperty(pAbItem, eAbPicture,
gdStream );
GDObject_Release(pAbItem);
}
}
GDAddrBook_GetCount(pAddrBook, &iCount);
/////////////////////////////////////////////////////////////////
//Now we can retrieve contacts items and their properties
////////////////////////////////////////////////////////////////
for (int i = 0; i < iCount; i++)
{
if (GDAddrBook_GetItem(pAddrBook, i, &pAbItem) == 0)
{
//GDCTSTR lpFirstName = AbCastString
( AbItem_GetProperty(pAbItem, eAbFirstName) );
GDCTSTR lpFirstName = (GDCTSTR)
GDAbItem_GetProperty(pAbItem, eAbFirstName);
GDCTSTR lpLastName = (GDCTSTR)
GDAbItem_GetProperty(pAbItem, eAbLastName);
GDCTSTR lpMobTel = (GDCTSTR)
GDAbItem_GetProperty(pAbItem, eAbMobileTelNumber);
//GDConsole_Writeln(_T
("FirstName %s\nLastName %s\n", ...);
gyn_printf( _T("[%d] %s %s %s\n"),
i, lpFirstName, lpLastName, lpMobTel);
//
//Contact_GetProperty
//(pContact, eContactLastName, &pPropVal);
GDObject_Release(pAbItem);
}
}
}
}
GDObject_Release(pAddrBook);
即使 Gynoid 使用 C 接口,我也选择采用面向对象的方法,API 被分成 GDAddrBook
对象和 GDAbBitem
。
所有这些 GDxxxx 对象实际上都是“继承”自 GDObject
结构体,声明如下:
typedef struct _GDObject
{
long cRefs;
PFNGDDESTROY pfnDestroy;
} GDObject;
这个结构体允许使用引用计数来管理对象的生命周期,再次简化了面向对象的封装。我最初是用 void 作为 GDObject
来编写 Gynoid 对象的,但为了能够提供 C++ 封装,我需要使用 shared_pointer 或类似的智能指针,能够转移底层 Gynoid 指针的所有权。
我不喜欢这个解决方案,因为我想保持简单,但这并不妨碍你提供不同的 C++ 封装,我将来也可能会改变主意使用智能指针。
让我们继续探索 Gynoid,在使用 GDObject
时,你需要先分配它,然后像这样进行初始化:
if (!GDAddrBook_Alloc(&pAddrBook) &&
!GDAddrBook_Init(pAddrBook, 0) )
{
...
}
iPhone 开发者也不会陌生,这会让他们想起他们在 Objective-C 2.0 中创建对象的方式:
Iphone sample code to get address book:
// For people not talking objective-ce the code below is a nested
// construct where alloc message is sent to
// ABPeoplePickerNavigationController and then return object is
// sent a init message
ABPeoplePickerNavigationController *controller =
[[ABPeoplePickerNavigationController alloc] init];
回到我们的通讯录示例,我们首先分配并初始化一个 GDAddrbook
“对象”,然后我们可以开始检索相关的属性,这就是我们在调用 GDAddrBook_GetCount
来检索联系人数量时所做的。
一个需要注意的是,所有的 getter/setter 都是通过参数实现的,而不是通过返回值,这将允许进行严格的错误检查。
一旦我们知道通讯录中有多少条目,我们就可以通过使用 GDAddrBook_AddItem
(返回一个 GDAbItem
对象)和 GDAbItem_SetProperty
来创建一个带有某些属性的条目。
完成添加属性后,我们只需要调用通用函数 GDObject_Release()
来释放对象。
现在你对工作方式有了大致的了解,但我可以理解有些开发者不喜欢用 C 语言编程,尤其是在设计 GUI 应用程序时,我们可以从高级语言提供的某些便利中受益。
面向对象封装
最容易用于封装的面向对象语言是 C++。由于内存管理由我们的底层 C 接口处理,你会发现封装只是我们 GDObject
指针的容器。
这是我用来封装 GDAbItem
指针的类:
class AddrBookItem
{
public:
AddrBookItem(): m_gpAbItem(0)
{}
AddrBookItem(const AddrBook& addrBook)
{}
AddrBookItem(GDAbItem* gpAbItem): m_gpAbItem(gpAbItem)
{}
AddrBookItem::AddrBookItem(const AddrBookItem &s) : m_gpAbItem(0)
{
*this = s;
}
AddrBookItem& AddrBookItem::operator =(const AddrBookItem &s)
{
// If we are already holding an item we release it
if (m_gpAbItem != NULL)
{
GDObject_Release(m_gpAbItem);
m_gpAbItem = NULL;
}
m_gpAbItem = s.m_gpAbItem;
GDObject_AddRef(m_gpAbItem);
return *this;
}
virtual AddrBookItem::~AddrBookItem()
{
release();
}
void* getProperty(EAbItemProp eAbitemProp)
{
return GDAbItem_GetProperty(m_gpAbItem, eAbitemProp);
}
ErrorCode setProperty(EAbItemProp eContactProp, GDCTSTR lpPropValue)
{
return GDAbItem_SetProperty
(m_gpAbItem, eContactProp, lpPropValue);
}
ErrorCode show()
{
return GDAbItem_Show(m_gpAbItem);
}
protected:
long release()
{
return GDObject_Release(m_gpAbItem);
}
protected:
GDAbItem* m_gpAbItem;
};
那么,如果我们尝试用新的 C++ 封装来重写第一个示例呢?
////////////////////////////////////////////////////////////////
// Same example done with C++ wrapper
////////////////////////////////////////////////////////////////
using namespace gynoid;
int nCount;
AddrBook addrBook;
iCount = addrBook.getCount();
if (iCount == 0)
{
AddrBookItem abItem = m_addrBook.addItem();
abItem.setProperty(eAbFirstName, _T("Vincent") );
abItem.setProperty(eAbLastName, _T("emmohcir") );
abItem.setProperty(eAbEmail1Address, _T("v.emmohcir@gynoid.com") );
abItem.setProperty(eAbEmail2Address, _T("vemmohcir@gynoid.com") );
}
iCount = addrBook.getCount();
for (int i = 0; i < iCount; i++)
{
AddrBookItem abItem = addrBook.getItem(i);
CString sFirstName = abItem.getProperty(eAbFirstName);
CString sLastName = abItem.getProperty(eAbLastName);
CString sMobTel = abItem.getProperty(eAbMobileTelNumber);
gyn_printf( _T("[%d] %s %s %s\n"),
i, lpFirstName, lpLastName, lpMobTel);
}
所以这次,正如你所看到的,我们使用了 AddrBook
和 AddrBookItem
对象,我们甚至不需要处理代码的释放,因为对象的析构函数完成了这项工作。
我使用了 CString
对象来管理 string
s,但很快它将被 GDString
对象取代。
最后谈谈面向对象封装,有一件事我不能不提,那就是错误检查,关于异常或非异常的问题。
错误处理 (异常或非异常)
错误处理是一个难题,以 Gynoid 当前的状态,现在回答这个问题还为时过早。
但一旦我们开始使用面向对象语言,关于使用异常的问题通常就不会太远。我将在下一篇文章更新中尝试回答。
示例代码
为了说明通讯录 API 的用法,由于我还没有编写图形部分,我使用了 WTL 和由 Windows Mobile 大师 João Paulo Figueira 制作的一个很棒的触摸控件。
在下一篇文章中,我希望能够使用一个平台独立的 GUI(使用 SDL、guichan 或 efl 已经可以实现)。
结论
这个项目还很年轻,可能不会走很远,这取决于下一个挑战,即封装 iPhone 的通讯录 API。无论如何,如果它不能成为一个跨平台框架,它仍然对 Windows CE 开发者有用,因为它简化了对原生 API 的访问,隐藏了丑陋的 COM 接口,并提供了 OO 封装,但这仅在你需要时才有用。
不要犹豫贡献,你可以在 Sourceforge 上关注活动。
历史
- 2009年5月29日:首次发布