[Java] 透過SWIG 從JNI (C/C++) callback 回 Java

最近寫 Android,一般 Java 層可以透過 JNI 呼叫 C/C++ 撰寫的 .so 檔,但是要從 C/C++ 呼叫回 Java 層呢!? 一般需要 JNIEnv 這個特殊的變數,才可以透過它來呼叫到 Java Library。

不過今天這邊要介紹的是另外一個方法 - 透過 SWIG 的 directors 的 feature
SWIG 可以將 C++ 的 class 包裝成一個 Java layer 的 class ,而 Java layer 可以去繼承 Java wrap C++ 的 class,然後 override 它的 virtual function 後,C/C++ layer 呼叫到這個 virtual function 時,就會被轉 call 到 Java layer了。

[emomeDDSMS] 下載emome上的備份簡訊 更新0.74

emome更新

好久沒更新了,最近要備份才發現網頁更新了 XD,就順手改了一下
還得要抓到舊版的 Firefox 3.6 才行,還好 chickenfoot 的連結還在...
下載連結(按右鍵另存新檔):emomeDDSMS 0.74 (2013/12/20)

使用說明,請參考前一篇文章。

[JNI] 在C/C++中透過 SWIG 取得 JNIEnv or JavaVM

在 C/C++ layer 裡,想要 call Java layer 的 function 的話,適必需要 JNIEnv or JavaVM 這兩個 instance pointer,而透過 SWIG 包裝起來的 C/C++ layer 是不能直接接觸到 JNI layer,必須要一些手法才行。

[C/C++] 在 Windows 上 Build static libCURL library

libcurl 是個跨平台的 library ,支援非常多的 protocol,重點還是 open source!
以下就是簡單的 build 出一個 libcurl 的 static library 教學

1. 首先 download libcurl 的 source code 壓縮檔

libCURL download page

2. 在 command line 準備好 VC 的 compile 環境

"{VC Install Path}\VC\vcvarsall.bat" x86
設置完環境後,才能執行 namke

3. 開始 build libcurl

cd {libcurl extract dir}\winbuild
nmake /f Makefile.vc mode=static DEBUG=yes MAXHINE=x86
mode = static 也可以是 dll ,只是我們目標是 static library
build 完後,可以在 {libcurl extract dir}\builds 找到 lib 還有 header 檔

其他參數說明
  • VC=<6,7,8,9,10>
    • VC版本(不一定要填)
  • WITH_SSL=<dll or static>
    • 使用OpenSSL詳細參閱,會有 License 問題
  • ENABLE_WINSSL=<yes or no>
    • Enable native 的 Windows SSL 功能
    • 記得,如果要 enable 的話,ENABLE_SSPI一定也要 enable !
  • GEN_PDB=<yes or no>
    • 產生 .pdb 檔
  • DEBUG=<yes or no>
    • Debug or Release mode
  • MACHINE=<x86 or x64>
    • Target architecture
  • RTLIBCFG
    • 不指定就是 default,compile 參數會加上 /MD or /MDd (Multi-threaded)
    • =static,compile 參數會加上 /MT or /MTd (Multi-threaded DLL)

4. VC project setting 設置

Project Property | Linker | General | Additional Library Directories 
加上放有 build 出的 .lib 的目錄

Project Property | Linker | Input | Additional Dependencies 
加上 libcurl_a.lib or libcurl_a_debug.lib

Project Property | C/C++ | Preprocessor 
加上 CURL_STATICLIB 這個 macro 才行
否則會出現類似這樣的錯誤訊息:
1>A.obj : error LNK2019: unresolved external symbol __imp__curl_global_init referenced in function "bool __cdecl DownloadProtectedKey(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?DownloadProtectedKey@@YA_NAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@11@Z)
1>A.obj : error LNK2019: unresolved external symbol __imp__curl_slist_append referenced in function "bool __cdecl DownloadProtectedKey(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?DownloadProtectedKey@@YA_NAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@11@Z)
1>A.obj : error LNK2019: unresolved external symbol __imp__curl_slist_free_all referenced in function "bool __cdecl DownloadProtectedKey(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?DownloadProtectedKey@@YA_NAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@11@Z)
1>A.obj : error LNK2019: unresolved external symbol __imp__curl_easy_strerror referenced in function "bool __cdecl DownloadProtectedKey(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?DownloadProtectedKey@@YA_NAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@11@Z)
1>A.obj : error LNK2019: unresolved external symbol __imp__curl_easy_init referenced in function "bool __cdecl DownloadProtectedKey(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?DownloadProtectedKey@@YA_NAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@11@Z)
1>A.obj : error LNK2019: unresolved external symbol __imp__curl_easy_setopt referenced in function "bool __cdecl DownloadProtectedKey(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?DownloadProtectedKey@@YA_NAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@11@Z)
1>A.obj : error LNK2019: unresolved external symbol __imp__curl_easy_perform referenced in function "bool __cdecl DownloadProtectedKey(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?DownloadProtectedKey@@YA_NAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@11@Z)
1>A.obj : error LNK2019: unresolved external symbol __imp__curl_easy_cleanup referenced in function "bool __cdecl DownloadProtectedKey(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?DownloadProtectedKey@@YA_NAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V12@11@Z)


[Linux/Android] Export API for SharedLibrary (.so)

當開發一個 SharedLibrary (.so) 時,不想 Export 太多 API 出去(可能涉及商業機密或是單純的爽字),Windows or Linux 都有做法可以做到,這邊只說明一下 Linux / Android 的做法。

用 nm 觀察 .so 的 Symbol Table

nm -gC MyLib.so
  • -g : 只列出 external symbol
  • -C : 將 C++ 的 symbol 轉換成易讀的版本

[Android] 在 C Level 用 dlopen 使用 第三方的 Shared Library (.so)

在 Android 裡,撰寫 JNI C Level 的 code ,不管是想要 load 自己寫的獨立 shared library module (.so),或是想要把第三方的 shared library 拿來用,都必須要多做一些步驟才能達成。


[Android] NDK Debug 與 breakpoint 失效的解決之道

在 Android 開發環境裡,新版的 NDK (目前為 r9 )提供的 GDB Debug C//C++環境其實已經算是很友善的了。不像以前需要下一堆的 command line 才行。

目前我的開發環境:

  • ADT - adt-bundle-windows-x86_64-20130729
  • NDK - android-ndk-r9


簡單說一下 Debug NDK 的步驟

1. Eclipse Preferences

設定  Eclipse Preference | Android | NDK | NDK Location 
填入正確的 NDK 下載路徑

2. 設定 Project property NDK_DEBUG=1

 Project Property | C/C++ Builder | Builder Settings 
取消 Use default build command的選項
輸入 ndk-build NDK_BUILD=1
註:Application.mk or Android.mk 都無需修改,新版的 ADT/NDK 都已經 handle 好其他的設定了

3. 設好中斷點

在 Eclipse 裡的 C/C++ code,設好中斷點,等著上鉤

4. Debug As "Android Native Application"

由於目前 ADT 在 Debug 時,同一時間只能選擇 Debug Java or C/C++
所以如果 Debug As "Android Application"的話,就是 Debug Java level
如果 Debug As "Android Native Application"的話,就是 Debug C/C++ level


* 問題 *

Debug 時設中斷點,可能會出現類似這樣的Error

  • No symbol table is loaded.  Use the "file" command.

[Win32] 在C/C++中偵測精準的程式時間(Performance Time)

在Windows底下,要偵測精準的Perfromance Time,可以透過QueryPerformanceFrequency() & QueryPerformanceCounter() 來取得。

由於取得的時間存在LARGE_INTEGER的structure,要取出來計算所花費的時間需要多點程式碼才行。這邊寫了一個簡單的Wrapper Class,讓這個AutoQueryPerformance的instance life-time結束後,自行dump出所花費的時間。

AutoQueryPerformance

#include "windows.h"

// Auto dump the life-time of AutoQueryPerformance
// Ex:
// {
//     AutoQueryPerformance autoQuery("Test", true);
//     ...
// } // it will dump message when @autoQuery destroy
class AutoQueryPerformance
{
public:
    AutoQueryPerformance(const char* info, INT loop_count=1, BOOL autoDump=TRUE)
        : m_count_time(0), m_loop_count(loop_count), m_auto(autoDump)
    {
        m_info = info;
        ::ZeroMemory(&m_cpu_freq, sizeof(LARGE_INTEGER));
        ::QueryPerformanceFrequency(&m_cpu_freq);

        if( autoDump )
            begin();
    }
    ~AutoQueryPerformance()
    {
        pause();
        if( autoDump )
            printf("[AutoQueryPerformance][%s] spend time=%.4f", m_info.c_str(), getTime());
    }
    inline void begin()
    {
        ::ZeroMemory(&m_start_clock, sizeof(LARGE_INTEGER));;
        ::QueryPerformanceCounter(&m_start_clock);
    }
    inline void pause()
    {
        ::ZeroMemory(&m_cur_clock, sizeof(LARGE_INTEGER));
        ::QueryPerformanceCounter(&m_cur_clock);
        m_count_time += m_cur_clock.QuadPart-m_start_clock.QuadPart;
    }
    inline void stop()
    {
        pause();
        m_count_time = 0;
    }
    inline float getTime()
    {
        return (m_count_time/(float)m_cpu_freq.QuadPart) / m_loop_count;
    }
private:
    LARGE_INTEGER m_cpu_freq;
    LARGE_INTEGER m_start_clock;
    LARGE_INTEGER m_cur_clock;
    LONGLONG      m_count_time;
    INT           m_loop_count;//for for-loop to calculate correct average time
    std::string   m_info;
    BOOL          m_auto;
};
#define AUTO_QUERY_PERFORMANCE(x) AutoQueryPerformance __at_qp = AutoQueryPerformance(x);


使用範例

void TestingTotalTime(int loop_count)
{
    AutoQueryPerformance ap("TestingTotalTime");
    int sum = 0;
    for(int i=0 ; i<loop_count; i++)
       sum += i;
}

void TestingSingleTime(int loop_count)
{
    AutoQueryPerformance ap("TestingSingleTime", loop_count);
    for(int i=0 ; i<loop_count; i++)
       //HeavyFunction call here;
}

Reference

[Win32/COM] Use COM DLL without registered

依照Windows COM元件的使用方法,應該要將COM DLL註冊後,即可在程式中,以CoCreateInstance()帶入CLSID、IID就可以將該DLL自動load起來,創建該class的instance了。

但是在開發時,實際上總是沒這麼美好,註冊DLL其實是很麻煩的,新舊相容性,開發中的版本等等的問題,而走manifest其實可以避開註冊這條路,但是今天我需要寫一個COM module讓別人使用,而這個COM module在AP裡是走manifest的,所以不會被註冊,連帶的module會用到其他的Library也不走註冊這條路,而因為一些其他的原因,也不能將此Library寫入manifest裡,只能尋求其他途徑了。

由於COM的DLL都會link到一些基本的COM Library,所以是有一些export的API可以繞過註冊來create COM instance的,直接參考以下的Sample Code吧。


// {599D2F5F-BA8D-4009-941C-A5393EBD5C58}
DEFINE_GUID(CLSID_MyCOM, 
0x599d2f5f, 0xba8d, 0x4009, 0x94, 0x1c, 0xa5, 0x39, 0x3e, 0xbd, 0x5c, 0x58);

// {4C452CB2-469C-4236-AE5C-F48A28EC0870}
DEFINE_GUID(IID_IMyCOM, 
0x4c452cb2, 0x469c, 0x4236, 0xae, 0x5c, 0xf4, 0x8a, 0x28, 0xec, 0x8, 0x70);

HRESULT CreateInstance(IMyCOM** pMyCom)
{
    HMODULE hDll = LoadLibrary("MY_COM.dll");//Need refine by your self
    if(hDll)
    {
        //////////////////////////////////////////////////////////////////////////
        // Initial COM instance without register
        //   Try to load COM DLL directly, and call DllGetClassObject to get IID_IClassFactory
        //   And Create COM instance by IID_IClassFactory
        // -----------------------------------------------------------------------
        //   HRESULT hr = CoCreateInstance(CLSID_MyCOM, 
        //                                 NULL, 
        //                                 CLSCTX_INPROC_SERVER, 
        //                                 IID_IMyCOM, 
        //                                 (PVOID*)&pMyCom);
        //////////////////////////////////////////////////////////////////////////
        CComPtr<IClassFactory> spFactory;

        // Get address of DllGetClassObject exported method
        PDLLGETCLSOBJ pDllGetCLSObj = (PDLLGETCLSOBJ)GetProcAddress(hDll, "DllGetClassObject");

        // Call DllGetClassObject and get the proper IClassFactory interface
        HRESULT hr = pDllGetCLSObj(CLSID_MyCOM, IID_IClassFactory, reinterpret_cast<PVOID*>(&spFactory));
        if( FAILED(hr) )
        {
            printf(__FUNCTION__" [Err] Can't get spFactory hr=%X", hr);
            return E_FAIL;
        }

        // Create an instance of IMyCOM
        hr = spFactory->CreateInstance(NULL, IID_IMyCOM, (PVOID*)pMyCom);
        if( FAILED(hr) )
        {
            printf(__FUNCTION__" [Err] Init IMyCOM Fail! hr=%X", hr);
            return E_FAIL;
        }
    }
    else
    {
        return E_FAIL;
    }
    return S_OK;
}


Reference:

[Python] 使用objgraph找出memory leak(circular reference)

在Python的世界裡,所有的東西都是Object,而控制Object的life cycle靠的就是reference的機制,一個簡單的assignement (=等號),或是function call就會讓Object的reference count自動加1。
而當Object的reference count減至0的時候,Python的GC會找時間把這些Object清除掉。

對Python而言,memory leak產生的可能性有兩種

  • Circular reference
  • Referece在global的變數或是module上

[Jython] Use JNA Read/Write Windows Registry

最近用Sikuli開發,Run Script時需要讀寫Windows的Registry,由於Sikuli script是用Jython的,而Jython裡並不像Python支援Read/Write Windows Registry,所以必須用一些其他的Library才行

而其中一個方法就是用JNA
下載最新的JNA與JNA-Platform
放到要執行的Script旁,或是放到Jython的目錄裡

以下的範例,將jna-3.5.1.jar與jna-platform-3.5.1.jar放到Sample.py旁,以Jython執行
Sample.py

import os
import sys
folder = os.path.dirname(os.path.abspath(__file__))

# load JavaLib jna/platform
sys.path.append(os.path.join(folder, 'jna-3.5.1.jar'))
sys.path.append(os.path.join(folder, 'jna-platform-3.5.1.jar'))

# Read
from com.sun.jna.platform.win32 import Advapi32Util, WinReg
path = Advapi32Util.registryGetStringValue( \
    WinReg.HKEY_LOCAL_MACHINE, 
    r'Software\CyberLink\ColorDirector2', 
    'InstallPath' )

print path.encode(sys.getfilesystemencoding())


Reference

[Python] Windows底下 使用SWIG呼叫C/C++的function

使用Python的script language的特性,開發起來實在是又快又舒服,但是Python有個致命的缺點就是GIL,在Multi-Thread的機制下,有了這個GIL的限制,感覺Multi-Thread就被俺掉一半了。照理來說heavy的blocking code應該要寫在另一條thread,並且放掉GIL,讓其他的thread有時間去做事才對。透過Python轉call進C/C++的function後,才有機會讓heavy的function放掉Python的GIL。

不過,本篇的重點在於介紹如果讓Python呼叫C/C++ level的function,有機會的話,再介紹一下GIL好了。目前其實有很多Tool提供這樣的功能,包括SWIG、Boost.Python、Robin…等等,這邊就介紹一下SWIG的用法。

[PHP] Debug PHP by Eclipse with PDT PlugIn

強大的Eclipse開發社群,提供了PHP的debug功能 - PHP Development Tool (PDT) PlugIn,可以執行並下中斷點,對PHP開發者而言著實是一大福音!

PDT套件可以視為是debug的client端,而server端的話,就需要在PHP安裝extension才行,目前PDT支援的包括(1)Zend Debugger (2)XDebug,以下就介紹PDT以Zend Debugger的方式進行debug ...


[Sikuli] Compile Sikuli-IDE source code in Eclipse

Sikuli是新堀起的Programming Language,以圖象化的方式來coding真的是一大圖破創新的想法。重點是...還是台灣人做的呢!Sikuli是Open Source的,所以有不少人為這個Project供獻一己之力,最近公司的Project用它來做AutoTest,整個很給力,決定來compile它的source code來玩看看,雖然說很久沒寫Java了.... XD

Sikuli的Project切成兩部分,一個是整個核心的Sikuli-API是以Java、C++為主,另一個是開發平台的Sikuli-IDE是純Java的Project,由於整個核心API切出來成一個單獨的Project,所以如果想自己寫一個Java的程式直接使用Sikuli-API是可行的!詳見:How to use Sikuli Script in your Java Program?

也由於Sikuli API模組化的關係,目前Sikuli Script語法是Python,以後可能還會陸續支援其他語言吧。

[C/C++] 研究long long * float overflow 可能導致的誤差

最近看bug看到一個奇怪的現象,在64bit的AP執行時,long long * float會有誤差出現,但是32bit的AP卻沒有這樣子的問題出現,直覺上不是應該32bit的AP比較會有問題嗎!?

先來看一下 long long 跟 float ,long long的size是8個byte,float的size是4個byte
long long * float,compiler會將結果存到float的temp變數裡,此時誤差就可能會出現了,但是這個誤差是「有條件」地出現,並不是任何long long的數字乘上float都會產生誤差的

以一個簡單的程式來驗證是否會產生誤差

bool check_longlong(long long v1)
{
    float f = 1.0f;
    long long v2 = long long( v1 * f );
    printf("ori value=%lld, new value=%lld, ori==new=%d", v1, v2, v1==v2);
    return v1==v2;
}

int _tmain(int argc, _TCHAR* argv[])
{
    check_longlong(0xF0000000000);//true - OK
    check_longlong(0x0FFFFFFFFFF);//false - Error!
    check_longlong(0x0FFFFFF);//true - OK
    check_longlong(0x1FFFFFF);//false - Error!
    return 0;
}


[C/C++] 使用STL時,混用Debug & Release Mode的DLL or LIB可能會造成 Crash!

當你的Release mode的EXE,使用Debug mode的DLL,中間如果有操作(new/delete)STL的container,很容易讓AP crash,包括下列的error ...

  • Debug Assertion Failed
  • _BLOCK_TYPE_IS_VALID
  • _CrtIsValidHeapPointer

[Python] 為Python2.5 unittest 加上setUpClass & tearDownClass 的功能

最近使用 Sikuli 替Project作AutoTest的功能,以Python的unittest為架構下去撰寫,發現有些綁手綁腳的地方,因為Sikuli是使用Jython 2.5,連帶的unittest也止於Python 2.5的版本。由於測試的項目眾多,需要撰寫多個TestCase,所以setUpClasstearDownClass更顯得重要,可以為每個TestCase做pre-process與post-process。偏偏setUpClass與tearDownClass是在Python 2.7後才支援的,所以想在Sikuli裡套用的話,必須自己加一些Code才行了。

[C/C++] 取得LIB目前的DLL module path

如果寫了一個LIB or Cpp檔,讓多個DLL module include使用,該如果拿到目前DLL的module path?

GetModuleFileName() 參數hModule傳NULL的話,只能知道執行檔exe的路徑
要想知道自己在哪個DLL module的話,必須埋一些code才行

EXTERN_C IMAGE_DOS_HEADER __ImageBase
//...
WCHAR dll_path[MAX_PATH] = {0};
GetModuleFileNameW((HINSTANCE)&__ImageBase, dll_path, _MAX_PATH);

Reference :

[C/C++] General pointer type for support x86 & x64

一個pointer的size在x86跟x64的環境裡是不一樣大的

  • x86's pointer size = 4 bytes
  • x64's pointer size = 8 bytes
如果你在程式中用int or long去儲存一個pointer的話,可能在compile成x86可以work,但是compile成x64執行的話,可能就會exception了

所以建議是用標準的 uintptr_t / intptr_t 去儲存pointer,uintptr_t在copmpile成x86時的size是4 bytes,x64是8 bytes,它在C++11時才被列入標準,但在大部分的C++03的compiler已經支援了

include header : <cstdint>

Reference : 

[wxWidgets] function 傳入wxString的不定參數

在C/C++裡有不定參數的功能,讓一個function可以傳入不固定數目的參數,在function裡,透過va_list, va_arg() 把參數一個個抓出來。

在wxWidgets裡,擁有特有的字串類別wxString,用法跟std::string, std::wstring稍稍不同(多了很多API)。

std::string/std::wstring可以透過c_str()取得的內部字串buffer (char*/wchar_t*)
wxString也可以透過c_str()取得內部字串的buffer,型別是wxChar

wxChar是wxWidgets定義的,會根據compile定義的wxUSE_UNICODE(是否為unicode的build)分別對應到unicode : wchar_t, ansi : char

由於wxString是wxWidgets包裝起來的class,若將它傳進function當不定參數之一,va_arg()將無法取得wxString正確的字串內容,所以如果要將wxString的字串傳入不定參數的function中,需以c_str()傳入wxChar*字串的pointer,才能在function中取得字串的內容。

// the parameter must be wxChar*
// -> please convert wxString by c_str() before push it
//    _T("") would be convert to wxChar*
// Ex: wxString s = _T("Falldog");
//     ComposeContent(2, s.c_str(), _T("'s blogger"));
wxString ComposeString(int count, ...)
{
    wxString res;
    va_list vl;
    va_start(vl, count);
    for(int i=0 ; i<count ; ++i)
        res += va_arg(vl,wxChar*);
    va_end(vl);
    return res;
}