One important feature of the .NET runtime is COM and Win32 interoperability. With runtime interoperability services, developers can use both COM and Win32 libraries in managed applications. The classes related to these services are defined in the System.Runtime.InteropServices namespace.
We can use COM libraries in managed applications by simply adding a reference to the COM library using the Add Reference option of VS.NET or the Type Library Importer (Tlbimp.exe) .NET tool. Both of these options allow developers to convert a COM library to a .NET assembly, which can then be treated as other .NET assemblies. The graphical user interface (GUI) functionality of Windows is defined in a Win32 library called Gdi32.dll. Using Win32 libraries in managed code is a little more difficult than using COM libraries. However, there is nothing to worry about because the System.Runtime.InteropServices.DllImportAttribute class allows developers to use functionality defined in unmanaged libraries such as Gdi32.dll.
14.1.1 The DllImportAttribute Class
The DllImportAttribute class allows developers to import Win32 SDK functionality into managed applications. The DllImportAttribute constructor is used to create a new instance of the DllImportAttribute class with the name of the DLL containing the method to import. For example, the GDI functionality is defined in Gdi32.dll. So if we want to use GDI functions in our application, we need to import them using DllImportAttribute. The following code imports the Gdi32.dll library:
[System.Runtime.InteropServices.DllImportAttribute ("gdi32.dll")]
After adding this code, we're ready to use the functions defined in the Gdi32.dll library in our .NET application.
Now let's take a look at a simple program that uses the MoveFile function of Win32 defined in the KERNEL32.dll library. The code in Listing 14.1 first imports the library and then calls the MoveFile function to move a file from one location to another.
Listing 14.1 Using the Win32 MoveFile function defined in KERNEL32.dll
[System.Runtime.InteropServices.DllImportAttribute ("KERNEL32.dll")] public static extern bool MoveFile (String src, String dst); private void Move_Click(object sender, System.EventArgs e) { MoveFile("C:\output.jpeg", "f:\NewOutput.jpeg"); }
As with KERNEL32.dll, we can import other Win32 libraries to use them in .NET applications. The DllImportAttribute class provides six field members, which are described in Table 14.1.
The CallingConvention enumeration specifies the calling convention required to call methods implemented in unmanaged code. Its members are defined in Table 14.2.
The DllImportAttribute class has two properties: TypeId and Value. TypeId gets a unique identifier for an attribute when the attribute is implemented in the derived class, and Value returns the name of the DLL with the entry point.
Method |
Description |
---|---|
CallingConvention |
Required to call methods implemented in unmanaged code; represented by the CallingConvention enumeration. |
CharSet |
Controls name mangling and indicates how to marshal String arguments to the method. |
EntryPoint |
Identifies the name or ordinal of the DLL entry point to be called. |
ExactSpelling |
Indicates whether the name of the entry point in the unmanaged DLL should be modified to correspond to the CharSet value specified in the CharSet field. |
PreserveSig |
Specifies that the managed method signature should not be transformed into an unmanaged signature that returns an HRESULT structure, and may have an additional argument (out or retval) for the return value. |
SetLastError |
Specifies that the callee will call the Win32 API SetLastError method before returning from the named method. |
Member |
Description |
---|---|
Cdecl |
The caller cleans the stack. This property enables calling functions with varargs. |
FastCall |
For future use. |
StdCall |
The callee cleans the stack. This is the default convention for calling unmanaged functions from managed code. |
ThisCall |
The first parameter is the this pointer and is stored in the ECX register. Other parameters are pushed onto the stack. This calling convention is used to call methods in classes exported from an unmanaged DLL. |
Winapi |
Uses the default platform-calling convention. For example, on Windows it's StdCall, and on Windows CE it's Cdecl. |
14.1.2 Using the BitBlt Function
One of the most frequently asked questions on discussion forums and newsgroups related to GDI in managed code has to do with the use of BitBlt. Is this because developers want to implement sprites and scrolling-type actions in their applications? If you want to use the BitBlt function, you are probably aware of what it does. For the uninitiated, however, we should explain that this function performs a bit-block transfer of the color data corresponding to a rectangle of pixels from one device context to another. It is defined as follows:
BOOL BitBlt( HDC hdcDest, // handle to destination device context int nXDest, // x-coordinate of destination upper left corner int nYDest, // y-coordinate of destination upper left corner int nWidth, // width of destination rectangle int nHeight, // height of destination rectangle HDC hdcSrc, // handle to source device context int nXSrc, // x-coordinate of source upper left corner int nYSrc, // y-coordinate of source upper left corner DWORD dwRop // raster operation code );
More details of BitBlt are available in the GDI SDK documentation. Just type "BitBlt" in MSDN's index to find it.
First we need to import the BitBlt method and the Gdi32.dll library using the DllImportAttribute class.
[System.Runtime.InteropServices.DllImportAttribute ("Gdi32.dll")] public static extern bool BitBlt( IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, System.Int32 dwRop );
Now we just call BitBlt. The code in Listing 14.2 uses the BitBlt function. As the function definition shows, we need source and destination device contexts. There is no concept of device context in managed code, but to maintain GDI interoperability, the Graphics class's GetHdc method is used to create a device context for a Graphics object (a surface). GetHdc returns an IntPtr object.
In Listing 14.2, first we create a Graphics object by using CreateGraphics and we draw a few graphics items. From this Graphics object we create a Bitmap object, and we create one more Graphics object as the destination surface by using the FromImage method of the Graphics object. Next we call BitBlt with destination and source device contexts as parameters. Finally, we make sure to call ReleaseHdc, which releases device context resources. The Save method saves a physical copy of the image. We also call the Dispose method of Graphics objects.
Listing 14.2 Using the BitBlt function
private void Form1_Load(object sender, System.EventArgs e) { Graphics g1 = this.CreateGraphics(); Graphics g2 = null; try { g1.SmoothingMode = SmoothingMode.AntiAlias; g1.DrawLine(new Pen(Color.Black, 2), 10, 10, 150, 10); g1.DrawLine(new Pen(Color.Black, 2), 10, 10, 10, 150); g1.FillRectangle(Brushes.Blue, 30, 30, 70, 70); g1.FillEllipse(new HatchBrush (HatchStyle.DashedDownwardDiagonal, Color.Red, Color.Green), 110, 110, 100, 100 ); Bitmap curBitmap = new Bitmap( this.ClientRectangle.Width, this.ClientRectangle.Height, g1); g2 = Graphics.FromImage(curBitmap); IntPtr hdc1 = g1.GetHdc(); IntPtr hdc2 = g2.GetHdc(); BitBlt(hdc2, 0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height, hdc1, 0, 0, 13369376); g1.ReleaseHdc(hdc1); g2.ReleaseHdc(hdc2); curBitmap.Save("f:\BitBltImg.jpg", ImageFormat.Jpeg); } catch (Exception exp) { MessageBox.Show(exp.Message.ToString()); } finally { g2.Dispose(); g1.Dispose(); } }
14.1.3 Using GDI Print Functionality
We discussed .NET printing functionality in Chapter 11, but what about using GDI printing in managed code? One reason for using GDI may be speed and familiarity with GDI or having more control over the printer.
Until now we have been selecting objects such as fonts and lines and then drawing on a page, which is then printed out. Keep in mind that all the fonts you can use within the .NET environment have to be TrueType fonts. Before TrueType came along, there was something called PCL (Printer Control Language), also known as bitmap fonts. So what's the difference?, you may ask. It's simple: A PCL or bitmap font is made up of patterns of dots that represent each letter.
The problem is that a different PCL font was required for every size of letter needed, such as 12, 14, and so on. Different PCL fonts were needed even for italic and bold versions! As you can imagine, it was necessary to have lots of PCL fonts to maintain the flexibility we take for granted today.
TrueType fonts, on the other hand, are a lot more flexible. The reason is that the fonts are mathematical representations of each letter rather than a pattern of dots. If I decide I need a Times New Roman font at size 20, the font is simply recalculated rather than just a different pattern of dots being loaded.
What happens if your printer does not support the TrueType font you have selected? The only way to print it is to send what you want to print to the printer as graphics, which can be time-consuming if you're creating large printouts.
The code in Listing 14.3 does a few new things. For one, it uses Win32 APIs to talk directly to the printer, which gives us the best possible speed. Finally, it demonstrates the use of PCL5 commands to draw a box on the page.
Using the code in Listing 14.3, you would be able to create detailed pages consisting of multiple fonts and graphics. The nice thing is that they can all be created by just sending text to the printer rather than using graphics commands.
You may want to change the printer before you test this code. The following line of code specifies the printer:
PrintDirect.OpenPrinter("\\192.168.1.101\hpl", ref lhPrinter,0);
Listing 14.3 Using GDI print functionality in a managed application
// PrintDirect.cs // Shows how to write data directly to the // printer using Win32 APIs. // This code sends Hewlett-Packard PCL5 codes // to the printer to print // out a rectangle in the middle of the page. using System; using System.Text; using System.Runtime.InteropServices; [StructLayout( LayoutKind.Sequential)] public struct DOCINFO { [MarshalAs(UnmanagedType.LPWStr)] public string pDocName; [MarshalAs(UnmanagedType.LPWStr)] public string pOutputFile; [MarshalAs(UnmanagedType.LPWStr)] public string pDataType; } public class PrintDirect { [ DllImport( "winspool.drv", CharSet=CharSet.Unicode,ExactSpelling=false, CallingConvention=CallingConvention.StdCall )] public static extern long OpenPrinter(string pPrinterName, ref IntPtr phPrinter, int pDefault); [ DllImport( "winspool.drv", CharSet=CharSet.Unicode,ExactSpelling=false, CallingConvention=CallingConvention.StdCall )] public static extern long StartDocPrinter(IntPtr hPrinter, int Level, ref DOCINFO pDocInfo); [ DllImport( "winspool.drv", CharSet=CharSet.Unicode,ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern long StartPagePrinter( IntPtr hPrinter); [ DllImport( "winspool.drv", CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern long WritePrinter(IntPtr hPrinter, string data, int buf, ref int pcWritten); [ DllImport( "winspool.drv" , CharSet=CharSet.Unicode,ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern long EndPagePrinter(IntPtr hPrinter); [ DllImport( "winspool.drv" , CharSet=CharSet.Unicode, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern long EndDocPrinter(IntPtr hPrinter); [ DllImport( "winspool.drv", CharSet=CharSet.Unicode,ExactSpelling=true, CallingConvention=CallingConvention.StdCall )] public static extern long ClosePrinter(IntPtr hPrinter); } public class App { public static void Main() { System.IntPtr lhPrinter = new System.IntPtr(); DOCINFO di = new DOCINFO(); int pcWritten=0; string st1; // Text to print with a form-feed character st1="This is an example of printing " + "directly to a printerf"; di.pDocName="my test document"; di.pDataType="RAW"; // The "x1b" means an ASCII escape character st1="x1b*c600a6b0Pf"; // lhPrinter contains the handle for the printer opened. // If lhPrinter is 0, then an error has occurred. PrintDirect.OpenPrinter("\\192.168.1.101\hpl", ref lhPrinter,0); PrintDirect.StartDocPrinter(lhPrinter,1,ref di); PrintDirect.StartPagePrinter(lhPrinter); try { // Moves the cursor 900 dots (3 inches at // 300 dpi) in from the left margin, and // 600 dots (2 inches at 300 dpi) down // from the top margin st1="x1b*p900x600Y"; PrintDirect.WritePrinter(lhPrinter, st1, st1.Length, ref pcWritten); // Using the print model commands for rectangle // dimensions, "600a" specifies a rectangle // with a horizontal size, or width, of 600 dots, // and "6b" specifies a vertical // size, or height, of 6 dots. "0P" selects the // solid black rectangular area fill. st1="x1b*c600a6b0P"; PrintDirect.WritePrinter(lhPrinter, st1, st1.Length, ref pcWritten); // Specifies a rectangle with width of // 6 dots, height of 600 dots, and a // fill pattern of solid black st1="x1b*c6a600b0P"; PrintDirect.WritePrinter(lhPrinter, st1, st1.Length, ref pcWritten); // Moves the current cursor position to // 900 dots from the left margin and // 1200 dots down from the top margin st1="x1b*p900x1200Y"; PrintDirect.WritePrinter(lhPrinter, st1, st1.Length, ref pcWritten); // Specifies a rectangle with a width // of 606 dots, a height of 6 dots, and a // fill pattern of solid black st1="x1b*c606a6b0P"; PrintDirect.WritePrinter(lhPrinter, st1, st1.Length, ref pcWritten); // Moves the current cursor position to 1500 // dots in from the left margin and // 600 dots down from the top margin st1="x1b*p1500x600Y"; PrintDirect.WritePrinter(lhPrinter, st1, st1.Length, ref pcWritten); // Specifies a rectangle with a width of 6 dots, // a height of 600 dots, and a // fill pattern of solid black st1="x1b*c6a600b0P"; PrintDirect.WritePrinter(lhPrinter, st1, st1.Length, ref pcWritten); // Send a form-feed character to the printer st1="f"; PrintDirect.WritePrinter(lhPrinter, st1, st1.Length, ref pcWritten); } catch (Exception e) { Console.WriteLine(e.Message); } PrintDirect.EndPagePrinter(lhPrinter); PrintDirect.EndDocPrinter(lhPrinter); PrintDirect.ClosePrinter(lhPrinter); } }
Using this code will enable us to drive a printer at its maximum output rate.
GDI+: The Next-Generation Graphics Interface
Your First GDI+ Application
The Graphics Class
Working with Brushes and Pens
Colors, Fonts, and Text
Rectangles and Regions
Working with Images
Advanced Imaging
Advanced 2D Graphics
Transformation
Printing
Developing GDI+ Web Applications
GDI+ Best Practices and Performance Techniques
GDI Interoperability
Miscellaneous GDI+ Examples
Appendix A. Exception Handling in .NET