COM – Anvil of Time https://anvil-of-time.com Nature Forges Everything on the Anvil of Time Thu, 11 Mar 2021 17:51:02 +0000 en-US hourly 1 Getting your COM component Registered for use with PowerBuilder https://anvil-of-time.com/powerbuilder/getting-your-com-component-registered-for-use-with-powerbuilder/ Tue, 06 Nov 2012 00:29:08 +0000 http://anvil-of-time.com/wordpress/?p=1508 I built a COM component to allow for the taking of pictures from a webcam in PowerBuilder. The component itself was done in Visual Studio 2010 in C# and makes use of a web camera control created to use the Directshow API library available as part of the Windows SDK. You can find more information on this here.

Using the techniques I’ve described in earlier posts, I created an interop project, dropped the webcam object on it, and exposed various methods and properties I needed to make the thing work. Testing on my PC worked fine; issues arose when I tried to register the thing on another PC.

The deployment of this component involves two dll files, one for the web camera control and the other for the interop wrapper control that the webcam sits on. I’m sure there are ways to incorporate the one into the other without the need for two dlls but that’s something to tackle later.

The web camera control project was set up as a standard class library. Since the second project I started with utilized the “VB6 Interop UserControl” template, it was already marked as COM visible when it was created. Once the web camera project was built I put a reference to it into my second project and made sure the setting to ‘copy local’ was set.

Once the project was complete and built, I could place the control onto a window in PowerBuilder and use it without problems. When I go to register the dlls on a separate PC (Windows 7 64 bit) I’m going to use the regasm utility. I copy all the files from the second project \bin folder to the PC then, from a command prompt, execute “Regasm webcamera.dll”.

This gives me the reply: RegAsm : warning RA0000 : No types were registered.

I do some searching on the internet and MSDN I find and set up the following in my projects.

Flag the Web Camera Control project to be COM visible. See the following:

Sign the assembly. You can create the strong name key file from the dropdown.

Do this for both projects (each has its own key file).

Copy all the files from your project’s output folder to the PC you wish to register the control on and execute the following for each project:

regasm PBWebcam.dll /codebase /tlb: PBWebcam.tlb
regasm WebCameraController.dll /codebase /tlb: WebCameraController.tlb

Leaving out the “/tlb” section will register the control, and you can see it in PowerBuilder, but you won’t have access to any methods/properties on the control. For further information on regasm go here.  You can avoid some of this mess by putting the dll into the Global Assembly Cache (GAC) but that’s a whole different can of….

]]>
PowerBuilder – Using .Net Visual Controls in PB Classic Applications https://anvil-of-time.com/powerbuilder/powerbuilder-using-net-visual-controls-in-pb-classic-applications/ Tue, 05 Jun 2012 22:27:24 +0000 http://anvil-of-time.com/wordpress/?p=1335 This article describes the techniques and code used during my presentations at the Carolina Code Camp 2012 and at the May 2012 meeting of the North Carolina PowerBuilder User Group.

The techniques described here utilize Visual Basic .Net (coded in Visual Studio 2010) with the Interop Forms Toolkit available from Microsoft. The Interop Forms Toolkit was originally intended as a ‘bridge’ between the VB 6 world and VB .Net. It allows you to host any .Net control in any Win32 application that supports COM. The current version supports Visual Studio 2005, 2008, and 2010.

In this example we are creating a horizontal track bar control to use in a PB application (PB12.5). Why not use the PB control you say? Well, we want one which allows us to control the background color.

First create a project in Visual Studio and under ‘Visual Basic’ – ‘Windows’ choose the VB6 Interop UserControl. Give the control a meaningful name at the bottom, I picked ‘vb_trackbar’.

In the Solution Explorer you should see a number of components which are automatically created. Almost all the work is done in the ‘InterobUserControl.vb’ element

Double click on the ‘InteropUserControl.vb’ element to bring it up in design mode so the actual control can be built.

Choose a Trackbar from the tools window in Visual Studio

and drop it on the usercontrol. Rearrange its size and position as appropriate

Now choose a Label and drop it on the control.

Change the backcolor of the trackbar to yellow; the name to TB; the Margin to 0,0,0,0; the Maximum to 100; the TickFrequency to 10; and the TickStyle to Both.

Modify the background color of the label and the usercontrol itself to yellow. Rename the control to ‘IOP_Trackbar’, the label to ‘LB_Value’, and give the label a border.

Right click on the ‘InteropUserControl.vb’ element from the Project window and choose ‘View Code’

You will see code like this (this is from Visual Studio 2010).

Here is the code to add.

   'Please enter any new code here, below the Interop code

    ' event to show in PB
    Public Event tbScroll()
    Public Event tbValuechanged()

    Private Sub TB_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TB.Scroll
        LB_Value.Text = TB.Value.ToString()
        RaiseEvent tbScroll()
    End Sub
    Private Sub TB_Valuechanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TB.ValueChanged
        RaiseEvent tbValuechanged()
    End Sub

    ' method to call from PB
    Public Sub tbSetBackColor(ByVal testval As Integer)
        Dim myColor As Color = ColorTranslator.FromWin32(testval)

        TB.BackColor = myColor ' set color on trackbar
        LB_Value.BackColor = myColor ' set color on label
        BackColor = myColor ' set color on interop control
    End Sub
    ' Property get/set
    Public Shadows Property TBBackgroundColor() As Integer
        Get
            Return ActiveXControlHelpers.GetOleColorFromColor(TB.BackColor)
        End Get
        Set(ByVal value As Integer)
            TB.BackColor = ActiveXControlHelpers.GetColorFromOleColor(value)
        End Set
    End Property
    ' this is the value of the trackbar
    Public Shadows Property TBValue() As Integer
        Get
            Return TB.Value
        End Get
        Set(ByVal value As Integer)
            TB.Value = value
        End Set
    End Property

In the first portion there are two events we want to expose to PowerBuilder so we can write code in our PB app to respond to the control. These are ‘tbScroll’ and ‘tbValuechanged’. One is triggered in response to the Scroll event on the trackbar (which we named ‘TB’) and the other by the Valuechanged event. In the tbScroll event we are setting the Text in the label (we called it ‘LB_Value’) on the userobject to the Value property of the Trackbar.

We also are creating a method, tbSetBackColor, which can be called from our PB app to set the background colors of the userobject, trackbar, and label.

Finally we are exposing two properties of the Trackbar so we can either set or get them from our PB app. These are called ‘TBBackgroundColor’ and ‘TBValue’.

The last thing we need to do in Visual Studio is to add an Interface to the project so the events we are exposing can be seen from within PowerBuilder. From the Project menu choose ‘Add New Item’.

Then under the ‘Common Items’ choose ‘Interface’

In the code for the interface, replace it with the following.

<guid("ca16a9e8-02c7-43d2-90f6-4ef30885a338"), _="" interfacetype(cominterfacetype.interfaceisidispatch)=""> _
Public Interface iIOP_Trackbar
    <dispid(1)> _
    Sub tbScroll()
    <dispid(2)> _
    Sub tbValuechanged()
End Interface</dispid(2)></dispid(1)></guid("ca16a9e8-02c7-43d2-90f6-4ef30885a338"),>

The first thing to do now is to replace the example GUID with one of your own (Visual Studio can generate one). Also give the Interface a more meaningful name – ‘iIOP_Trackbar’ in this example. Within the interface definition are the two events which will be visible from within PowerBuilder.

With this done you can now build the solution from the ‘Build’ menu

This will create and register the dll on the machine.

Now in PowerBuilder the control will be available when you choose to add an userobject.

The events are visible in Powerscript.

Now in the tbscroll event on our ole control we put the following.

long ll_val
//report on the current value of the control
//this uses the 'getter' method to return the track bar value from the .net control
ll_val = ole_1.object.event tbvalue()
//move the PB control
htb_1.position = ll_val
// show value
st_1.text = string(ll_val)

Code in the tbvaluechanged event on the ole control

sle_2.text = 'tbvaluechanged at ' + string(Now(),'hh:mm:ss')

Code to set the color of the ole control.

long ll_color

// set the desired color on the control
IF sle_1.text = string(RGB(0,174,221)) THEN
	ll_color = 15780518
ELSE
	ll_color = RGB(0,174,221)
END IF
// call method on .Net control to set the background colors
ole_1.object.event tbsetbackcolor(ll_color)
// get the color value using the 'getter' method in .Net class
sle_1.text = string(ole_1.object.tbbackgroundcolor())

Running the application gives the following

See the original control background color. Clicking the Set Color button gives this

Moving the trackbar ole control results in this.

There are a couple of things to look out for when using the techniques described here. The first is that any events exposed via the .Net Interface must have some sort of Powerscript in them within PB. Failure to do this results in an ‘unhandled exception’ application crash. There may be some way to prevent this from within Visual Basic but I am not sure.

The other issue is that if you have to change your VB.Net component you must re add it to your window/form within PowerBuilder. Usually I will add a second ole object to the existing window, transfer all the code from the old ole control, delete the original control, and finally rename the new control to that of the old control. This is the biggest issue I have with working with ‘roll your own’ ole controls. If anyone knows of a work around for this I would like to hear it.

Visual studio and PB files can be downloaded from here.

The video presentation of this is on YouTube.

]]>
PowerBuilder – Accessing C# Classes via COM to Capture a Screenshot https://anvil-of-time.com/powerbuilder/powerbuilder-accessing-c-classes-via-com-to-capture-a-screenshot/ Wed, 13 Apr 2011 01:20:38 +0000 http://anvil-of-time.com/wordpress/?p=739 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.

]]>