Mister Goodcat

Peter's home of all things life

Friday, 9/9/2011 9:43 PM
by Peter Kuhn
2 Comments

Silverlight 5 PInvoke fun - System Font Dialog

Friday, 9/9/2011 9:43 PM by Peter Kuhn | 2 Comments

Edit 2011-12-11: This article is compatible with the final version of Silverlight 5 (5.0.61118.0).

Silverlight 5 has built-in support for Platform Invocation Services, which more often is abbreviated by PInvoke or P/Invoke. If you're not familiar with the topic, you can read a small tutorial here, or an MSDN Magazine article here. In a nutshell, P/Invoke allows you to call into unmanaged code that is located in Win32 dlls on the system. People already have come up with nice short tutorials regarding this, for example how to let your computer beep. However, often P/Invoke is much more complex than these simple examples, especially when you need to move data between the managed and unmanaged world. But on the other hand, these more complex scenarios enable thrilling new possibilities with Silverlight. How about displaying system dialogs, and use the results in your Silverlight application? Let's play some games with fonts!

Prerequisites

Calling system code obviously is something that cannot be allowed for web applications. To make use of this your application needs to be run with elevated trust. For my example, I have created a sample out of browser application that is configured for this, so I can make use of P/Invoke without any problems.

Another note before we start: I explain some details of the involved process of importing functions and creating data structures that can be used with these functions. However, this is not meant to be a full P/Invoke tutorial, and I'm far from being an expert on every aspect of that. If you want to learn more about the topic, please follow the many links provided here to learn the details.

Marshalling

When you take a look at the documentation of the system dialog for fonts (here and here) you can see that the method required to show it looks pretty simple at first:

 1: BOOL WINAPI ChooseFont(
 2:   __inout  LPCHOOSEFONT lpcf
 3: );

But when you take a second look you realize that the single parameter required is not a simple type, but a structure (documentation here):

 1: typedef struct {
 2:   DWORD        lStructSize;
 3:   HWND         hwndOwner;
 4:   HDC          hDC;
 5:   LPLOGFONT    lpLogFont;
 6:   INT          iPointSize;
 7:   DWORD        Flags;
 8:   COLORREF     rgbColors;
 9:   LPARAM       lCustData;
 10:   LPCFHOOKPROC lpfnHook;
 11:   LPCTSTR      lpTemplateName;
 12:   HINSTANCE    hInstance;
 13:   LPTSTR       lpszStyle;
 14:   WORD         nFontType;
 15:   INT          nSizeMin;
 16:   INT          nSizeMax;
 17: } CHOOSEFONT, *LPCHOOSEFONT;

Hm, wow. If you are not familiar with C/C++ and the Windows API, something like this can come as quite a shock. DWORD? LPCFHOOKPROC? HINSTANCE? What are all these types? And even more important, how can I use them in Silverlight, I don't have any types like these in the framework! The answer is that you need to find out what corresponding types of Silverlight/C# you can use as a substitution for these types. This process is of converting between a managed type and an unmanaged one is named marshalling.

One way to find those mappings and signatures is go to http://www.pinvoke.net/ and search for the information there. This is a wiki-like site that has a huge collection of conversions ready to use from .NET – and now also from Silverlight. The site even offers a Visual Studio add-in if you want to access its features directly from within the IDE. In the past, this site has helped me a lot when I was in need to translate structs like the above to the managed world, and I recommend this as a first step to everyone.

Unfortunately, pinvoke.net does not have a definition for the CHOOSEFONT struct. If this happens, then you can look up what types the Windows data types are mapped to here, for example. When you look through this list, you can see that for example DWORD is a 32-bit unsigned integer and hence would be translated to (U)Int32. Those kinds of tables can be found in many places on MSDN; another one that lists the most common types and directly maps them to their .NET equivalents can be found here, for example (scroll most the way down for the table).

Marshalling CHOOSEFONT

After going through the list of fields in the struct and looking for their counterparts in Silverlight, you should come up with something like this:

 1: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
 2: public struct ChooseFontStruct
 3: {
 4:     public Int32 lStructSize;
 5:     public IntPtr hwndOwner;
 6:     public IntPtr hDC;
 7:     public IntPtr lpLogFont;
 8:     public Int32 iPointSize;
 9:     public Int32 Flags;
 10:     public Int32 rgbColors;
 11:     public Int32 lCustData;
 12:     public Int32 lpfnHook;
 13:     public String lpTemplateName;
 14:     public IntPtr hInstance;
 15:     public String lpszStyle;
 16:     public Int16 nFontType;
 17:     public Int32 nSizeMin;
 18:     public Int32 nSizeMax;
 19: }

Most of the elements look much simpler after the mapping and are value types that won't make any problems when we use them. We simply have to find out what values we can use for them, but that all is documented on MSDN (see link above). The fields that are more problematic are the pointers (IntPtr). For most fields we don't have to provide a value, but a particular one that is required is the LOGFONT struct (the field name is lpLogFont). Yes, often marshalling the data structures is the most complex and tedious part of doing P/Invoke, and if you're facing nested data structures like these, it can become a bit challenging.

Marshalling LOGFONT

The above mentioned LOGFONT struct looks like this:

 1: typedef struct tagLOGFONT {
 2:   LONG  lfHeight;
 3:   LONG  lfWidth;
 4:   LONG  lfEscapement;
 5:   LONG  lfOrientation;
 6:   LONG  lfWeight;
 7:   BYTE  lfItalic;
 8:   BYTE  lfUnderline;
 9:   BYTE  lfStrikeOut;
 10:   BYTE  lfCharSet;
 11:   BYTE  lfOutPrecision;
 12:   BYTE  lfClipPrecision;
 13:   BYTE  lfQuality;
 14:   BYTE  lfPitchAndFamily;
 15:   TCHAR lfFaceName[LF_FACESIZE];
 16: } LOGFONT, *PLOGFONT;

The documentation for it can be found here. One thing that becomes apparent here is that this structure is a lot simpler, it only consists of value types and a single string in the end.

What I personally find the most annoying thing with the MSDN documentation is that often there is no simple way to find out the values of constants like the one of "LF_FACESIZE" that is used here. For C/C++ developers this is not a problem, they can simply use these constants. But we need to know the actual value so we can use it in C#, and that often means to take a look at the header files of C++ to get the actual values, or to use a search engine to get the result. For a single value like this that is not a problem, but sometimes you have to map dozens of constants or enumerations, and then the fact that this information is not accessible directly from the MSDN articles can become annoying quickly.

Anyway, the value for LF_FACESIZE is 32, so the mapped struct in Silverlight looks like this:

 1: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
 2: public struct LogFontStruct
 3: {
 4:     public Int32 lfHeight;
 5:     public Int32 lfWidth;
 6:     public Int32 lfEscapement;
 7:     public Int32 lfOrientation;
 8:     public Int32 lfWeight;
 9:     public Byte lfItalic;
 10:     public Byte lfUnderline;
 11:     public Byte lfStrikeOut;
 12:     public Byte lfCharSet;
 13:     public Byte lfOutPrecision;
 14:     public Byte lfClipPrecision;
 15:     public Byte lfQuality;
 16:     public Byte lfPitchAndFamily;
 17:     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
 18:     public string lfFaceName;
 19: }

Defining the function signature

To access the "ChooseFont" function, we need to tell the runtime where it can be found. This is another slightly annoying thing with the MSDN documentation, unfortunately. When you look around the above links that talk about the function and system dialogs, you won't find what dll these functions are actually defined in. If you're doing a lot of P/Invoke you will know a lot of these by heart in no time, but for the occasional developer this once again means to do a separate search for this. In our case, the function we need is located in comdlg32.dll:

 1: [DllImport("comdlg32.dll", CharSet = CharSet.Auto, EntryPoint = "ChooseFont")]
 2: private extern static bool ChooseFont(IntPtr lpcf);

We now basically have all the information required to make use of the font dialog. We have the required structures, the function that shows the dialog, and we have a lot of documentation to make sense of the parameters and values.

Showing the dialog

Unfortunately, using the declared functions and structs also often is not very straight-forward. The fundamental problem that arises is that when we need to move complex data from the managed to the unmanaged world and back, the two concepts of memory management collide. We cannot simply pass complex structures to unmanaged functions, because in Silverlight and .NET memory management is done automatically behind the scenes for us. That means that at any time, without us even noticing, data may be moved around in memory for optimization. This doesn't affect our managed code, because the runtime ensures that we always have correct access to our data. However, unmanaged code is not able to make use of this; it relies on data in memory stays where it is. Another problem is that we cannot simply share memory between both worlds without any guarding mechanism due to security considerations. So the structure we have is something like this:

image

We need to use helper classes to move the data from and to the unmanaged world for us, in particular the Marshal class, and also the GCHandle class.

Creating the LOGFONT and CHOOSEFONT structures

To create the LOGFONT structure, we fist create an instance of our managed struct (and set any values we require). The next step you would usually do in P/Invoke is to use a helper method that allocates unmanaged memory we can copy our managed data to (AllocHGlobal). However, we don't have this method in Silverlight, unfortunately. As a workaround, I made use of the GCHandle class to allocate a byte array big enough to hold the LOGFONT structure data, and I used GCHandleType.Pinned to prevent the garbage collector from moving around the data in memory. That means that now unmanaged code can make use of this chunk of memory without the danger that it's not available anymore in the middle of processing.

 1: LogFontStruct lf = new LogFontStruct();
 2:  
 3: // The following way of allocating memory is not available in Silverlight,
 4: // so we use GCHandle.Alloc instead
 5: //IntPtr lplf = AllocHGlobal(Marshal.SizeOf(lf));
 6: lfBufferHandle = GCHandle.Alloc(new byte[Marshal.SizeOf(lf)], GCHandleType.Pinned);
 7: IntPtr ptrLf = lfBufferHandle.AddrOfPinnedObject();
 8:                 
 9: // copy over
 10: Marshal.StructureToPtr(lf, ptrLf, false);

Finally, the Marshal class helps us to copy the data from our managed structure to the allocated byte array. Please note that this way of allocating memory undermines the memory management of the runtime, and you absolutely need to make sure that you free this memory manually in the end again (I used a finally block for this) by calling the "Free" method on the GCHandle.

Creating the CHOOSEFONT structure works exactly the same. We first create the managed structure, and then use GCHandle and the Marshal class to move this to some memory area where it can be used from unmanaged code. The only interesting part is where we set the values of this struct:

 1: // create new structure
 2: ChooseFontStruct cf = new ChooseFontStruct();
 3: cf.lStructSize = Marshal.SizeOf(typeof(ChooseFontStruct));
 4: cf.Flags = CF_SCREENFONTS;
 5: cf.lpLogFont = ptrLf;

As you can see I set the "lpLogFont" field to the pointer we have retrieved from the GCHandle object before. This connects those two constructs and the unmanaged system function can now go ahead and work with both of them.

Show the dialog

The last step of course is to show the dialog:

 1: // show the dialog!
 2: if (!ChooseFont(ptrCf))
 3: {
 4:     // user cancelled
 5:     return null;
 6: }

Here is what this looks like:

image

As you can see, we're showing a system font dialog from a Silverlight application (in the background) :).

Processing the results

Of course simply showing the dialog is not very thrilling, so let's go ahead an process the results. When the dialog is closed and the user has selected some font settings, the LOGFONT structure that we have passed into the unmanaged code will be filled with these results. The next logical step now would be to use the Marshal class to copy back that data into our managed struct, so we can easily work on it.

Unfortunately Silverlight only supports the overload of the required "PtrToStructure" method that takes the source pointer and an object. This method does not work with value types like the structs we have. Usually there's a second overload of this method that can be used for this, but it's not available in Silverlight (even though the documentation says different!).

The result is that we need to manually parse back the values into our managed structure from the byte array we created earlier using the GCHandle class. I have added a constructor to the struct that does this:

 1: public LogFontStruct(byte[] rawData)
 2: {
 3:     MemoryStream stream = new MemoryStream(rawData);
 4:     BinaryReader br = new BinaryReader(stream, Encoding.Unicode);
 5:     lfHeight = br.ReadInt32();
 6:     lfWidth = br.ReadInt32();
 7:     lfEscapement = br.ReadInt32();
 8:     lfOrientation = br.ReadInt32();
 9:     lfWeight = br.ReadInt32();
 10:     lfItalic = br.ReadByte();
 11:     lfUnderline = br.ReadByte();
 12:     lfStrikeOut = br.ReadByte();
 13:     lfCharSet = br.ReadByte();
 14:     lfOutPrecision = br.ReadByte();
 15:     lfClipPrecision = br.ReadByte();
 16:     lfQuality = br.ReadByte();
 17:     lfPitchAndFamily = br.ReadByte();
 18:     lfFaceName = new string(br.ReadChars(32)).Trim('\0');
 19: }

The final step is to transform these values into something that Silverlight can understand. For example, we need to create a FontFamily object from the "lfFaceName" value of this struct. Other values need more complex conversions. For example, the struct does not have a definition for the point size of a font. Instead it has a "lfHeight" property that returns (negative) values we cannot use in Silverlight. The documentation tells you a formula that is used to compute the "lfHeight" property from a point size:

 1: lfHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);

So to retrieve the point size from the "lfHeight" value, we first need to invert that function…

 1: pointSize = MulDiv(-lfHeight, 72, GetDeviceCaps(hDC, LogPixelsY);

… and then we would need to use the involved system call "GetDeviceCaps" to retrieve yet another value required for the computation. In the sample I have not implemented this and instead assumed this will always be 96 – this is the DPI setting for usual system setups with a font size of 100%. In a real world example, you would of course fetch this value dynamically.

Similarly, we can convert the returned values for the font weight and style so we're able to use them in Silverlight. For demonstration, I simply set the font properties of the button to what was returned from the system dialog:

 1: // show the font dialog and get the result
 2: var fontData = FontInteropHelper.ShowFontDialog();
 3:  
 4: // get the button and see if we have new font data
 5: var button = sender as Button;
 6: if (fontData != null && button != null)
 7: {
 8:     button.FontFamily = fontData.FontFamily;
 9:     button.FontSize = fontData.FontSize;
 10:     button.FontStyle = fontData.FontStyle;
 11:     button.FontWeight = fontData.FontWeight;
 12: }

And there you go! After choosing the settings that are visible in the above screenshot (Impact, Bold Oblique, 48 points), the Silverlight application looks like this:

image

In the beginning I was very curious whether this would actually work and if Silverlight would make use of this information about local fonts – but as you can see, it was successful, and now I'm able to choose the style of my Silverlight font using a built-in system dialog:

image

image

image

Nice, isn't it?! :)

Conclusion

This sample shows three things:

a) using P/Invoke can be complex, and marshalling the involved data correctly sometimes is a challenge,

b) Silverlight is missing some methods that would be very helpful for P/Invoke,

c) this feature opens the door to a whole new world of possibilities for Silverlight applications!

Feel free to download the sample project here and use it for your own needs; the code is not perfect, but should give you the right ideas. It contains comments and also the links back to the documentation where required. If you find errors or improvements, I'd be glad if you'd let me know about it, of course.

Have fun!

Comments (2) -

I'm trying to measure text in Silverlight.  I'd like to call GetTextExtentPoint32, but it requires a device context.  Would I have to call CreateWindow and pass that object's device context after somehow setting font properties in that newly created window?

Hi Brad. I don't know what would be required to use that, but what you say sounds reasonable. However, I think you could simply use a TextBlock for this, and inspect ActualWidth/ActualHeight to get the text size.

Pingbacks and trackbacks (4)+

Comments are closed