Sign in Join
Wiki copytemporary

WinCE Programming Tips: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: WinCE Programming Tips

OldNewLine
1+## TIPS / FAQ
2+
3+### The error message: SomeProject is not a valid Windows CE application
4+
5+This is a generic error message from Windows CE which can be caused by one of the following reasons:
6+
7+- The executable was compiled for a different Operating System, for example desktop Windows
8+- The executable was compiled for a different architecture then the device has
9+- One of the DLLs which the device requires could not be found. Usually this is either a database dll or aygshell.dll. See further in this FAQ about those
10+- The binary is too big and doesn't fit the available RAM
11+
12+Unfortunately Windows CE does not give specific error messages in this case, so one should simply test all hypotheses.
13+
14+### Application runs on Windows Device Emulator, but not on physical device
15+
16+When running a compiled application on the Windows Device emulator, it works fine, but running it on the physical device you get the error:
17+
18+*Cannot find 'project1' (or one of its components).*
19+
20+Possible causes are:
21+
22+#### Missing aygshell.dll
23+
24+This error is usually indicative of missing DLLs on the target device, especially if you have a very simple "Hello World" type of application. In many instances, it is related to the aygshell.dll file, which is not present on many industrial-type devices running a bare Windows CE version - normally devices running "Windows Mobile" versions of Windows CE will not have this problem.
25+
26+This problem has been reported on Motorolla/Symbol MC9000 and MC1000 barcode scanners running both Windows CE 4.2 and 5.
27+
28+To resolve, do a search for "aygshells.zip" in a search engine - there are some "Dummy" aygshell.dll files available that can be copied to the device to overcome this problem.
29+
30+Here is a list of forum topics where people had trouble with aygshell.dll and found a solution:
31+
32+- <http://forum.lazarus.freepascal.org/index.php/topic,7161.0.html>
33+- <http://forum.lazarus.freepascal.org/index.php/topic,5524.0.html>
34+
35+And here a number of DLLs to substitute missing ones and make applications work:
36+
37+- <http://hpcmonex.net/izemize.htm>
38+
39+#### Wrong architecture
40+
41+Maybe your device runs a x86 or mips processor instead of the more common ARM.
42+
43+#### Missing functions due to Operating System version
44+
45+Possibly your operating system is very old and doesn't have some functions required by Lazarus.
46+
47+### Get Device ID
48+
49+Get and ID of your device useful for protect your application. This work only on Windows Mobile 5.0 and Windows CE 5.1
50+
51+``` pascal
52+
53+function GetDeviceUniqueID(AppData:LPCWSTR; cbApplictionData:Integer; dwDeviceIDVersion:Integer;
54+ var deviceIDOuput; var pcbDeviceIDOutput:DWORD):Integer; external 'coredll.dll' name 'GetDeviceUniqueID';
55+
56+function GetDeviceID: string;
57+var
58+ AppData: array[0..19] of WideChar;
59+ DeviceID : array[0..19] of Byte;
60+ Count: DWORD;
61+ s: string;
62+ Res, i:Integer;
63+begin
64+ //not sure about Unicode
65+ AppData := Utf8Decode('MY_SIG');//any string you like
66+ Count := SizeOf(DeviceID);
67+ FillChar(DeviceID, Count, #0);
68+ Res := GetDeviceUniqueID(AppData, SizeOF(AppData), 1, DeviceID, Count);
69+ if Res = 0 then
70+ begin
71+ Result := '';
72+ for i := 0 to Count -1 do
73+ begin
74+ if (i > 0) and ((i mod 2) = 0) then
75+ Result := Result + '-'; //add space make the string wrap in label
76+ Result := Result + IntToHex(DeviceID[i], 2);
77+ end;
78+ end
79+ else
80+ Result := '';//error accord
81+// you can MD5 it with your string
82+// Result := MD5Print(MD5Buffer(DeviceID, Count));
83+end;
84+```
85+
86+Reference pages: <http://msdn2.microsoft.com/en-us/library/ms893522.aspx> <http://peterfoot.net/RetrieveIMEIThroughTAPI.aspx> <http://blogs.msdn.com/jehance/archive/2004/07/12/181067.aspx>
87+
88+### Get Device Name
89+
90+Easy to get it from registry
91+
92+``` pascal
93+function GetDeviceName: string;
94+var
95+ aReg:TRegistry;
96+begin
97+ aReg := TRegistry.Create(KEY_READ);
98+ try
99+ aReg.RootKey := HKEY_LOCAL_MACHINE;
100+ aReg.OpenKey('Ident', False);
101+ if aReg.ValueExists('Name') then
102+ Result := aReg.ReadString('Name')
103+ else
104+ Result := 'GUEST';
105+ finally
106+ aReg.Free;
107+ end;
108+end;
109+```
110+
111+### Show/Hide SIP Panel
112+
113+SIP: Software Input Panel button, it is a keyboard come with WinCE for touch screen devices.
114+
115+``` pascal
116+const
117+ //some of consts already found in Windows
118+ SIPF_OFF = $00000000;
119+ SIPF_ON = $00000001;
120+ SIPF_DOCKED = $00000002;
121+ SIPF_LOCKED = $00000004;
122+
123+function SipShowIM(IPStatus:DWORD):Integer; stdcall; external 'coredll.dll' name 'SipShowIM';
124+
125+begin
126+ SipShowIM(SIPF_ON)
127+end;
128+```
129+
130+Microsoft documentation for the SipShowIM routine: [1](http://msdn.microsoft.com/en-us/library/ms941818.aspx)
131+
132+### Wakeup Device/ Power On
133+
134+If you like to make alarm application this function make your device power on, you need also make some sounds with it.
135+
136+``` pascal
137+function SetSystemPowerState(psState: PWideChar; StateFlags: DWORD; Options : DWORD):DWORD;
138+ stdcall; external 'coredll.dll' name 'SetSystemPowerState';
139+
140+ SetSystemPowerState(nil, POWER_STATE_ON, POWER_FORCE);
141+ Application.BringToFront;
142+ ShowWindow(Handle, SW_SHOW);
143+```
144+
145+### LED / Vibrator
146+
147+You can turn on/off then LED/vibrator in, your device, it worked for me but not as like as i want, may be it need some improvements.
148+
149+``` pascal
150+const
151+ NLED_COUNT_INFO_ID = 0;
152+ NLED_SUPPORTS_INFO_ID = 1;
153+ NLED_SETTINGS_INFO_ID = 2;
154+
155+type
156+ TNLED_COUNT_INFO = record
157+ cLeds: DWORD;
158+ end;
159+
160+ TNLED_SETTINGS_INFO = record
161+ LedNum: DWORD; // LED number, 0 is first LED
162+ OffOnBlink: Integer; // 0 = off, 1 = on, 2 = blink
163+ TotalCycleTime: DWORD; // total cycle time of a blink in microseconds
164+ OnTime: DWORD; // on time of a cycle in microseconds
165+ OffTime: DWORD; // off time of a cycle in microseconds
166+ MetaCycleOn: Integer; // number of on blink cycles
167+ MetaCycleOff: Integer; // number of off blink cycles
168+ end;
169+
170+ function NLedGetDeviceInfo(nID:Integer; var pOutput): WordBool;
171+ stdcall; external 'coredll.dll' name 'NLedGetDeviceInfo';
172+ function NLedSetDevice(nID: Integer; var pOutput): WordBool;
173+ stdcall; external 'coredll.dll' name 'NLedSetDevice';
174+```
175+
176+Examples
177+
178+``` pascal
179+function TForm1.MakeLEDOn;
180+var
181+ Countnfo: TNLED_COUNT_INFO;
182+ Info:TNLED_SETTINGS_INFO;
183+begin
184+ NLedGetDeviceInfo(NLED_COUNT_INFO_ID, Countnfo);
185+ //with Countnfo.cLeds you can check if your device support LEDs;
186+ Info.LedNum := 0; //<--- First LED
187+ Info.OffOnBlink := 1;
188+ Info.OffTime := 0;
189+ Info.MetaCycleOff:= 50;
190+ Info.MetaCycleOn:= 50;
191+ Info.TotalCycleTime := 100;
192+ NLedSetDevice(NLED_SETTINGS_INFO_ID, Info);
193+end;
194+
195+procedure TForm1.MakeLedOff;
196+var
197+ Info:TNLED_SETTINGS_INFO;
198+begin
199+ Info.LedNum := 0;
200+ Info.OffOnBlink := 0;
201+ NLedSetDevice(NLED_SETTINGS_INFO_ID, Info);
202+end;
203+```
204+
205+Vibrator it is the last LED in your device, if you can write some music you can now make your phone dance.
206+
207+``` pascal
208+function TForm1.MakeVibratorOn;
209+var
210+ Countnfo: TNLED_COUNT_INFO;
211+ Info:TNLED_SETTINGS_INFO;
212+begin
213+ NLedGetDeviceInfo(NLED_COUNT_INFO_ID, Countnfo);
214+ Info.LedNum := Countnfo.cLeds -1;
215+ Info.OffOnBlink := 1;
216+ NLedSetDevice(NLED_SETTINGS_INFO_ID, Info);
217+end;
218+
219+function TForm1.MakeVibratorOff;
220+var
221+ Countnfo: TNLED_COUNT_INFO;
222+ Info:TNLED_SETTINGS_INFO;
223+begin
224+ NLedGetDeviceInfo(NLED_COUNT_INFO_ID, Countnfo);
225+ Info.LedNum := Countnfo.cLeds -1;
226+ Info.OffOnBlink := 0;
227+ NLedSetDevice(NLED_SETTINGS_INFO_ID, Info);
228+end;
229+```
230+
231+### Getting Battery Status
232+
233+For more information : [MSDN GetSystemPowerStatusEx](http://msdn.microsoft.com/en-us/library/aa453172.aspx)
234+
235+``` pascal
236+//by Philip Heinisch
237+ type
238+ TBAT_INFO = record
239+ ACLineStatus:byte; //0=Offline, 1=Online, 2=Backup Power,3= Unknown status
240+ BatteryFlag:byte; //0=High, 1=Low, 2=Critical, 3=Charging, 4=No Battery, 5=Unknown
241+ BatteryLifePercent:byte; //0..100 Battery Life in Percent
242+ Reserved1:byte; //always 0
243+ BatteryLifeTime: DWORD; //remaining time in seconds
244+ BatteryFullLifeTime: DWORD; //max usage time in seconds
245+ Reserved2:byte; //always 0
246+ BackupBatteryFlag:byte; //0=High, 1=Low, 2=Critical, 3=Charging, 4=No Battery, 5=Unknown
247+ BackupBatteryLifePercent:byte; //0..100 Backup Battery Life in Percent
248+ Reserved3:byte; //always 0
249+ BackupBatteryLifeTime: DWORD; //remaining time in seconds
250+ BackupBatteryFullLifeTime: DWORD; //max usage time in seconds
251+ end;
252+
253+ function GetSystemPowerStatusEx(var pOutput;fUpdate:boolean ): WordBool;
254+ stdcall; external 'coredll.dll' name 'GetSystemPowerStatusEx';
255+
256+//Usage Example:
257+
258+function getbat : byte;
259+var
260+ batinfo: TBAT_INFO;
261+begin
262+if GetSystemPowerStatusEx(batinfo,True) then getbat:=batinfo.BatteryLifePercent
263+else getbat:=255; //255=Function Call Failed
264+end;
265+```
266+
267+### Prevent Phone from Entering in Standby Mode
268+
269+Inside a timer event add the following line of code:
270+
271+``` pascal
272+keybd_event(VK_F24, 0, KEYEVENTF_KEYUP or KEYEVENTF_SILENT, 0);
273+```
274+
275+### Installation of an app build with Lazarus on a WinCE device
276+
277+The simplest way to install an app is to simply copy the executable and other files it needs, but a proper installer can be made with the instructions bellow: 1) one needs a inf file (maybe Lazarus could generate one on his own some time...). A good brief on how to create one can be found here: <http://web.archive.org/web/20080205125046/http://www.sundialsoft.freeserve.co.uk/cabinfo.htm> (original website is dead) I used the following (only for ARM prozessors):
278+
279+``` ini
280+[Version] ; Required section
281+Signature = "$Windows NT$"
282+Provider = "bilettiX"
283+CESignature = "$Windows CE$"
284+
285+[CEDevice.ARM]
286+ProcessorType = 2577 ; processor value for ARM
287+
288+[DefaultInstall.ARM]
289+CopyFiles = Files.ARM
290+
291+[Files.ARM]
292+bilettixscan.exe,,,0
293+sqlite3.dll,,,0
294+
295+[DefaultInstall] ; Required section
296+CEShortcuts = Shortcuts.All
297+AddReg = RegData
298+
299+[SourceDisksNames] ; Required section
300+1 = ,"common files",,C:\Dokumente und Einstellungen\xyz\Dev;A existing folder on your HD, where the common files to be copied are found (not processor specific)
301+
302+[SourceDisksNames.ARM]
303+2 = ,"ARM files",,arm;folder below the common files folder above for ARM specific files
304+
305+[SourceDisksFiles] ; Required section, application binary, files to copy
306+bilettixscan.EXE = 2
307+sqlite3.DLL = 2
308+bilettix.db = 2
309+
310+[DestinationDirs] ; Required section
311+Shortcuts.All = 0,%CE11%
312+DefaultDestDir = 0,%InstallDir%
313+
314+[CEStrings] ; Required section
315+AppName = Ticket Validator
316+InstallDir = %CE1%\%AppName%
317+
318+[Shortcuts.All]
319+%AppName%,0,bilettixscan.exe
320+
321+[RegData]
322+HKLM,Software\bilettix\%AppName%,MajorVersion,0x00010001,1
323+HKLM,Software\bilettix\%AppName%,MinorVersion,0x00010001,0
324+HKLM,Software\bilettix\%AppName%,Installpath,0x00000000,%InstallDir%
325+```
326+
327+2\) now create a cab file out of your files using this inf file by using cabwiz.exe from the platform SDK of MS 3) generate a install.ini - my looks like this (icon-section is nor really needed...):
328+
329+``` ini
330+[CEAppManager]
331+Version = 1.0
332+Component = Ticket Validator
333+
334+[Ticket Validator]
335+Description = bilettiX Ticket Validator
336+Uninstall = bilettiXscan
337+DeviceFile = bilettiXscan.exe
338+IconFile = bilettix.ico
339+IconIndex = 0
340+CabFiles = bilettiXscan.ARM.CAB
341+```
342+
343+4\) download EzSetup for free from here: <http://www.spbsoftwarehouse.com/products/ezsetup/index.html>
344+
345+5\) generate the installer using the ini-file, a readme and a eula file you generated as txt files
346+
347+6\) done :-)
348+
349+be sure to have ActiveSync installed. double-click the installer and have fun :-)
350+
351+### Debugging with a log file
352+
353+Windows CE doesn't ship with a command line, so people used to command-line log debugging may have trouble. In some versions of Windows CE it is possible to install a command-line, but another solution for this is using the logging routines from the LCLProc unit in Lazarus to write log information to a file in the same directory as the executable and then read it, as in the code bellow.
354+
355+``` pascal
356+uses LCLProc;
357+
358+DbgAppendToFile(ExtractFilePath(ParamStr(0)) + '1.log', 'Some text');
359+```
360+
361+The unit LCLProc also contains other cool routines for debugging, like GetStackTrace, which returns a string with the stack trace.
362+
363+### Current Directory and Placement of DLLs
364+
365+Windows CE does not have the concept of current directory, so paths to files should always be complete and never relative to the executable path.
366+
367+As one exception to this rule, DLLs can be placed in the same folder as the executable and they will be loaded from there. Another suitable location for DLLs is the \Windows directory.
368+
369+### Going Full Screen
370+
371+Making an application Fullscreen requires removing both the bottom menu bar and the top taskbar
372+
373+#### Removing the top taskbar
374+
375+This code should remove the taskbar by passing False to the parameter of this function:
376+
377+Note that if you don't provide a quit button it will be then impossible to close the application and that if the application crashes, the taskbar will not be restored automatically. Rebooting the device will restore it.
378+
379+``` pascal
380+uses Windows, aygshell;
381+
382+procedure TForm1.RemoveTaskbar(AFullScreen: Boolean);
383+const
384+ MENU_HEIGHT = 26;
385+var
386+ rc: TRect;
387+begin
388+ if AFullScreen then
389+ begin
390+ GetWindowRect(Handle, @rc);
391+
392+ SHFullScreen(Handle, SHFS_HIDETASKBAR);
393+
394+ MoveWindow(Handle,
395+ rc.left,
396+ rc.top-MENU_HEIGHT,
397+ rc.right,
398+ rc.bottom+MENU_HEIGHT,
399+ TRUE);
400+ end
401+ else
402+ begin
403+ GetWindowRect(Handle, @rc);
404+ SHFullScreen(Handle, SHFS_SHOWTASKBAR);
405+ MoveWindow(Handle,
406+ rc.left,
407+ rc.top+MENU_HEIGHT,
408+ rc.right,
409+ rc.bottom-MENU_HEIGHT,
410+ TRUE);
411+ end;
412+end;
413+```
414+
415+#### Removing the Virtual Keyboard
416+
417+One can remove the keyboard with SHFS_HIDESIPBUTTON, like this:
418+
419+``` pascal
420+uses Windows, aygshell;
421+
422+procedure TForm1.RemoveVirtualKeyboard(ARemove: Boolean);
423+begin
424+ if ARemove then SHFullScreen(Handle, SHFS_HIDESIPBUTTON)
425+ else SHFullScreen(Handle, SHFS_SHOWSIPBUTTON);
426+end;
427+```
428+
429+#### Alternative way with SHFullScreen
430+
431+This is a different solution for going Fullscreen. Make sure that you have included the windows unit to your program (uses windows;) In the INTERFACE section of your program paste the following code:
432+
433+``` pascal
434+const
435+ //ShFullScreen
436+ SHFS_SHOWTASKBAR = $01;
437+ SHFS_HIDETASKBAR = $02;
438+ SHFS_SHOWSIPBUTTON = $04;
439+ SHFS_HIDESIPBUTTON = $08;
440+ SHFS_SHOWSTARTICON = $10;
441+ SHFS_HIDESTARTICON = $20;
442+
443+ function SHFullScreen(hwndRequester: hWnd; dwState: DWord): WINBOOL;
444+ stdcall; external 'aygshell.dll' name 'SHFullScreen';
445+```
446+
447+Then in your Forms, OnCreate or OnShow (better on the OnShow) event add the following code:
448+
449+``` pascal
450+procedure TForm1.FormCreate(Sender: TObject);
451+var
452+ Rect:TRect;
453+ hTaskBar:THandle;
454+ menuh:Integer;
455+begin
456+ hTaskBar := FindWindow('HHTaskBar','');
457+ GetWindowRect(hTaskBar,rect);
458+ menuh:=Rect.Bottom-Rect.Top;
459+ GetWindowRect(Form1.Handle,Rect);
460+ SHFullScreen(Form1.Handle,SHFS_HIDETASKBAR or SHFS_HIDESTARTICON or SHFS_HIDESIPBUTTON);
461+ movewindow(Form1.Handle,Rect.Left,Rect.Top-menuh,Rect.Right,Rect.Bottom+menuh,True);
462+end;
463+```
464+
465+### Rotating the Screen
466+
467+There is a Microsoft tutorial with code in C here about this: <http://msdn.microsoft.com/en-us/library/ms812499.aspx> Bellow is code equivalent to that tutorial, but in Pascal.
468+
469+#### Getting the Current Display Settings
470+
471+To obtain the current display settings, pass the ENUM_CURRENT_SETTINGS constant in the iModeNum parameter to the EnumDisplaySettings API, as illustrated by the following Pascal code.
472+
473+``` pascal
474+var
475+ dm: DEVMODE;
476+begin
477+ FillChar(dm, #0, SizeOf(dm));
478+ dm.dmSize := sizeof(dm);
479+
480+ if 0 <> EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, @dm) then
481+ begin
482+ // inspect the DEVMODE structure to obtain details
483+ // about the display settings such as
484+ // - Orientation
485+ // - Width and Height
486+ // - Frequency
487+ // - etc.
488+ end;
489+```
490+
491+#### Enumerating All Supported Display Settings
492+
493+To enumerate all display settings supported by the current display device pass zero in the iModeNum parameter to the EnumDisplaySettings API and then continue calling it with incremented iModeNum values until the function returns zero, as shown in the following Pascal code.
494+
495+``` pascal
496+var
497+ index: Integer = 0;
498+ dm: DEVMODE;
499+begin
500+ FillChar(dm, #0, SizeOf(dm));
501+ dm.dmSize := sizeof(dm);
502+
503+ while 0 <> EnumDisplaySettings(NULL, index, @dm) do
504+ begin
505+ // inspect the DEVMODE structure to obtain details
506+ // about the display settings such as
507+ // - Orientation
508+ // - Width and Height
509+ // - Frequency
510+ // - etc.
511+ Inc(Index)
512+ end;
513+```
514+
515+#### Changing Display Settings
516+
517+To change the display settings pass in a pointer to a valid DEVMODE structure to the ChangeDisplaySettings API. The following Pascal code demonstrates how to rotate the screen orientation clockwise by 90 degrees. Note that this code will only work with devices that support the respective display settings. It is important to obey the return value of the ChangeDisplaySettings API as some operations may require the computer to be restarted in order for the graphics mode to work.
518+
519+``` pascal
520+var
521+ dm: DEVMODE;
522+ dwTemp: Cardinal;
523+ lRet: Integer;
524+begin
525+ FillChar(dm, #0, SizeOf(dm));
526+ dm.dmSize := sizeof(dm);
527+
528+ if (0 <> EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, @dm)) do
529+ begin
530+ // swap height and width
531+ dwTemp := dm.dmPelsHeight;
532+ dm.dmPelsHeight:= dm.dmPelsWidth;
533+ dm.dmPelsWidth := dwTemp;
534+
535+ // determine new orientaion
536+ case (dm.dmDisplayOrientation) of
537+ DMDO_DEFAULT: dm.dmDisplayOrientation := DMDO_270;
538+ DMDO_270: dm.dmDisplayOrientation := DMDO_180;
539+ DMDO_180: dm.dmDisplayOrientation := DMDO_90;
540+ DMDO_90: dm.dmDisplayOrientation := DMDO_DEFAULT;
541+ else
542+ // unknown orientation value
543+ // add exception handling here
544+ end;
545+ lRet := ChangeDisplaySettings(@dm, 0);
546+ if (DISP_CHANGE_SUCCESSFUL <> lRet) then
547+ begin
548+ // add exception handling here
549+ end;
550+ end;
551+```
552+
553+## Pre-compiled Windows CE Libraries/Utils
554+
555+### Pre-compiled sqlite dll
556+
557+You can find sqlite3.dll pre-compiled for Windows CE [here](ftp://ftp.freepascal.org/pub/fpc/contrib/arm-wince-sqlite322.zip).
558+
559+And newer ones can be found here: <http://www.parmaja.com/downloads/sqlite3/>
560+
561+### OpenSSL for Windows CE
562+
563+It is pretty hard to find a good binary OpenSSL library for Windows CE these days, so good versions which dont depend on any wierd, unusual library, are hosted here:
564+
565+<https://sourceforge.net/projects/p-tools/files/OpenSSL%20for%20Windows%20CE/>
566+
567+They were obtained from here: <http://q3.snak.org/en/old.html> and hosted on the link above in case this page goes offline
568+
569+## See also
570+
571+Here are some links that might be useful for creating Windows CE interfaces.
572+
573+- [[Windows CE Development Notes]]
574+- [[KOL-CE|WinCE port of KOL GUI library]]
575+
576+---
577+*From the Free Pascal wiki, page "WinCE Programming Tips", as it stood on 2020-08-09 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history