使用 C++ 编写 Android GUI: 第 3 部分:调试
本文介绍了如何使用 NDK-debug 工具调试 C++ 代码,并提供了一些关于 Android C++ 编程的建议。如果应用程序是用 Java 编写的,程序员可以使用 Eclipse 来调试代码、设置断点并逐行跟踪。
引言
本文介绍了如何使用 NDK 调试工具调试 C++ 代码,并提供了一些关于 Android C++ 编程的建议。如果应用程序是用 Java 编写的,程序员可以使用 Eclipse 来调试代码、设置断点并逐行跟踪。那么用 C++ 编写时,我们如何调试呢?目前,对于 Android 2.2 及以上版本,NDK 提供了一个 NDK 调试工具,该工具运行在 Linux 上,可用于调试源代码。
调试环境可能因平台而异。此处提供的步骤基于我的主机环境,即 Windows XP、Android-ndk-r6b,并使用 Cygwin 来为 Android 构建 C++ 代码。Android-ndk-r6b 和 Cygwin 的安装超出了本文的范围。您可以参考其他资料。
要调试的示例代码是前一篇文章中提供的列表视图持有者。假设您已经准备好了环境,并且 C++ 代码可以成功地为 Android 构建。
项目更改
- 修改 AndroidManifest.xml 以启用 gdbserver。
根据 NDK 的文档,为了调试本地代码,AndroidManifest.xml 应添加标志
android:debuggable="true"
,如下所示。<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:debuggable="true" > <activity android:label="@string/app_name" android:name=".DebugActivity" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
- 使用调试标志“NDK_DEBUG=1”重新编译项目。
- 从 eclipse 运行项目。
使用 ndk-debug 进行调试
切换到 Cygwin 控制台
- 如下所示运行 ndk-debug
可能会有很多警告。但不必担心,它们不会影响调试过程。
- 键入 list 命令,您可以看到源文件。
- 使用 b 命令设置断点。
- 使用 c 命令继续运行。
- 移动设备响应可能会变慢,请等待设备可以正常操作。
如果设备长时间没有响应,可以键入 CTRL+C 退出 ndk-debug 并重复以上步骤。
- 单击按钮,源代码将在之前设置的断点处中断。
在 Cygwin 控制台中,查看变量值时,可能会看到“值已优化掉”。
在这种情况下,应将 –Oo 标志添加到 Android.mk 文件,重新生成项目并重新启动。
LOCAL_CFLAGS += -Wno-write-strings -O0 -DENV_ANDROID
LOCAL_CPPFLAGS += -Wno-write-strings -O0 -fexceptions -DENV_ANDROID
LOCAL_LDFLAGS += -Wno-write-strings -O0 -DENV_ANDROID
在模拟器上调试的效率可能较低,响应速度慢。更好的选择是购买一部便宜的手机或掌上设备,并使用真实设备进行调试。在 Android 上调试 C++ 代码不是一件容易的事,无论您是否熟悉 GDB。我认为将成熟的代码从其他平台迁移到 Android 是一个不错的选择,或者在其他平台上使用更方便的工具进行调试,而不是直接在 Android 上进行调试。
在 Win32 上调试
CLE 也支持 Windows 和 Linux,因此我们可以在 Windows 上调试程序逻辑。让我们更深入地讨论一下。要在 Windows 上进行调试,需要做很多工作,不是技术上的,而是设置 Android 类存根的工作。这项工作尚未开始,因此本文仅提供步骤来说明我们如何在 Windows 上调试 Android C++ 代码。您也可以按照本文中的示例自己编写存根。示例也包含在下载包中。C++ 代码的结果是一个共享库,在 Windows 平台下,它只能构建为动态库。因此,第一步是创建一个加载器来初始化环境并加载它。
开始之前,应从 http://www.srplab.com/data/starcore_win32.1.5.1.exe 安装 CLE for Win32。
动态库加载器
加载器初始化 CLE 环境,导入 wrapandroid 服务描述文件 SRPWrapAndroidEngine.xml,并为共享库创建一个服务。加载器可以使用 Java、Python、Lua、C#、C++ 或 CLE 支持的其他语言创建。这里我们使用 C++ 来创建。代码如下。
- 初始化 CLE 环境。
class ClassOfBasicSRPInterface *BasicSRPInterface; VS_CORESIMPLECONTEXT Context; // init CLE environment. The first parameter Context is used to get the return value. BasicSRPInterface = VSCore_InitSimpleEx(&Context,0,0,NULL,0,NULL); if( BasicSRPInterface == NULL ){ printf("init starcore fail\n"); return -1; } // import wrapandroid service description file. This file contains id and name of android classe. { FILE *hFile; int FileSize; char *xmlBufer; hFile = fopen("SRPWrapAndroidEngine.xml","rt"); fseek(hFile,0,SEEK_END); FileSize = ftell(hFile); fseek(hFile,0,SEEK_SET); xmlBufer = (char *)malloc(FileSize+1); FileSize = fread(xmlBufer,1,FileSize,hFile); fclose(hFile); xmlBufer[FileSize] = 0; if( BasicSRPInterface ->ImportServiceFromXmlBuf(xmlBufer,false) == VS_FALSE ){ printf("parse [SRPWrapAndroidEngine.xml] failed\n"); return -1; } free(xmlBufer); } // create a service for share library. BasicSRPInterface -> CreateService( "","wrapandroid", NULL, "123",5,0,0,0,0,0 ); // obtain the service interface. SRPInterface = BasicSRPInterface ->GetSRPInterface("wrapandroid","root","123"); // set not check password flag for share library. SRPInterface ->CheckPassword(VS_FALSE);
- 创建根 Activity。
共享库需要一个预先创建的 Activity。以下代码用于创建它。然后我们为 Activity 的
getCurrent
函数编写存根代码。这样就可以从共享库调用此函数。这是
getCurrent
的存根函数void *RootActivity; static void *ActivityClass_getCurrent(void *Object){ return RootActivity; }
创建根 Activity 的代码
//---get ActivityClass defined in wrapandroid service description file. void *ActivityClass = SRPInterface -> GetObjectEx(NULL,"ActivityClass"); //---change to atomic object void *AtomicActivityClass = SRPInterface ->ObjectToAtomic(ActivityClass); //---create function stub for getCurrent void *AtomicActivityClassFunction_getCurrent = SRPInterface ->CreateAtomicFunctionSimple( AtomicActivityClass,"getCurrent","VS_OBJPTR getCurrent();",NULL,NULL,VS_FALSE,VS_FALSE); //---set the function address SRPInterface -> SetAtomicFunction(AtomicActivityClassFunction_getCurrent,(void *)ActivityClass_getCurrent); //alloc root activity RootActivity = SRPInterface ->MallocObjectL(&VSOBJID_ActivityClass,0,NULL);
- 加载共享库。
加载共享库很简单。我们调用 CLE 接口
DoFile
函数来完成此操作,更像是脚本接口。if( SRPInterface ->DoFile("","../libcode.dll",NULL, NULL, VS_FALSE) == VS_FALSE ){ printf("load library file\n"); return -1; }
编译时,应将 starlib_vcm.lib 添加到项目中,如下所示。
将头文件添加到项目中
在 code.cpp 上设置断点,然后运行。
为 Android 类创建存根
我们为 Android 类创建存根是为了调试或其他目的。这将使我们能够在 Windows 平台上调试 Android C++ 代码。我们如何创建存根?很简单,就像这样。
定义函数
static void *ActivityClass_getCurrent(void *Object){
…
}
创建存根函数
void *AtomicActivityClassFunction_getCurrent =
SRPInterface ->CreateAtomicFunctionSimple(AtomicActivityClass,
"getCurrent","VS_OBJPTR getCurrent();",NULL,NULL,VS_FALSE,VS_FALSE);
并将地址分配给存根函数
SRPInterface -> SetAtomicFunction(AtomicActivityClassFunction_getCurrent,(void *)ActivityClass_getCurrent);
如果您为 Android 类的所有函数创建了存根,您的应用程序就可以迁移到 Windows 或其他平台。