Wednesday, December 26, 2007
Call to IDirectDrawSurface::Blt returns E_INVALIDARG

While possible causes for this error are numerous, I wanted to point out a specific one that is not mentioned anywhere: either source or destination rectangle is empty ( zero width or height )

12/26/2007 1:25:03 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 

It's been a while. Things were a bit hectic. Nevertheless I am back.

These days I am working on a rather interesting project involving DirectDraw on Windows Mobile 6. Without going into the project details, I wanted to share some experiences. So far, a number of time I would run into an issue, and of course not it would not be explained/documented/covered anywhere where the Google search takes you. It struck me that I must be not the first one to hit these issues, and if I list them here, chances are that Google search will be more productive for the next poor sod to plow through underdocumented (to put it mildly), convoluted (again, to put it mildly) and unforgiving API such as DirectDraw. I am going to have multiple posts with keywords facilitating search. If a month from now you will find out that I produced all of one post (this one seems to be on track so far. Chances for it not making it are slim), don't judge me too hard. I am away from home for the 3rd week in a row, having missed Christmas, but should be back for the New Year. Ok, here goes.


Call to IDirectDrawSurface::Blt returns DDERR_SURFACEBUSY

The documentation says to check for other threads accessing your surface at the same time. The group search suggests to search for mismatching Lock/Unlock calls. The actual cause turned to be a missed call to IDirectDrawSurface::ReleaseDC (after a successfull call to IDirectDrawSurface::GetDC). If you think about it, internally these calls must be using Lock/Unlock, so cudos to Group search and boo to MSDN documentation

 

12/26/2007 1:20:39 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Friday, June 01, 2007

I was quite surprised to learn that apparently I have missed an important new feature of Windows Mobile 6 (Professional)- inking and ink serialization support. Gone are the days when a developer had to tinker with oh-so-temperamental InkX control. Now everyone and his brother can take advantage not only of high-quality precision ink support with smoothing and serialization, but also of handwriting recognition built into Windows Mobile 6 Professional. To quote the documentation - "It provides a rich inking experience, through high quality curve–fitted ink with anti-aliasing, transparent ink, and highlighter ink. It provides an API for Ink collection, data management, rendering, and recognition. It also provides Ink controls to support the note–taking scenario."

Another important feature is interoperability and serialized data format compatibility with ink support on Tablet PC.

While all of this is nice, there is slight bit of bad news - using this rich set of goodies requires C++. There are 2 ways to use ink in Windows Mobile 6 - InkCanvas control and an COM automation library. InkCanvas control offers the ability to use a regular Win32 control (similar to InkX) to write, highlight, collect ink etc via a set of Windows messages. COM automation library on the other hand allows accessing the entire set of features offered by WISP. And there is no managed wrapper for the time being.

As a public service, we at OpenNETCF.com are proud to offer WISPLite managed wrapper. The wrapper offers the entire WISPLite functionality (although not every method of every interface has been tested). There is InkControl class, which wraps InkCanvas, and a OpenNETCF.WindowsMobile.Ink namespace that contains imported COM interfaces. Some of the interfaces do not wrap cleanly, so a bit of coding is needed.

Here is what the demo app looks like (warning, before running it, change line 132 in Form1.cs to be

inkControl1.SetPenStyle((float)trackBar1.Value, penColor, penType); )

Saving ink data to a file:

using (SaveFileDialog fd = new SaveFileDialog())
{
  fd.Filter =
"Ink files (*.isf)|*.isf|All files (*.*)|*.*";
 
if (fd.ShowDialog() == DialogResult.OK)
  {
   
byte[] data = (byte[])inkControl1.GetInkData(IC_INKENCODING.BINARY);
    FileStream stm = File.OpenWrite(fd.FileName);
    stm.Write(data, 0, data.Length);
    stm.Close();
  }
}

Getting ink as bitmap and retrieving recongnition result:

pbPreview.Image = inkControl1.GetInkDataAsBitmap();
lblReco.Text = inkControl1.RecognizedText;

In conclusion I'd like to ask to report problems with this wrapper to this blog's comments.

6/1/2007 2:20:22 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [3]  | 
 Tuesday, April 17, 2007

Sometimes when trying to connect Visual Studio t a device, especially to a custom CE platform, it is helpful to see if there are any problems reported by the CoreCon components on the device side. CoreCon components (ConmanClient, trasnport DLLs, edbgtl.dll and edm.exe) all have an integrated logging facility, which can be used by a developer for troubleshooting.

To enable Corecon debug log you can set the following under HKLM\Software\Microsoft\VSD\Logging

VSD_LogEnabled: DWORD:1,0

VSD_LogToDebugger: DWORD:1,0

VSD_LogToConsole: DWORD: 1,0

VSD_LogToFile: DWORD:1,0

VSD_LogLevel: DWORD - set to at least 4, up to 9

VSD_LogFile: REG_SZ (default VSDLogFile.txt)

Keep in mind that the ConMan log can be quite chatty, so enable it sparingly and only when needed

4/17/2007 12:12:44 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Sunday, February 25, 2007

Raffaele Rialdi pointed out that attempting to run RTF Host from Compact Framework 3.5 Power Toys on an emulator produces the following error:

This is caused by RTF getting confused because the default CoreCon transport on the emulator is DMA (DeviceDMA.dll). Here are the steps to add emulator as a manual Tcp connection. Not that many will need it, but it is useful for a demo

1. Start Emulator using Device Emulator Manager.
2. Configure Network and Storage card folder
3. Copy to storage card folder the following file: "C:\Program Files\Common
Files\microsoft shared\CoreCon\1.0\Target\wce400\armv4i\TCPConnectionA.dll"
4. In File ExplorerNavigate to \Windows\Corecon1.1. If you don't see it
there, connect to the emulator from Studio
5. Launch ClientShutdown (you should see a guid-named folder to appear)
6. Copy \Storage Card\TcpConnectionA.dll to \Windows
7. Go to \Windows and delete DeviceDMA.dll (if you can't, you forgot to
launch ClientShutdown)
8. Go back to \Windows\Corecon1.1 and launch ConmanClient.exe
9. Launch RTF Host. Enjoy

2/25/2007 6:45:36 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Monday, October 02, 2006

I have a lot of respect for MSDN technical articles. They are more often than not a source of knowledge one would be hard pressed to obtain from official documentation. Kraig Brockschmidt's treatise on OLE internals, Nancy Winnick Clutz explanation of TAPI, Icon internals by John Hornick - all of these are precious gems of knowledge. Heck, I even wrote a few myself. Over the years there were some that are brilliant and concise, other that were less interesting. Almost every article there had some code posted with it. The code samples would also vary in quality, but not so much as to raise an eyebrow.

On several occasions I had this conversation with my teenage daughter, the gist of which was that even though the Algebra lesson is not an English lesson, it does not mean she can disregard the grammar completely, while doing her math homework. Apparently it was not obvious to her, that writing properly is not something you do only when you absolutely have to. Similarly, I suppose when you write code, even throwaway code, you should still be conscious of how you do it.

When I first came across this, my first reaction was - this is good stuff. It'll teach developers not to use an old, outdated control and show, how to replace it with alternative modern controls. And then I saw the following gem:

If you must search compiled code, you can look certain patterns that represent the GUIDs. For example, the following GUID:

{ABCDEFGH-IJKL-MNOP-QRST-UVWXYZ012345}

becomes the following hexadecimal sequence in binary:

GH EF CD AB KL IJ OP MN QR ST UV WX YZ 01 23 45

 

Er, what hexadecimal sequence?
But following it was a code sample

// Compile and execute:  "FindGUIDs YourApplication.exe"
using System;
using System.Text;
using System.IO;

namespace FindGUIDs {
class Program {
  static void Main(string[] args) {
    FileStream    fs = File.OpenRead(args[0]);
    StringBuilder sb = new StringBuilder();
    do {
      Int32 b = fs.ReadByte();
      if (-1 == b) {
        break;
      }
      sb.AppendFormat("{0:X2}", b);
    } while (true);
    fs.Close();
    String s = sb.ToString();
    if (s.Contains("E0A58D4371F1D011984E0000F80270F8"))
      Console.Out.WriteLine("GUID for TriEditDocument Class detected.");
    if (s.Contains("DFA58D4371F1D011984E0000F80270F8")) {
      Console.Out.WriteLine(
        "GUID for ITriEditDocument Interface detected.");
      }
      if (s.Contains("0002362DF5FFd1118D0300A0C959BC0A")) {
        Console.Out.WriteLine("GUID for DHTMLEdit Class detected.");
      }
      if (s.Contains("91B504CE1F2Bd2118D1E00A0C959BC0A")) {
        Console.Out.WriteLine("GUID for IDHTMLEdit Interface detected.");
      }
    }
  }
}

Basically, what's happening here is that the application is trying to find occurences of a GUID in a binary file. I've seen many approaches to searching a binary string in a file. Some were simpler to implement, the other are more efficient, but harder to understand. This one takes the cake. In a nutshell, this code reads a binary file, byte by byte, and converts each byte into its string hexadecimal represenation. Then this string is appended to a StringBuilder. Once the entire file is loaded into StringBuilder (consuming filesize * 4 bytes of memory), the StringBuilder is used to produce a string (another memory allocation of the same size edited: no, this is actually done in place. Thanks, Dunkan!) and the string is being searched for a GUID substring.

I won't even go into the efficiency of string search as used above. My point is that you either do things right, or you sidestep the whole issue by not providing the code sample (not really needed in the context of this article) and leaving it as an excercise for the reader.

Some screens later in the article we find the following gem of regular expression (JScript, searching an HTML document for tag):

var rex = new RegExp("]*>", "i");

Er, what happened to the non-greedy qualifiers? What is that \s doing inside []? This feels like something written by a person, who does not use javascript day-to-day.

Please, please let's keep MSDN technical library standards high. After all we all benefit from better written code.

10/2/2006 1:23:53 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [3]  | 
 Thursday, September 28, 2006

Several people have hit a problem while installing the service pack on Windows Server 2003 machines. The installation (that takes obscenely long time) eventually fails with a message:

Error 1718: The file xxxxxxxx.msp was rejected by digital signature policy.

Two things with repsect to that:

1) I had this problem and was eventually able to complete installation (on the second try). What has changed is that I moved the SP file from a network location to a local drive.

2) Take a look at the setup log. It can be found under %temp%\VS80sp1-KB918525-X86-Beta-ENU (this path can be pasted into Run box to open the folder)

9/28/2006 2:08:37 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Wednesday, August 09, 2006

Today I went on to research the process of formatting a storage card programmatically. I decided to do this after I tried to advise someone in the MSDN forums, without clearly understanding what's involved.

After digging around for a while I came up (thanks to XDA-Developers) with the following:

int _tmain(int argc, _TCHAR* argv[])
{

 STOREINFO fd = {0};
 fd.cbSize = sizeof(fd);
 DWORD dw = GetFileAttributes(_T("
\\StoreMgr"));
 HANDLE hFind = FindFirstFileW(L"
\\StoreMgr", (LPWIN32_FIND_DATAW)&fd);

 WCHAR szBuffer[255];
 wcscpy(szBuffer, L"
\\StoreMgr\\");
 wcscat(szBuffer, fd.szDeviceName);
 HANDLE hStore = CreateFile(szBuffer, GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
 DWORD dwErr = GetLastError();

 FindClose(hFind);

 STOREINFO si = {0};
 si.cbSize = sizeof(si);
 PSLGetStoreInfo(hStore, &si);

 BOOL bfmt = PSLDismountStore(hStore);
 bfmt = PSLFormatStore(hStore);

 return 0;
}

What can I tell you... Kids, don't try this at home. What I have totally forgotten is that the typical PPC device contains several flash memory stores, some of which hold the ROM (that's why you can “flash” device, right?) and some - OS snapshot (on WM5 where RAM is persisted in the flash).

Stepping in the debugger through the above I got to the line that calls PSLFormatStore. It failed. The device froze. Essentially, I cleared the ROM on the running device - typically the first step when flashing the ROM update. This is very similar to running format c: on DOS.

Soft reset - nothing (just a blue Imate boot screen)

Hard reset - nothing. It did say something about formatting the flash memory, but that was the extent of it.

Fortunately I had a ROM update sitting around and that worked.

Here is the corrected code:

#include "stdafx.h"

#define FIRST_METHOD 0xF0010000
#define APICALL_SCALE 4
#define HANDLE_SHIFT 8
#define HT_FIND 8
#define HT_FILE 7

int _tmain(int argc, _TCHAR* argv[])
{
 /*
 Store enumeration
 STOREINFO storeinfo = {0};
 storeinfo.cbSize = sizeof(STOREINFO);
 HANDLE hFindStore = FindFirstFile(L"
\\StoreMgr", (LPWIN32_FIND_DATAW)&storeinfo);
 while( FindNextFile(hFindStore, (LPWIN32_FIND_DATAW)&storeinfo) )
 {
 }
 */

 WCHAR szBuffer[255];
 wcscpy(szBuffer, L"
\\StoreMgr\\");
 wcscat(szBuffer, L"DSK1:"); // DSK1: is hardcoded for simplicity
 HANDLE hStore = CreateFile(szBuffer, GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
 DWORD dwErr = GetLastError();

 BOOL bfmt = PSLDismountStore(hStore);
 bfmt = PSLFormatStore(hStore);

 STOREINFO si = {0};
 si.cbSize = sizeof(si);
 PSLGetStoreInfo(hStore, &si);


 bfmt = PSLCreatePart(hStore, L"PART00", 0, si.snBiggestPartCreatable >> 32, si.snBiggestPartCreatable, TRUE);
 HANDLE hPart = PSLOpenPartition(hStore, L"PART00");
 bfmt = PSLFormatPart(hPart, 0, TRUE);
 bfmt = PSLMountPartition(hPart);

 PARTINFO pi = {0};
 pi.cbSize = sizeof(pi);
 HANDLE hFind = PSLFindFirstPartition(hStore, &pi);
 while( PSLFindNextPartition(hFind, &pi) )
 {
 }

 PSLFindClosePartition(hFind);

 CloseHandle(hStore);

 return 0;
}

 

8/9/2006 1:23:53 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [1]  | 
 Tuesday, April 04, 2006

On April 18, 10 AM PST I am doing an MSDN webcast on IMAPI v2 and using it to burn CDs and DVDs. As you may know, I've been to some extent successful in uitilizing IMAPI v1 (found in Windows XP) in my own applications. Now with the advent of Windows Vista it is finally possible to write CDs and DVDs even from VB Script. Join me for this webcast to see how this techology can be utilized from .NET languages.

Watch this post for the web cast code samples link.

4/4/2006 3:07:24 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Saturday, January 28, 2006

I've been encouraged to blog about a new feature available as a part of newly released beta 1 of OpenNETCF SDF - the Imaging API wrapper.

The Imaging API is an attempt to bring Image codec support from the desktop GDI+ to mobile devices. It is implemented as a set of COM interfaces available to C/C++ applications. There is a cocreatable class ImagingFactory and a bunch of interfaces that allow loading, saving and manipulating the images. Effective 1.0 SP2 Compact Framework uses this API internally to load (and in 2.0 to save) images instead of imgdecmp.dll used in CF1 SP1 and before.

In CF2 it became possible to wrap COM interfaces for use in the managed applications. The Bitmap class is also significantly richer than before. Given this, one would ask what would be the reason to try using Imaging API directly. Here is a brief list of things that are not part of the CF2 Bitmap class:

  • Access to image tags (EXIF header etc)
  • Image transformation (flip, rotate, gamma/brightness/contrast controls)
  • Thumbnails, loading parts of the large image

The Imaging wrapper is located inside OpenNetCF.Drawing components as OpenNetCF.Drawing.Imaging namespace. On top of the basic interfaces it offers a small utility layer presented as ImageUtils class. ImageUtils has the following high-level methods:

  • Bitmap RotateFlip(Bitmap bitmap, RotateFlipType type)
  • Bitmap Flip(Bitmap bitmap, bool flipX, bool flipY)
  • Bitmap IBitmapImageToBitmap(IBitmapImage imageBitmap)
  • IBitmapImage BitmapToIImageBitmap(Bitmap bitmap)
  • IBitmapImage CreateThumbnail(Stream stream, Size size)

The last method - CreateThumbnail - allows loading a small thumbnail instead of a large image. Most Pocket PC devices won't be able to load and display a 5-6 megapixel image produced by most digital cameras. At the same time one can easily load 640x480 thumbnail that will let one implement basic zoomable view.

In the next couple of days I'm going to provide a few code samples. Stay tuned.

1/28/2006 1:53:31 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Wednesday, January 04, 2006

Alex Y sent me a link to the digg.com, where ISO Recorder is being discussed. I'm not sure what to make of it, but I know it's better to be “digged” than to be /.ed

1/4/2006 6:08:53 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Tuesday, November 01, 2005

Few days ago (Oct 26) in San Francisco I spotted what must be a part of the upcoming Visual Studio/SQL Server/Biztalk launch event. The picture is a bit grainy, but then whatever JAM is, a great camera it is not.

These are TX1s from London Taxi Company

11/1/2005 2:55:33 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Wednesday, October 12, 2005

I've been reading about the new Microsoft Streets and Trips 2006 and came across a feature comparison chart

This reminded me of one of the reasons why I dislike marketing people. Here are 10 rules “How to create a favorable comparison chart“

  1. Pick your competition wisely. If possible, compare to unrelated products
    or products marketed for a different niche
  2. Try limiting your comparison to only those features that exist in your
    product. Never mind the competition has something that you don't - you are
    playing on the home field and you are setting the rules.
  3. Never hesitate to use meaningless "features" as sales points. "Only our
    product is built on the powerful XYZ technology". So if the competitor used ZYX
    technology, that's his problem. We are not here to discuss whose technology
    is better. Suffice it to say that they did not use ours.
  4. Be charitable. If you can throw in a few features that exist in all
    competing products do it, but do it sparingly. You are not going to look
    good if there are too many of those.
  5. "New!" next to the feature description works very well. It is especially
    good when done in red bold font with a little star ornament. And no, you do
    not have to explain that "new" refers to something that is new to this
    release of *your* product, even if competition had it for ages.
  6. It really helps to compare your almost released product to competitor's
    last year release even if they just announced a new version. After all you
    haven't seen it - it may very well not exist. Remember, other people create
    vaporware - we have bold plans and imminent upcoming releases.
  7. If you can't think of 10 rules, make it 6.
10/12/2005 5:55:39 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Monday, September 12, 2005

Update: I'm getting reports that this build of ISO Recorder crashes on the recent Vista builds. I pulled the download for now. Expect a new drop for the Vista Beta 2 timeframe

With certain amount of help from other Vista beta testers I have released an early build of ISO Recorder for Windows Vista.

I want to thank David Smith and Jonathan Rosenberg for their assistance with testing and willingnes to help prove that 3rd time is indeed a charm. Apparently working with beta OS (Vista) and beta compiler (VS2005) can be ... tricky sometimes

9/12/2005 12:56:36 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Friday, September 09, 2005

If you have tried to install VS2005 August CTP on Windows Vista 64-bit edition you probably found out that it is not possible to do because 64-bit .Net framework fails to install. The reason it fails to install is quite silly. It's a bug in the launch condition in the installation package.

Here is how to get around this. You will need the msidb.exe tool found in the bin directory of the Platform SDK

  1. Create a temporary directory and copy \VS\WCU\dotnetframework\x64\NetFX64.exe from the Visual Studio DVD into this directory. Copy msidb.exe into the same directory (or ensure that it is in the path)
  2. Explode it using a command
    NetFx64.exe /Q /C /T:%CD%
  3. Extract the LaunchCondition decriptor by running:
    msidb -e -dnetfx.msi -f%CD% LaunchCondition
  4. Use notepad to edit LaunchCondtion.idt. Add OR (VersionNT=600) to the condition as shown below:
    (Version9X >= 410) OR ((VersionNT = 500) AND (ServicePackLevel >= 3)) OR (VersionNT = 501) OR ((VersionNT = 502) AND (ServicePackLevel >= 1)) OR (VersionNT=600) [LocProductName] is not supported on Windows 95, Windows NT, Windows 2000 without Service Pack 3 or greater, and Windows Server 2003 without Service Pack 1 or greater.
  5. Use MSIDB again to import the updated launch condition:
    msidb -i -dnetfx.msi -f%CD% LaunchCondition.idt
  6. Now launch install.exe and complete the framework installation. At this point you can relaunch Studio setup and successfully complete it.
9/9/2005 12:33:53 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Sunday, August 28, 2005

I've been lazily browsing the registry on my computer (it seems I tend to do it a lot) when I noticed a familiar name - System.Collections.Queue. Wait a minute. Isn't it a .NET class? Indeed, what I was looking at was a COM registration for a class System.Collections.Queue, pointing to mscoree.dll and mscorlib.dll as its server. It sounded like someone has kindly registered the mscorlib (parts of it anyway) with COM, and it was sitting there ready to be used. To test my hypothesis, I quickly create a simple VB script:

' CollTest.vbs
set coll = CreateObject("System.Collections.Queue")
coll.Enqueue "Bob"
coll.Enqueue "Joe"
WScript.Echo coll.Dequeue()
WScript.Echo coll.Dequeue()

When I ran it, lo and behold “Bob“ and “Joe“ were the 2 strings printed. When you stop to think about it, of course it should have worked, but nevertheless it felt as a nice surprise. After all the VB Script stock object set is quite limited and getting a free extension (.NET 1.1 is on every machine I work with) was quite welcome.

Of course I went to explore it further. From the System.Collections namespace we apparently have:

  • Queue
  • Stack
  • ArrayList
  • SortedList
  • Hashtable

I'd say it makes a rather nice addition to the existing VB script (and javascript as well) toolset. But wait, there is more...

System.IO namespace is represented by two useful classes:

  • StringWriter
  • MemoryStream

Finally, there are System.Text.StringBuilder and System.Random classes.

Why would I be interested in StringWriter (or StringBuilder)? Because frankly, VBScript memory allocator sucks. Anyone who ever tried to build a large string concatenating small ones knows that after 65536 characters performance drops drastically. Concatenating first 65536 characters one by one takes about 1.5 sec on my 3.5 GHz machine. Appending the next 65536 takes about 4 sec. The following 65536 characters take 33 sec. And you don't want to know what happens after that. Well, in case you do, appending together the string representations of numbers 1 through 100000 takes over 2 minutes.

Of course I decided to check if I could do it faster with StringWriter. But there was a catch. If you check the lsit of methods of the StringWriter class in the Visual Studio object browser, or rather the TextWriter class, from which StringWriter derives all of the Write methods, you would notice that Write() has 17 overloads, and WriteLine() has 18. Obviously you would want to use a particlular overload. How do you choose one. As it happens, the .NET framework deals with this problem in a straightforward, if inelegant way. If you have 18 overloaded methods called Write, they are exposed as Write, Write_2, Write_3 ... Write_18. How do you find out which one is which? If you look into the class browser (or Ildasm), the order of the overloads is reverse to what you see. E.g. for StringWriter we have:

  • System.IO.TextWriter.Write(string, params object[]) // Invoke as Write_17
  • System.IO.TextWriter.Write(string, object, obejct, object) // Invoke as Write_16
  • System.IO.TextWriter.Write(string, object,object) // Invoke as Write_15
  • System.IO.TextWriter.Write(string, object) // Invoke as Write_14
  • System.IO.TextWriter.Write(object) // Invoke as Write_13
  • System.IO.TextWriter.Write(string) // Invoke as Write_12
  • System.IO.TextWriter.Write(decimal) // Invoke as Write_11
  • System.IO.TextWriter.Write(double) // Invoke as Write_10
  • System.IO.TextWriter.Write(float) // Invoke as Write_9
  • System.IO.TextWriter.Write(ulong) // Invoke as Write_8
  • System.IO.TextWriter.Write(long) // Invoke as Write_7
  • System.IO.TextWriter.Write(uint) // Invoke as Write_6
  • System.IO.TextWriter.Write(int) // Invoke as Write_5
  • System.IO.TextWriter.Write(bool) // Invoke as Write_4
  • System.IO.TextWriter.Write(char[], int, int) // Invoke as Write_3
  • System.IO.TextWriter.Write(char[]) // Invoke as Write_2
  • System.IO.TextWriter.Write(char) // Invoke as Write

This means that if we want to write a string into a stringwriter, we would call:

s = “Bob“
Set wrt = CreateObject("System.IO.StringWriter")
wrt.Write_12 s

To test the performance of a StringWriter as compared to concatenating a VB string I ran the following script:

'stringtest.vbs
WScript.Echo Now
s = ""
for i = 1 to 100000
s = s & CStr(i)
next
WScript.Echo Now


set wrt = CreateObject("System.IO.StringWriter")
for i = 1 to 100000
s = CStr(i)
wrt.Write_12 s
next
s = wrt.GetStringBuilder().ToString()
WScript.Echo Now

And here are the results:

8/28/2005 1:02:14 AM
8/28/2005 1:04:07 AM
8/28/2005 1:04:08 AM

As you can see, concatenating a VB string took nearly 2 minutes, while .NET string was only a second.

But wait, there is more...

Let's take a look at the StringBuilder class. The most important feature of it (at least in the context of what we are doing here) is formatting. There are 5 AppendFormat methods:

AppendFormat ( System.IFormatProvider provider , System.String format , params object[] args )
AppendFormat ( System.String
format , params object[] args )
AppendFormat ( System.String
format , System.Object arg0 , System.Object arg1 , System.Object arg2 )
AppendFormat ( System.String format , System.Object arg0 , System.Object arg1 )
AppendFormat ( System.String
format , System.Object arg0 )

Let's see if we could easily use one of them:

'sbtest.vbs
set sb = CreateObject("System.Text.StringBuilder")
sb.AppendFormat_5 Nothing, "{0} is a {1} number" & vbCrLf, Array(1, "loneliest")
sb.AppendFormat_5 Nothing, "{0} is a {1} number" & vbCrLf, Array(2, "happiest")
WScript.Echo sb.ToString()

Prints:

1 is a loneliest number
2 is a happiest number

Weee!! We have formatting! In VBScript.

Oh, yes, did I mention it also works in ASP?

8/28/2005 1:21:02 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Thursday, August 25, 2005

I've just done a webcast, which was effectively a repeat of my MEDC'05 presentation on hosting ActiveX controls in CF 2.0. It can be seen here

Update (09/03/2005). It is apparently not entirely clear from the webcast, where the source code is. It can be found here

Update 2 (09/03/2005). The source code will not compile on beta 2 of Visual Studio because of Marshal.AllocHGlobal/FreeHGlobal that were not introduced until July CTP. Simply replace them with AllocCoTaskMem/FreeCoTaskMem

Update 3 (09/03/2005). TO be able to host WMP in Pocket PC application you must have WMP10. WMP9 did not have an ActiveX interface. Some (but not all) of the 2003SE device have WMP10. All WM5 devices will have WMP10

8/25/2005 4:40:44 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [3]  | 
 Tuesday, August 02, 2005

Yesterday Peter Foot has released his suite of Windows Mobile APIs for .NETCF (http://www.inthehand.com/1stAugust2005.aspx). This is an evolution of the PocketOutlook library which now incorporates Email and SMS functionality. Along with this are a selection of other assemblies for Configuration, System Status and Telephony. Very cool!

8/2/2005 9:07:56 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Friday, July 01, 2005
Looking at the flash animation - "How it works" for Verizon in-home fiber-optic internet service... They offer speeds up to 30Mbps down/5 up
At some point they go to explain why is their service so fast (compared to traditional broadband providers). Apparently it is because "the light travels so quickly". Of course! Silly me. I totally forgot that an average speed of an electron in a copper wire is about 1.5 mm/s. That explains my sluggish internet connection.
 
On a related note. SBC at some point announced the plans to deploy fiber connectivity option to the consumers. The deployment project was called "Project Lightspeed". Catchy... Then they announced plans to accelerate the deployment (after FCC lifted some restrictions). Ars Technica suggests that the project should be now named "Project FTL"
Computers | Life | Rant
7/1/2005 4:20:32 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [27]  | 
 Tuesday, May 24, 2005

... or is it Superman?

The kind folks, who administer MSDN webcasts, sent me as one of the year 2005 presenters a shirt and a ... cape. Yes, a red cape.

5/24/2005 11:42:31 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Wednesday, April 20, 2005

I've been asked for assistance with the following issue today. A developer has a CAB that he wants to install on the device without using CeAppMgr, from his own setup application. In addition he wanted to force the installation to the storage card if present.

This task involves several steps:

  1. Copy CAB to the device
  2. Detect storage card presence
  3. Launch wceload.exe to install the cab

1. Copying CAB to the device.

I would use cecopy (from Windows Mobile Developer Power Toys) or RAPI (CeCreateFile, CeWriteFile). If working with managed code, I suggest OpenNETCF.Communications library, or RapiDeploy tool

2. Detecting storage card presence.

While on the device side the preferred method is to use FindFirstFlashCard/FindNextFlashCard, these functions do not have Rapi equivalent. From the desktop side use CeFindAllFiles/FAF_FOLDERS_ONLY (or CeFindFirstFile/CeFindNextFile) to search the root directory and enumerate all files for having FILE_ATTRIBUTE_DIRECTORY and FILE_ATTRIBUTE_TEMPORARY combination of attributes (0x110).

3. Launch wceload.exe to install the cab

This is the interesting part. Here is a list of command-line switches that wceload.exe supports:

  • /delete - if value = 0 do not delete cab after install
  • /noui - perform a silent operation. Do not ask if it is ok to overwrite the exiting files
  • /nouninstall - do not create a .uninstall file. The applciation entry will not appear in Remove Programs list
  • /askdest - will force wceload to display a dialog that allows user to select installaltion location and some other things
  • /noaskdest - install specified applications to specified locations (see below)

 


Dialog displayed by wceload when /askdest is specified

The /noaskdest switch is the most inetersting of them all. When you specify it, wceload ignores the rest of the command line. Instead it checks the following registry location - [HKEY_LOCAL_MACHINE\SOFTWARE\Apps\Microsoft Application Installer\Install]

The key contents are key/value pairs:
[CAB file path] = [CAB destination directory]
e.g. \Storage Card\MyApp.CAB = \Storage Card\Program Files\My App

wceload will try to install the cab specified in the value name to the location specified in the value value. Below is the sample registry content:

To reiterate: in order to install a cab to the memory card, one needs to:

  1. Copy CAB to the device (e.g. to the memory card)
  2. Create a registry value on the device specifying the cab location and cab destination
  3. Launch wceload.exe with /noaskdest parameter
4/20/2005 5:52:05 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [5]  | 
 Wednesday, March 09, 2005

Preface:
===========================
The President: C'mon, let me nuke that bastard.
Commander Gilmour: Are you suggesting that we blow up the moon?
The President: Would you miss it?
[looks around the table]
The President: Would you miss it?
============================ [Austin Powers, The Spy, Who Shagged Me] =========

The programming world has its panties in uproar. In just over two weeks Microsoft ends the support cycle of the VB6 and VBA products. A group of MVPs has initiated a petition asking Microsoft to stop this insanity. Not only they want for MS to rescind the end-of-support decision, but they actually demand continued development and, unbelievably, integration of VB6 into Visual Studio product alongside with .NET languages. I probably would not be able to state my opinion on this better than Geoff Appleby did, so I'll just say nothing. Except of maybe this.

What support? The VB6 support has been lately done by the MVPs - the people who have drafted and  signed the aforementioned petition. They are absolutely free to continue to do so. The future security patches will necessarily cover the VB components. But no new development (see preface).

I am not a VB-hater (does such category even exist?). But I know from my own newsgroup support experience the amount of harm a loose, poorly structured language like VB brings. I wrote my share of VB6 code and the company I work for uses it extensively, but I moved on and the company is moving on. Hey, I love my car, but I realize one day I will have to buy a new car.

Get over it. And learn some new tricks along the way. World does not stand in place. It moves along.

3/9/2005 11:14:57 PM (Pacific Standard Time, UTC-08:00)  #    Comments [26]  | 
 Thursday, February 24, 2005

The Toolbar control provided as a part of Compact Framework v1.0 does not support tooltips (they are shown when you tap and hold toolbar button). Here is how to add this missing functionality.

First of all, let's see how the tooltips are added. The proper way to do it is to create a tooltip control and pass its handle to the TB_SETTOOLTIPS message as wParam. This sounds pretty painful if we were to do this by means of CF. Fortunately there is an easier way listed in the documentation as legacy but supported all the way through CE 5.0. You can send TB_SETTOOLTIPS passing an array of tooltip strings as lParam and string count as wParam and the toolbar control will create a tooltip control for you. Finally one has to remember to modify toolbar style to include TBS_TOOLTIP and do all of the above before adding the buttons to the toolbar.

Armed with this knowledge we start with defining a few P/Invoke functions:


[DllImport("coredll")]

extern static IntPtr GetCapture();

[DllImport("coredll")]

extern static IntPtr LocalAlloc(int flags, int size);

[DllImport("coredll")]

extern static IntPtr LocalFree(IntPtr p);

[DllImport("aygshell")]

extern static IntPtr SHFindMenuBar(IntPtr hwnd);

[DllImport("coredll")]

extern static int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);

[DllImport("coredll")]

extern static int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

const int TB_SETTOOLTIPS = (WM_USER + 81);

const int TB_SETSTYLE = (WM_USER + 56);

const int TB_GETSTYLE = (WM_USER + 57);

const int TBSTYLE_TOOLTIPS = 0x0100;

const int WM_USER = 0x0400;

Now, that we are almost ready to set tooltips, there is one final step left. The control expects to receive an array of string pointers. The Compact Framework marshaller is not capable of creating one. Instead we write a special piece of code that would marshal an array of strings into a block of unmanaged memory.

To add tootlips we create a string array, convert it into unmanaged array and then use it as the LPARAM when sending a TB_SETTOOLTIPS message. Keep in mind that the tooltip control will expect this memory to be preserved throught the application lifetime. Release it when the form is closed (or the tooltips are changed).

private IntPtr m_pLabels;

private string[] m_labels = new string[]
{ "Button1", "Button2", "Another button" };

SendMessage(hWndToolbar, TB_SETSTYLE, 0, SendMessage(hWndToolbar, TB_GETSTYLE, 0, 0) | TBSTYLE_TOOLTIPS);

m_pLabels = AllocateStringArray(m_labels);

SendMessage(hWndToolbar, TB_SETTOOLTIPS, m_labels.Length, m_pLabels);

The end result looks like this:

The sample code can be found here.

2/24/2005 7:14:38 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Wednesday, February 09, 2005

There is a common technique of obtaining the hWnd of various controls in Compact Framework applications to use with native Win32 APIs. It involves setting a Capture property to true and then using GetCapture() to get the handle:

txtUser.Capture = true;
IntPtr hWndUser = GetCapture();
txtUser.Capture = false;

Suprisingly this does not work on Smartphone. It gets you a handle alright, but the handle seems to be wrong. It is wrong indeed. The reason is that on the Smartphone the native Edit control (wrapped by the TextBox) is hosted inside another child window. This has to do with the Smartphone navigation. When you set TextBox.Capture to true, the outer control gets Captrure and as a result, it's the outer control, of which you get the handle. Since we know that the outer control has just one child, we can see our way from this quandary.

txtUser.Capture = true;
IntPtr hWndUser = GetCapture();
hWndUser = GetWindow(hWndUser, GW_CHILD);
txtUser.Capture = false;

//GW_CHILD = 5;

The required PInvoke definitions are parts of Win32Window class in OpenNETCF SDF

Note: This is applicable to CF v1. I have a reason to believe that in v2 things are done differently

2/9/2005 12:24:05 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 

A question popped up. Let's say we have an application \Program Files\MyApp\MyApp.exe that references a class library \Program Files\MyApp\Framework\MyLib.dll. How can we avoid a TypeLoadException in this scenario?

To answer this let's take a look at how the type resolution works in CF. When an application code attempts to load type T, it first checks if the assembly that contains the type (the one referenced in the Type's full name) is already loaded in the current AppDomain. Obviously, if the appdomain already has the assembly, there is no reason to perform a costly file operation lookihng it up and loading it again. This suggests an easy way to “help” the loader to resolve a type. All you need to do is preload the assembly before the code attempts to use the type from that assembly. In our scenario the following code need to be made:

System.Reflection.Assembly.LoadFrom(@”\Program Files\MyApp\Framework\MyLib.dll”);

This will ensure that the types that belong to this class library are successfully resolved.

The next question is - when to load the assembly. The easy answer is - to play it safe, load all such assemblies in the Main, before the Application.Run. This approach has a disadvantage - a noticeable performance hit because a number of modules are being loaded in the memory before the UI started painting. Besides some of them might be never used. Because of this I would advise staggered load. The trick is to make sure the appropriate assembly is loaded before code execution has entered a block that defines/instantiates a variable of a type defined in that assembly. For example if you have a function:

void DoSomething()
{
MyType myVar = new MyType();
}

where MyType is defined in a dynamically-loaded assembly, and your code never ever call this function, there is no reason at all to load the assembly (provided the no type from that assembly is ever used outside that function).

By cleverly structuring your code you can avoid performance hit even if you have a large amount of dynamically loaded assemblies

2/9/2005 12:15:44 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Tuesday, January 18, 2005

Today in the Compact Framework public newsgroup I spotted a code snippet (seemed like a piece of a newsreader application) where a variable was called neueNachricht. Try doing something like that in English. I don't think so.

Code | Computers | Life
1/18/2005 11:00:24 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Sunday, December 19, 2004

Q: When I append text to the end of the current content of a multiline listbox, it is always scrolled back to the first line. Even if I later use ScrollToCaret to return to the end of the text, the whole control briefly flickers. Is there a way to avoid it?

A: Indeed there seems to be an issue in Compact Framework where replacing selection in an edit control. Apparently someone wanted to be clever and even though there is a dedicated control message EM_REPLACESEL, he implemented set_SelectedText in the following way:

string SelectedText
{
   
get
   
{
       
if
( SelectionLength == 0 )
           
return
"";
       
return new string
(Text.ToCharArray(), SelectionStart, SelectionLength);
    }
   
set
   
{
       
this.Text = this.Text.Remove(SelectionStart, SelectionLength).Insert(SelectionStart, value
);
        SelectionStart =
this
.Text.Length;
        SelectionLength = 0;
    }
}

 

Why is it done this way, I don't know. There are perfectly good tools at the edit control message level to work with selection. What's important here is that set_SelectedText internally calls Control.set_Text which translates into WM_SETTEXT message. In case of an edit control it has a side effect of moving caret to the beginning and scrolling the text to the same place, thus creating an unpleasant flicker.

To remedy this problem we simply replace set_SelectedText with our own implementation:

Instead of
textBox.SelectedText = "A quick brown fox jumped over lazy dogs. ";
use
SendMessage(hWnd, EM_REPLACESEL, false, "A quick brown fox jumped over lazy dogs. ");

You will also need the following PInvoke definitions:

 

const uint EM_REPLACESEL = 0xc2;

[DllImport("coredll")]
extern static IntPtr GetCapture();
[DllImport("coredll")]
extern static int SendMessage(IntPtr hWnd, uint Msg, bool WParam, string LParam);

 

To get HWND of your textbox use something like this:

 

private IntPtr GetHWND(Control ctl)
{
    ctl.Capture =
true
;
    IntPtr hWnd = GetCapture();
    ctl.Capture =
false
;
   
return
hWnd;
}

 

12/19/2004 10:12:42 PM (Pacific Standard Time, UTC-08:00)  #    Comments [2]  | 
 Tuesday, December 07, 2004
Pioneered by Amazon (IIRC) the unobtrusive suggestions like "Other customers who bought Terminator II, have also purchased Eraser" actually make sense. That is until someone gives a whole new meaning to the word "often".
 

"Customers who bought this item often buy"... Locking Key Box (48 hooks) and ... T D INdustrial Compound Laser Miter Saw. Really???

Or this one:  ATN Night Vision spotting Scope

"Customers who bought this item often buy" Cosmopolitan Leather Tote Bag (three colors)

Well, this one actually makes sense. After all you do need something to tote your shiny new night scope around.

12/7/2004 4:43:55 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Sunday, November 21, 2004

At the heart of our production application is a paradigm of a work order. As one can surmise, the real-life work order has quite a complex data structure. In our case it is a chunk of XML couple of thousands of bytes in size. When you need to edit this XML in your app, you have to keep writing long and unpleasant queries like this:

txtWONumber.Text = objWO[”WorkOrder”].GetAttribute[”Number”];
txtWOTask.Text = objWO[”WorkOrder”][”Asset”][”Task”].InnerText;

Of course in part this is caused by lack of XQuery support in CF 1.0, but still, later you have to write the update code and make sure you haven't forgotten to call it. And if you need to populate a listbox or a datagrid from a list of XML nodes, you are quite out of luck and in for some manual work.

And yet, the databinding underneath is highly automated and quite extensible, so let's see what we can do to make it understand XML data.

First of all, we will decide on what is our data unit (component). It will be an XML node. Component properties could be either attributes or subnodes, or even any valid XQuery expression - anything that returns data. A logical choice of data source is an XML node list (not an array). Such a list is easily obtained from an XML document via ChildNodes property of its document element.

I am not going to go into details of how the databinding works for there are many good sources that cover this process1. Instead I will simply say that at the root of custom databinding is an object derived from a class called PropertyDescriptor. We are going to derive our own class from it and call it XmlPropertyDescriptor.

Let's set some gorund rules. We want to be able to take an xml document like this:

xml version="1.0" encoding="utf-8" ?>
<
bookstore xmlns:bk="urn:samples">

<book genre="novel" publicationdate="1997" bk:ISBN="1-861001-57-6">

<title>Pride And Prejudicetitle>

<price>24.95price>

book>

<book genre="novel" publicationdate="1992" bk:ISBN="1-861001-45-3">

<title>The New Dawntitle>

<price>29.95price>

book>

<book genre="novel" publicationdate="1991" bk:ISBN="1-861001-57-8">

<title>Blue Smoketitle>

<price>19.95price>

book>

bookstore>

and bind to a list of “book” items using display expression like “title” or “@bk:isbn" . If we had more complex book item structure, the binding name could be a more complex path - “author/Name/@First”. What we do not want to do (since we don't really have XQuery/XPath support in CF) is to allow relative paths, functions and queries. Moreover, we will say, that in the path, every element must be a node name, except of the last one, that can be an attribute name (starting with @ ).

When you derive from the PropertyDescriptor class there are few things to keep in mind:

  1. Override IsReadonly property. This will tell the framework whether your property supports updates
  2. Override PropertyType property to return typeof(string) - this is our property type; we don't do any data conversions here. If we want data conversion/validation, we can implement it in Format/Parse events of the binding. We could to some data validation and type conversion via reading the schema (if available), but it would be outside of the scope of this article.
  3. Override ComponentType property to return typeof(XmlNode). This is the only component type we deal with.
  4. Override GetValue method. This is the most important method. It actually goes ahead and retrieves the property value given the component. In our case it gets the XmlNode (and already has the “XPath” by way of constructor parameter). Our task is to perform the query. Very simple on the desktop. A bit more complex in CF.
  5. Override SetValue method. It receives the component and the new value and sets it using the known “Xpath”.

Now, wouldn't it be nice to make the binding use our XmlPropertyDescriptor class? Simple - we just need to create our own Data Source that would report XmlPropertyDescriptors instead of whatever is used by CurrencyManager. Whatever is usually a SimplePropertyDescriptor class, ReflectPropertyDescriptor or DataColumnPropertyDescriptor.

This brings us to building our own data source object. There is no abstract class to override. When creating it, we need to make sure we implement several interfaces:

  • ITypedList
  • IEnumerable
  • IList
  • IBindingList

ITypedList::GetItemProperties creates a bit of a problem. It is supposed to return Property descriptor collection populated with all properties the data unit exposes. Unfortunately in our case properties are valid xpath expressions, which could be quite a few, expecially on a complex XML document

The reason we need to implement ITypedList::GetItemProperties is that we need for example a data grid to be able to get populated automatically, without us specifying XPath expression for each column. The problem here is that we don't want to build a full list of all possible xpath expressions valid on our XML item, so we need to decide on some way to simplify it. Let's limit the autogenerated property list to all attributes or all 1-st level child elements. If the XML is more complex than that, well, you need to specify the grid columns explicitly.

The IEnumerable and IList are simply delegated to the underlying XmlNodeList. IBindingList implementation is somewhat simplistic as we don't really want to figure out how to track the external changes in the XmlNodeList.

We end up with a class that allows us to take the XML document above and write things like:


doc.Load("data.xml");

XmlDataSource src = new XmlDataSource(doc.DocumentElement.ChildNodes, XmlDataSourceMode.DataSourceModeAuto);

txtGenre.DataBindings.Add("Text", src, "@genre");

txtISBN.DataBindings.Add("Text", src, "@bk:ISBN");

txtPrice.DataBindings.Add("Text", src, "price");

txtPubDate.DataBindings.Add("Text", src, "@publicationdate");

txtTitle.DataBindings.Add("Text", src, "title");

 

listBox1.DisplayMember = "title";

listBox1.ValueMember = "@bk:ISBN";

listBox1.DataSource = src;

dataGrid1.DataSource = new XmlDataSource(doc.DocumentElement.ChildNodes,

new string[] { "@genre", "title/#text", "price/#text" });

This produces a screen like this one:

 

Selecting a list box item moves the current position along the node list. Data edits are also supported.

The source code for this article can be found here.

11/21/2004 8:41:11 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  |