Powerbuilder – Accessing C# Classes via COM to Capture a Screenshot

Posted on Tuesday, April 12th, 2011 at 8:20 pm in

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

3 Comments

Add your comment

  1. thebalan - May 5, 2012 at 4:42 am

    Great. I have been looking for this solution for many years.
    GOOD to see GOOD news in Powerbuilder.

    Thebalan
    Senkang
    Singapore

  2. Ringbretson - July 16, 2013 at 9:18 am

    I’m able to access C# dll’s from PowerBuilder as long as I’m running PB on the PC that created the C# Dll’s. Have you been successful in publishing a C# DLL’s to another PC and then accessing the published DLL’s from PowerBuilder? If so, I would appreciate any help you could offer. I’ve tried this on windows’s 7 PC’s with both 32 and 64 bit O/S without success. I mention this because I believe the registry process only register’s the DLL as 64 bit on a 64 bit PC and PB only accesses 32 bit DLL’s. Thanks in advance for any help you can offer.

Leave a Reply

Top