PowerBuilder – Accessing C# Classes via COM to Capture a Screenshot
I was working with setting up screen capture functionality from within a PB11.5 application recently and came up against a wall which forced me to rethink my strategy. Initially I was using External Functions to the Windows gdi32.dll but had issues with my code causing it to ‘stop working’ after a few files were written. Things were working fine and then, bam!, zero byte files are being created. My assumption was that some memory was not being addressed/cleared correctly, blah, blah, blah.
I then decided to do the major work in .Net using C# since everything was being done behind the scenes (no visual components needed). I had the 2008 Express version loaded on my pc and with a little searching I had enough code samples to create my class. My C# class also uses gdi32 and some user32.dll function calls as well (similar to what I was attempting in PowerBuilder).
After creating a new Class Library project in Visual Studio and renaming a few things appropriately, the namespace/class declaration portion of the C# code looks like this:
namespace ScreenCapture { [ComVisible(true)] [ClassInterface(ClassInterfaceType.AutoDual)] [ProgId("ScreenCapture.ScreenCapture")] public class ScreenCapture {
The three elements to pay particular attention to are the attributes (ComVisible, ClassInterface, and ProgId). You also need to make the project Com Visible by going to the properties of the project, selecting the Build Tab and checking the ‘Register for COM interop’ checkbox.
While in the project property window you should also sign the application which (among other things), assigns a GUID to it. Go to the Signing Tab and click on the ‘Sign the assembly’ checkbox. From the ‘Choose a strong name key file:’ dropdown choose, ‘New…’. Type in a keyfile name and uncheck the ‘Protect my key file with a password’ option then click ‘Ok’. When you save the project you will now have a .snk file associated with it.
The C# ‘ScreenShot.cs’ file is as follows:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Drawing; using System.Drawing.Imaging; namespace ScreenCapture { [ComVisible(true)] [ClassInterface(ClassInterfaceType.AutoDual)] [ProgId("ScreenCapture.ScreenCapture")] public class ScreenCapture { /// /// Creates an Image object containing a screen shot of the entire desktop /// public Image CaptureScreen() { return CaptureWindow(User32.GetDesktopWindow()); } /// /// Creates an Image object containing a screen shot of a specific window /// ///The handle to the window. /// public Image CaptureWindow(IntPtr handle) { // get te hDC of the target window IntPtr hdcSrc = User32.GetWindowDC(handle); // get the size User32.RECT windowRect = new User32.RECT(); // size is entire screen User32.GetWindowRect(handle, ref windowRect); int width = windowRect.right - windowRect.left; int height = windowRect.bottom - windowRect.top; // create a device context we can copy to IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc); // create a bitmap we can copy it to, // using GetDeviceCaps to get the width/height IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height); // select the bitmap object IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap); // bitblt over GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY); // restore selection GDI32.SelectObject(hdcDest, hOld); // clean up GDI32.DeleteDC(hdcDest); User32.ReleaseDC(handle, hdcSrc); // get a .NET image object for it Image img = Image.FromHbitmap(hBitmap); // free up the Bitmap object GDI32.DeleteObject(hBitmap); return img; } /// /// Captures a screen shot of a specific window, and saves it to a file /// /// /// /// public void CaptureWindowToFile(IntPtr handle, string filename, ImageFormat format) { Image img = CaptureWindow(handle); img.Save(filename, format); } /// /// Captures a screen shot of the entire desktop, and saves it to a file /// /// /// public void CaptureScreenToFile(string filename, ImageFormat format) { Image img = CaptureScreen(); img.Save(filename, format); } /// /// Called by external app to capture the screen and save it to the specified file /// ///Path and name of file ///Image format PNG, JPG, BMP, default GIF public void ExtCaptureScreenToFile(string filename, string sformat) { if (sformat == "PNG") { CaptureScreenToFile(filename, ImageFormat.Png); } else if (sformat == "JPG") { CaptureScreenToFile(filename, ImageFormat.Jpeg); } else if (sformat == "BMP") { CaptureScreenToFile(filename, ImageFormat.Bmp); } else { CaptureScreenToFile(filename, ImageFormat.Gif); } } /// /// Gdi32 API functions /// private class GDI32 { public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter [DllImport("gdi32.dll")] public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop); [DllImport("gdi32.dll")] public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight); [DllImport("gdi32.dll")] public static extern IntPtr CreateCompatibleDC(IntPtr hDC); [DllImport("gdi32.dll")] public static extern bool DeleteDC(IntPtr hDC); [DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr hObject); [DllImport("gdi32.dll")] public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); } /// /// User32 API functions /// private class User32 { [StructLayout(LayoutKind.Sequential)] public struct RECT { public int left; public int top; public int right; public int bottom; } [DllImport("user32.dll")] public static extern IntPtr GetDesktopWindow(); [DllImport("user32.dll")] public static extern IntPtr GetWindowDC(IntPtr hWnd); [DllImport("user32.dll")] public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC); [DllImport("user32.dll")] public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect); } } }
After building the code in Visual Studio I went to PowerBuilder.
To use the component create the OLE object.
iole = CREATE OLEObject // connect to C# class li_rc = iole.connecttonewobject("ScreenCapture.ScreenCapture") IF li_rc <> 0 THEN DESTROY iole END IF
Then to use the component my code looks like this:
CHOOSE CASE is_filetype CASE '.jpg' ls_formatparm = 'JPG' CASE '.bmp' ls_formatparm = 'BMP' CASE '.png' ls_formatparm = 'PNG' CASE ELSE ls_formatparm = 'GIF' END CHOOSE // ls_fname specified previously iole.ExtCaptureScreenToFile(ls_fname,ls_formatparm)
Pretty simple and straightforward.
You might also be interested in