![]() |
I have written "WAVEFORMAT_MIDI_MESSAGE, the missing example", an article on how to use the WAVEFORMAT_MIDI_MESSAGE to preview MIDI files on smartphone.
If you have a questions post on this thread at modaco or on microsoft.public.smartphone.developer.
I will try and keep checking in every few days.
If you have a correction, email me (Riki):
| g4mb17 | @ | hotmail_dot_com |
Now I have written this myself and havn't had anybody check my grammer, spelling or technical accuracy, sorry folks! So consider it a work in progress.
Welcome! Welcome all to my finishing hints. These are practical smartphone specific hints I have learned while working on various smartphone projects. Most of them are geared towards taking an application that "works for me" to something one could sell. The article assumes the reader has good knowledge of CE and MFC/WTL
As most people know, the first thing to do is Find-n-Replace all references to "\IPSM\" (Intel Persistent Storage Memory) and "\Storage\" to SHGetSpecialFolderPath(). If you are using a path it doesn't have try this:
CString GetPSM()
{
static CString psm;
#ifndef x86
if (psm.IsEmpty()) {
// get the PSM name
SHGetSpecialFolderPath(0, psm.GetBuffer(MAX_PATH), CSIDL_STARTUP, 0);
psm.ReleaseBuffer();// - \IPSM\windows\startup
psm = psm.Left(psm.ReverseFind('\\')); // - \IPSM\windows
psm = psm.Left(psm.ReverseFind('\\')+1);// - \IPSM\ -
}
return psm;
#else
if (psm.IsEmpty()) {
SHGetSpecialFolderPath(0, psm.GetBuffer(MAX_PATH), CSIDL_STARTUP, 0);
psm.ReleaseBuffer();
if (psm.Left(9) == L"\\Storage\\") // WM2003SE
psm = L"\\Storage\\";
else // WM2002/WM2003
psm = L'\\';
}
return psm;
#endif
}
Also I have found people like to store large data files on their storage card, so you might want to look for files on there at the same time you looking for files on the internal store; eg:
SetList_helper(L"\\Windows\\", theme, Themename, filename, s, h, h1); SetList_helper(GetPSM() + L"Application Data\\Home\\", theme, Themename, filename, s, h, h1); SetList_helper(GetStorage() + L"Application Data\\Home\\", theme, Themename, filename, s, h, h1);
So you will need a GetStorage() function
#include "projects.h"
CString GetStorage()
{
static CString s;
if (s.IsEmpty()) {
WIN32_FIND_DATA fd;
HANDLE h = FindFirstFlashCard(&fd);
if (h != INVALID_HANDLE_VALUE) {
CString s2 = GetPSM();
do {
CString s1 = CString(L'\\') + fd.cFileName + L'\\';
if (s1 != s2)
s = s1;
} while (FindNextFlashCard(h, &fd) && s.IsEmpty());
FindClose(h);
} else {
CRegKey key;
if (key.Open(HKEY_LOCAL_MACHINE, L"\\System\\StorageManager\\Profiles\\MMC\\") == ERROR_SUCCESS) {
DWORD sz = 0;
if (key.QueryValue(0, L"Folder", &sz) == ERROR_SUCCESS) {
key.QueryValue(s.GetBuffer(sz), L"Folder", &sz);
s.ReleaseBuffer();
}
key.Close();
}
}
}
return s;
}
You will need to link against note_prj.lib
Thankfully nowadays there is plenty of information on this, such as this excellent article by James Pratt
But I will present the practical side of getting into privileged mode on an unlocked phone.
There are two options OEMs have for unlocked phones, the one tier model, where an unsigned app is run in priviledged mode, or two tier where it is run in unpriviledged mode.
Should your app find itself on a 2 tier phone heres what you do:
To get into priviledged mode you will need the priviledged mode development certificate from running
"Windows CE Tools\wce420\SMARTPHONE 2003\Tools\spdps.exe" or "Windows CE Tools\wce300\Smartphone 2002\tools\sp2002dps.exe" with the /create flag
(you should have done this to sign the development tools device files). If you have a etrust or verisign account, then sign the .cab, and setup.dll with it,
then sign your application with your development certificate.
Firstly you need to fill out the template called "AddCert.XML" in your SDK tools directory:
To get the thumbprint and encoded certificate
open Internet Explorer 6, go to the "Tools" menu, then select "Internet Options" then the "Content" tab then click the "Certificates" button.
From here you need to export your privileged development certificate, so click "Export" then select the "Don't export private" then export as "Base64 Encoded"
in the exported .cer file you will find you certificate.
Now to get the thumbprint, from the Certificates dialog you opened above, double click on your development certificate then select the "Details" and click on the "Thumbprint" item.
Remember to remove ALL whitespace from your certificate and thumbprint when filling out your XML, or it won't work (thats caught a few people, and I should know!)
Then simply get the cab wizard to run this xml at the end of the install process like so:
If your application is really that good sooner or later someone will want it in another language. So your going to have to do some internationalisation (i18n). This means two main things, firstly make sure you are calling GetDateFormat(), GetTimeFormat(), GetCurrencyFormat() and GetNumberFormat(). Secondly translations.
To prepare for your translation dll you are going to have to move all those hardcoded strings into the string table and use LoadString()s. Yes its a big hassle.
To create your translation dll simply split all your resources (.rc files) into a resource dll. I recommend creating a sub directory in your project, call it say "MyAppEnUS"
(where En is short for English) and I also recommend renaming your .rc file too, eg MyAppResourcesEnUS.rc.
Then in your Link Properties, name this dll "myapp.exe.0409.mui" where "myapp.exe" is, of course, the name of your application.
and 0409 is the hex number of the language, in this case US English.
Now your ready for your first translation. Firstly you will need a list of your strings, menus and widget's. Here is a tool I prepared earlier: DumpStrings. You might need to edit it to skip over dialog resources that have strings which aren't shown. Just create a new MFC project using VC++ and drop that file in. It will generate a text file, such as MyAppResourcesEnUS.rc.txt which you can send off to your translator.
Now for the UK<->US translation (And in the scheme of things it's one of the easiest.), open DumpStrings output file in MS Word and do a Select All then change the dictionary (Tools->Language on Word '97). bingo. it will now show you any color<->colour spelling differences. (Note: this is quite a silly translation, personally I conditionally load strings from the same English .mui file at run time depending on the language, saves space)
To create a new resource dll, firstly copy your old resource directory, rename it, then rename the .rc file, and lastly the project (.vcp) file.
Secondly open your project file up in a text editor and search-n-replace
all occurrences of your old language id (0409) to your new language id (0809), and search-n-replace all instances of your old name (MyAppResourcesEnUS) to your new name (MyAppResourcesEnUK).
Thirdly load the .rc file up in a text editor and make changes from your translation. This step could be automated, but its tricky.
For more information search for MUI in the SDK help
I have found sometimes my .mui wasn't being loaded, so I popped this code in:
HINSTANCE h = 0;
unsigned short buf[80];
// sometimes Windows fails to load the resource dll
if (LoadString(hInstance, IDS_HOMESCREEN, buf, 80) == 0) {
CString s;
s.Format(L"ThemeChanger.exe.%.4x.mui", GetUserDefaultUILanguage());
h = LoadLibrary(s); // try ourselves
// default to english
if (h == NULL) h = LoadLibrary(L"ThemeChanger.exe.0409.mui");
if (h == NULL) MessageBox(0, L"Failed to load resources", L"ThemeChanger",
MB_ICONERROR|MB_OK); // oh bugger!
_Module.Init(NULL, h);
} else
_Module.Init(NULL, hInstance);
ASSERT(LoadString(_Module.GetResourceInstance(), IDS_HOMESCREEN, buf, 80) != 0);
For IDS_HOMESCREEN just put in the first string you come across in your project, its just checking if the .mui was loaded
Most programs have a brand name which is kept the same over all languages, if you want to translate the name, and by extenation the link in the start menu you have two options, the most obvious is to create the shortcut in your setup.dll based on the current language, the only problem
being if the user changes the language then the shortcut won't change.
Microsoft uses another cunning method: "mxip_lang.vol". In this Database volume there are three databases, the one we are interested in is "\FileNameLang".
In this database is a list of filenames (well Start Menu links and .mid filenames to be exact), with a translation to each language. For example:
Each item in brackets is the Property ID and the following value is the Property Name. For those not familiar with CE Databases think Column name and Cell value.
So hows it work? 0001 is the filename, and the following numbers (0809 et. al.) are the language ID (note the order, its important).
To read from this database simply use SHGetFileInfo(FileName, 0, &in, sizeof(in), SHGFI_DISPLAYNAME); in.szDisplayName will contain the translated filename.
To add your own values, well thats a bit more tricky. Firstly you need to be in privileged mode (see above). You can check with this:
bool IsPrivMode()
{
static int privmode = -1;
if (privmode == -1)
privmode = CeGetCallerTrust() == OEM_CERTIFY_TRUST;
return (privmode == 1 ? true : false);
}
Mmkay, then you look to see if your entry is already there. I look for L"Theme Changer". If its not then we add a new Record. I only add Danish, US English and Italian. Which means if the phone is in any other language SHGetFileInfo, and by extension the Start Menu won't translate the link name. Note to use this example you'll need GetPSM from this webpage.
void AddThemeChangerTranslation(CEGUID &vol, CEOID &ce)
{
LPBYTE lpBuffer = NULL;
HANDLE hDataBase, hHeap = 0;
CEOID CeOid;
WORD wcPropID;
DWORD dwcbBuffer;
hDataBase = CeOpenDatabaseEx (&vol, &ce, 0, 0, CEDB_AUTOINCREMENT, NULL);
if (hDataBase == INVALID_HANDLE_VALUE)
return;
bool found = false;
int i = 0;
while (!found && (CeOid = CeReadRecordPropsEx (hDataBase, CEDB_ALLOWREALLOC,
&wcPropID, NULL, &lpBuffer,
&dwcbBuffer, hHeap)) != 0) {
int c = wcPropID;
CEPROPVAL * vals = (PCEPROPVAL) lpBuffer;
for (int j = 0; j < c && !found; j++) {
if (LOWORD(vals[j].propid) == CEVT_LPWSTR) {
if (HIWORD(vals[j].propid) == 0x0001 &&
lstrcmp(vals[j].val.lpwstr, L"Theme Changer") == 0) {
found = true; // its already in there!
break;
}
}
}
}
LocalFree(lpBuffer);
if (!found) {
CEPROPVAL val[4];
// make sure the language ids are in the same order as MS does it!
val[0].propid = MAKELPARAM(CEVT_LPWSTR, 0x0001);
val[1].propid = MAKELPARAM(CEVT_LPWSTR, 0x0406); //Da
val[2].propid = MAKELPARAM(CEVT_LPWSTR, 0x0409); //En
val[3].propid = MAKELPARAM(CEVT_LPWSTR, 0x0410); //It
val[0].wFlags = val[1].wFlags = val[2].wFlags = val[3].wFlags = 0;
val[0].val.lpwstr = L"Theme Changer";
val[1].val.lpwstr = L"TemaSkifter";
val[2].val.lpwstr = L"ThemeChanger";
val[3].val.lpwstr = L"CambiaTema";
CeOid = CeWriteRecordProps(hDataBase, 0, sizeof(val)/sizeof(val[0]),
val);
/*if (CeOid)
MessageBox(0, L"write name ok", L"", 0);*/
}
CloseHandle(hDataBase);
}
void AddXlation()
{
CEGUID vol;
CREATE_INVALIDGUID(&vol);
CString psm = GetPSM();
BOOL res = CeMountDBVol(&vol, (psm + L"mxip_lang.vol").GetBuffer(0),
OPEN_EXISTING );
// if GetLastError() == 5 then we arent in privileged mode
if (CHECK_INVALIDGUID(&vol) || res == FALSE)
return;
CEOID ce;
HANDLE h = CeFindFirstDatabaseEx( &vol, 0 );
if (h != INVALID_HANDLE_VALUE) {
ce = CeFindNextDatabaseEx( h, &vol );
while (ce) {
CEOIDINFO inf;
CeOidGetInfoEx(&vol, ce, &inf);
if (lstrcmp(inf.infDatabase.szDbaseName, L"\\FileNameLang") == 0)
AddThemeChangerTranslation(vol, ce);
ce = CeFindNextDatabaseEx( h, &vol );
}
CloseHandle(h);
}
CeUnmountDBVol(&vol);
}
The setup dll is run on install/uninstall and can preform handy things; I use the uninstall fuction to remove entries from databases (such as those added above). But the most handy of all, is to close my application.
Now I was silly and made my main window a Dialog, so I can't find it by class, I have to use the title, which has been translated, so:
void CloseThemeChanger()
{
HWND h = ::FindWindow(0, L"ThemeChanger");
if (!h) h = ::FindWindow(0, L"TemaSkifter");
if (!h) h = ::FindWindow(0, L"CambiaTema");
if (h) SendMessage(h, WM_COMMAND, IDCANCEL, 0);
}
Many a time have a gone to install a new version and the phone wanted to reboot because I didn't close an old version in the background - handtastic!
For those wanting to know more about writing setup.dlls see the Samples/Win32/Setupdll directory of the 2003 SDK.
According to the Pragmatic Programers, Andrew Hunt and David Thomas, "DRY" or Don't Repeat Yourself. In other words (theirs as it happens) "Every piece of knowledge must have a single, unambiguous, authoritave representation within a system". So to follow this I have a single place for my version numbers, "majorminor.h":
#define MAJOR 3 #define MINOR L"5"
And build.h:
#define BUILD 1025
From this I set the numbers in my about box, like so:
CString s;
int len = ::GetWindowTextLength(GetDlgItem(IDC_STATIC1));
if (::GetWindowText(GetDlgItem(IDC_STATIC1), s.GetBuffer(len), len)) {
s.ReleaseBuffer();
CString r;
r.Format(L"%d", MAJOR);
s.Replace(L"major", r);
r.Format(L"%s", MINOR);
s.Replace(L"minor", r);
::SetWindowText(GetDlgItem(IDC_STATIC1), s);
}
where the window text of STATIC1 is "Theme Changer vmajor.minor\n© Riki June 2004".
Plan B would be to make the string "Theme Changer v%d.%s\n© Riki June 2004" then use it like:
t.Format(s, MAJOR, MINOR);
To keep my build number up to date I wrote a quick-n-dirty utility which I run in the Project->build->Post-build step. I found the macro commonly available on the 'net that runs on Application_BeforeBuildStart was too slow.
CFile f;
if (f.Open("Build.h", CFile::modeReadWrite | CFile::shareDenyNone)) {
CString s;
char * ptr = s.GetBuffer(80);
int i = 0;
int end = f.Read(ptr, f.GetLength());
ptr[end+1] = 0;
s.ReleaseBuffer();
i = 0;
while (s.GetAt(i) < '0' || s.GetAt(i) > '9' && i < s.GetLength()) ++i;
if (i == s.GetLength()) {
MessageBox(0, "Could not find build number", "BuildBoInc",
MB_OK | MB_ICONERROR);
return 0;
}
int buildno = atoi(&s.GetBuffer(0)[i]);
++buildno;
CString new_s = s.Left(i);
CString builds;
builds.Format("%d", buildno);
new_s += builds + "\n";
f.SetLength(new_s.GetLength());
f.SeekToBegin();
f.Write(new_s.GetBuffer(0), new_s.GetLength());
f.Close();
} else
MessageBox(0, "Failed to open Build.h", "BuildBoInc", MB_OK | MB_ICONERROR);
And as you will see in the build section the name of my setup is extracted from build.h too
My basic build.bat file looks like this:
I copy the built files over, note don't do this in the Post-Build step of EVC as the files won't be signed. I then build my cab manually, which I find is the most powerful.
ezsetup is a setup exe creater which is free, if you don't need to install files on the PC I highly recommend it, it creates exe's MUCH smaller than commercial offerings I
have used.
Lastly I zip it up using pkzip 2.50 (which has long filename support) and its ready for uploading to the web.
and tagnzip looks this this:
The native windows ports of these UNIX tools (tr, grep, and cut) are availble here: UnxUtils. Getting a commandline version of pkzip 2.50+ is a bit trickier, as I don't believe its available now.
My .inf file looks like this:
it's all pretty standard, note the shortcut which points to the loader. If your having trouble with it there are plenty of helpful pages on the web.
And lastly my .ini file
Again run of the mill.
Lastly I include some tips and tricks I've picked up, most people won't need them, but from time to time they might answer someones question, so keep them in mind, you might use them one day.
A common use for this is (well for me anyways) loading XML files.
"So, why not just use the WTL CString?"
Unfortunaly CString uses mbstowcs(), which doesn't handle UTF8, so you need to break out WideCharToMultiByte(). Now (and how many hours did it take for me to discover this!) the 2002 documentation is wrong, as it is missing out the fact XToY() functions support the CP_UTF8 flag, this oversite is corrected in the 2003 documentation, but if your program has to work on 2002 then likely you are using the 2002 help.
So you will need something like this:
CString s;
int len = strlen(tbuf);
::MultiByteToWideChar(CP_UTF8, 0, tbuf, -1, s.GetBuffer(len+1), len+1);
s.ReleaseBuffer();
And to pull it back again... Remember a Multibyte (UTF8) string can be longer (in characters) than the unicode, so its a touch more tricky:
// example use: char * file = NULL; ConvertToChar(cfile, &file);
// or you can allocate file using new char[] if you like.
// either way remember to delete[] it
void ConvertToChar(CString cfile, char** file)
{
// and back to UTF8
unsigned int needed = WideCharToMultiByte(CP_UTF8, 0, cfile.GetBuffer(0),
cfile.GetLength(), 0, 0, 0, false);
if (!*file || strlen(*file) < needed) {
if (*file) delete [] *file;
*file = new char [needed + 2];
ASSERT(*file != 0);
} else
needed = strlen(*file);
needed = WideCharToMultiByte(CP_UTF8, 0, cfile.GetBuffer(0),
cfile.GetLength(), *file, needed, 0, false);
(*file)[needed] = 0;
}
Tragicly the good 'ol GetSysColor() doesn't define all the colours available in the home screens XML file. Bugger. SO this means if you want to do anything tricky you have to role up your sleeves and get your hands dirty with the internal colour table. The big problem here is this changes from version to version, for example COLOR_GRADLEFT. If you still really need to get at the colour table here is a quick function I have rolled:
// AHHHHHHHHHHHHHHHHHHHHHH
// These colours aren't defined in WINUSER.H
// So we have to do some registry voodoo
// Close your eyes!
#define COLOR_INTGRADLEFT 15
#define COLOR_INTGRADRIGHT 16
#define COLOR_HIGHGRADLEFT 17
#define COLOR_HIGHGRADRIGHT 18
#define COLOR_ALERTTITLE 29
#define COLOR_ALERTWINDOW 30
#define COLOR_ALERTRULE 31
#define COLOR_GRADLEFT_03 36
#define COLOR_GRADRIGHT_03 37
#define COLOR_GRADLEFT_02 13
#define COLOR_GRADRIGHT_02 14
// thanks to Peter K for these:
#define SHCOLORTABLE_COLOR_HOMETEXT (32)
#define SHCOLORTABLE_COLOR_HOMERULE (33)
#define SHCOLORTABLE_COLOR_HOMEHIGHLIGHTTEXT (34)
#define SHCOLORTABLE_COLOR_HOMEHIGHLIGHT (35)
COLORREF GetColour(int colourenum)
{
HKEY key;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Color", 0, 0,
&key) != ERROR_SUCCESS)
return 0x00ff00fe;
// size buffer
DWORD count, type;
RegQueryValueEx(key, L"SHColor", 0, &type, 0, &count);
if (count < unsigned int(colourenum) / 4) {
RegCloseKey(key);
return 0x00ff00fe;
}
DWORD * data = new DWORD[count];
if (RegQueryValueEx(key, L"SHColor", 0, &type,
reinterpret_cast<unsigned char*>(data), &count)
!= ERROR_SUCCESS)
return 0x00ff00fe; // nothing against hot pink personally
// lookup colour
DWORD colour = data[colourenum];
delete [] data;
RegCloseKey(key);
return colour;
}
If its missing an index you need you'll need to mess around with a home screen layout (in \IPSM\Application Data\Home\*.home.XML) and set the colour you want to something crazy (umm hot pink) then look at the registry entry (I recommend using the remote registry viewer) and find out its index. Also note you will need to check it's the same over 2002/2003.
And while we are here:
bool is2002()
{
OSVERSIONINFO info;
GetVersionEx(&info);
return info.dwMajorVersion == 3;
}
Personally I like having an icon in my about box, like so:

So I don't have another copy of it in every resource dll, I put a empty static Picture in my dialog in the resource dll, and put the icon in my exe.
In WinMain I save the hInstance:
g_Inst = hInstance;
Lastly in my about dialog's WM_INITDIALOG I load it and set it like so:
HANDLE ico = LoadImage(g_Inst, MAKEINTRESOURCE( IDI_ICON1 ), IMAGE_ICON, 32, 32, 0 );
if (ico) {
::SendMessage(GetDlgItem(IDC_STATICICON), STM_SETIMAGE, IMAGE_ICON, (LPARAM)ico);
DestroyIcon((HICON)ico);
}
Nothing to tricky here, or smartphone specific, but STM_SETIMAGE wasn't the first thing I looked up when I was try to get it working :-)
Ever wondered why the PHM registry editor crashes when searching the registry?
Not only do non-privileged applications not have write access to alot of the registry, trying to open some keys (in HCLM\Drivers\PCMCIA\MOTOROLA-POWER_14.4-8980\Unimodem HCLM\Comm\L2TP?, HCLM\Comm\AsyncMac?, and HCLM\Comm\PPTP?) will lock CE up on some phones! so when iterating through the registry DO NOT even open keys requiring trusted access if you are not in privileged mode!
Larger applications (I found around the 410k mark plus 28k for the .mui) will fail to load from the internal store of some phones. This is a big problem. It's due to a bug in the Intel Stratoflash driver shipped to OEM's, which causes garbage to be loaded into your applications memory space. There is a new driver available to OEM's but application developers have no way of applying it, or even detecting if a phone has it.
SO if you have a large application and it suddenly stopped loading on some phones...
What to do? Well I just removed code that wasn't needed. Plan B is to copy your application to RAM using a loader, and run it from there. Afterwards delete it (IMPORTANT or the phones gonna run out of memory!).
Riki June is currently working for EmbeddedFusion and lives in Auckland, New Zealand. I wrote Sprite Backup (version 1) and ThemeChanger. I have been working on MS Smartphone since the 'ol SC1100 developer models were the only ones available.