powering your gadgets with activex · web viewa pictorial tutorial by bruce williams...

28
Powering Your Gadgets With ActiveX A Pictorial Tutorial By Bruce Williams ([email protected]) After you’ve written your first couple of sidebar gadgets, you may find yourself wanting to access system functionality that isn’t exposed by the gadget APIs. If you know how to do what you want using C++, then all you need is an ActiveX control in order to expose that functionality to your gadget. This series of blog posts will show you how. A Simple Gadget Lets create a simple gadget we can use to demonstrate the technique. First, create a gadget folder: cd %LOCALAPPDATA%\Microsoft\Windows Sidebar\Gadgets md test.gadget cd test.gadget Create a file named ‘gadget.xml’ with the following contents: <?xml version="1.0" encoding="utf-8" ?> <gadget> <name>ActiveX </name> <namespace>test</namespace> <version>1.0.0.0</version> <author name="Your Name"> <info url="www.your-url.com" />

Upload: others

Post on 07-Aug-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Powering Your Gadgets With ActiveX

A Pictorial Tutorial

By Bruce Williams ([email protected])

After you’ve written your first couple of sidebar gadgets, you may find yourself wanting to access system functionality that isn’t exposed by the gadget APIs. If you know how to do what you want using C++, then all you need is an ActiveX control in order to expose that functionality to your gadget. This series of blog posts will show you how.

A Simple GadgetLets create a simple gadget we can use to demonstrate the technique. First, create a gadget folder:

cd %LOCALAPPDATA%\Microsoft\Windows Sidebar\Gadgetsmd test.gadgetcd test.gadget

Create a file named ‘gadget.xml’ with the following contents:

<?xml version="1.0" encoding="utf-8" ?><gadget> <name>ActiveX </name> <namespace>test</namespace> <version>1.0.0.0</version> <author name="Your Name"> <info url="www.your-url.com" /> </author> <copyright>2006</copyright> <description>Call an ActiveX control</description> <hosts> <host name="sidebar"> <base type="HTML" apiVersion="1.0.0" src="gadget.html" />

Page 2: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

<permissions>Full</permissions> <platform minPlatformVersion="0.3" /> </host> </hosts> <version value="1.0.0.0" MinPlatformVersion="0.1"/></gadget>

And create a file named ‘gadget.html’ with these contents:

<HTML><SCRIPT>

function main(){ try { out("Hello, world!"); } catch (e) { content.innerText = "ERROR: " + e + " : " + e.name + " : " + e.message; }}

function out(text){ content.innerHTML += "<DIV style='height:20;overflow:hidden'>" + text + "</DIV>";}

</SCRIPT></HEAD><BODY STYLE="height:300;width:600;background-color:gray" onload="javascript:main();"><DIV id="content" style="height:270;width:580;border-style:solid;overflow:auto"></DIV></BODY></HTML>

Page 3: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Now we have a simple but functional gadget. If you like, you can open up your gadget gallery pane and add this gadget to your sidebar; it doesn’t yet create or call an ActiveX control, but it will.

Taking ControlNext, let’s create a simple ActiveX control. I’m going to use Visual Studio and the ATL framework; together they make it very easy. Before we start, though, we need to decide what functionality we will implement in our ActiveX control. To start off, I’m going to make a function named ‘Echo’ that takes a string, and simply returns the same string. Later we’ll do something a little more interesting.

Start up visual studio. From the File menu, select New Project. You’ll get a “New Project” dialog box, where you’ll pick a project template named “ATL Project”, and give the project a name “TestActiveX”:

Page 4: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Next the ATL Project Wizard will pop up – just click the “Finish” button, we’ll accept all the default settings:

Page 5: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Switch your VS explorer window over to “Class View”, right-click on “TestActiveX”, and add a new class to your project:

Page 6: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

The “Add Class” dialog will pop up – select the “ATL Simple Object” template and click the “Add” button:

Page 7: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Now you’ll see the “ATL Simple Object Wizard” – give the object a simple name (I used “TestControl”), and click “Next”:

Page 8: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

On the “Options” pane that comes up next, go ahead and select the “ISupportErrorInfo” option – I want the control to return decent error information to the calling script if something goes wrong. Then click “Finish”:

Page 9: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Now let’s add the function that’s actually going to do the work. Add a new method to your ITestControl interface, as shown below:

Page 10: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Yet another wizard will pop up – in this one, tell VS that you want to name your method “Echo”, and add the first of two parameters by entering the appropriate information and clicking “Add”, as shown below:

Page 11: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Now tell VS that the method should return a string back to the script. We do this by selecting a parameter type of “BSTR *” and selecting the parameter attributes as shown below:

Page 12: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

All the rest of the method default values are fine, so click “Finish” to complete the operation and add the “Echo” method to our ActiveX object:

Page 13: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Now we have a fully-functional ActiveX control, but it doesn’t do anything – remember, we want the “Echo” function to return the string that is passed in. So let us add that bit of implementation. Double-click on the “Echo” method of the CTestControl class, in the class explorer:

Page 14: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Where it says “TODO: Add your implementation code here”, add the following source code:

STDMETHODIMP CTestControl::Echo(BSTR input, BSTR* output){

// TODO: Add your implementation code here*output = SysAllocString(input);if (*output){

Page 15: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

return S_OK;}return AtlReportError(

CLSID_TestControl,L"Could not allocate echoed string",GUID_NULL, // system-defined error codeE_OUTOFMEMORY);

}

These lines appear simple, but they cover some important aspects of automation code. Note the use of the BSTR type, and SysAllocString. These are core features of almost all scriptable ActiveX controls, so if you’re not familiar with them, you should spend a few minutes up on http://msdn.microsoft.com/library and look them up.

Another interesting element – the use of AtlReportError. Remember we selected the “ISupportErrorInfo” option when we created our control class? This is where we close the loop on that functionality – you can use AtlReportError to return a nice legible error message to the calling script; in this case, the text “Could not allocate echoed string”. If this error is returned, the script will see an exception coming out of the function call, and can get that text message out of the exception object. (If you go back and look, you’ll see that our test gadget will catch and display exception errors.)

Now our initial ActiveX control implementation is complete – you can press F7 to build and register your new ActiveX control.

What About The Gadget?Ok, now we have this cool new Echo function – but we wanted a gadget that would use this functionality. Let’s do it; replace this line in your gadget.html:

out("Hello, world!");

With these lines:

var myControl = new ActiveXObject(“TestActiveX.TestControl”);out(myControl.Echo(“Bruce Williams”));

Add the gadget to your sidebar, and viola!

Page 16: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

But – That’s Dumb!Ok, you’re right – an “Echo” function isn’t really interesting – you could do that with no ActiveX controls at all. What we really want is a function that does something you just can’t do from javascript, or using the built-in gadget APIs. So we’re going to do something else. We are going to add a function that returns a list of all the software installed on the machine.

Let’s get started…

First, there is some C++ book-keeping we have to do. If you’re an old pro with C++, just bear with me while I cover the basics.

Open up the project properties:

Page 17: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Make sure the Platform SDK include files are in our include file path; in particular, we need to make sure we can access MSI.H. You can see that my include files are at “D:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\Include”, but your location may be different, depending on where you installed VS and/or the Platform SDK:

Page 18: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Add a preprocessor definition (“_WIN32_MSI=300”) telling the compiler that we want to support a reasonably-current version of the MSI APIs:

Page 19: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Add the library MSI.LIB to the list of libraries that get linked into your code. Note that the path you give to MSI.LIB will likely be different from my example in the picture, depending on where you installed VS and/or the Platform SDK:

Page 20: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

Ok, C++ book-keeping is complete; let’s get back to the code.

Go back to your “ITestControl” interface in the class viewer, right-click on it, and add another method. Call it “GetProducts”, and give it a [retval] parameter of type “VARIANT *”, as follows:

Page 21: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

The “VARIANT” type is another one of the tools that you’ll use all the time if you’re writing scriptable activeX controls – you will want to be familiar with them. Unfortunately, I’ve found the material available on the web to be a bit scattered, dated, and otherwise difficult to understand, so feel free to go to our forums at http://www.microsoftgadgets.com/forums if you have questions that the docs don’t answer.

Now go to the implementation stub of the GetProducts function, and fill it in as shown below. Note that this code also demonstrates how to return an array (in particular, a SAFEARRAY), back to the calling script. Unfortunately, this support for arrays is not completely transparent to the script, as well see next.

#include "msi.h"

#define ERROR_CLEANUP(hrError, description) \{\

AtlReportError(CLSID_TestControl, L"GetProducts: " description); \hr = hrError; \goto cleanup; \

}

Page 22: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

#define MAX_PRODUCTS 1000

STDMETHODIMP CTestControl::GetProducts(VARIANT* products){

WCHAR productCode[300]; // needs to be big enough to hold a GUID in string form: {00000319-0000-0000-C000-000000000046}LPWSTR productNames[MAX_PRODUCTS];unsigned int count = 0;int allocatedProductNameBuffers = 0;HRESULT hr = E_FAIL;SAFEARRAY * psa = NULL;

MSIINSTALLCONTEXT context;UINT enumStatus = MsiEnumProductsEx(

NULL,NULL,MSIINSTALLCONTEXT_USERMANAGED | MSIINSTALLCONTEXT_USERUNMANAGED | MSIINSTALLCONTEXT_MACHINE,0,productCode,&context,NULL,NULL);

if (enumStatus != ERROR_SUCCESS){

return AtlReportError(CLSID_TestControl,L"First attempted enumeration of installed products failed",GUID_NULL, // system-defined error codeHRESULT_FROM_WIN32(enumStatus));

}while (enumStatus == ERROR_SUCCESS){

DWORD productNameLength = 0;UINT infoStatus = MsiGetProductInfoEx(

productCode,NULL,context,INSTALLPROPERTY_INSTALLEDPRODUCTNAME,

Page 23: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

NULL,&productNameLength);

if (infoStatus != ERROR_SUCCESS){

ERROR_CLEANUP(HRESULT_FROM_WIN32(infoStatus), L"failed to get the length of a product name");}productNameLength++; // make room for the trailing NULLWCHAR * productNameBuffer = new WCHAR[productNameLength + 1];if (productNameBuffer == NULL){

ERROR_CLEANUP(E_OUTOFMEMORY, L"failed to allocate product name buffer");}productNames[allocatedProductNameBuffers++] = productNameBuffer;infoStatus = MsiGetProductInfoEx(

productCode,NULL,context,INSTALLPROPERTY_INSTALLEDPRODUCTNAME,productNameBuffer,&productNameLength);

if (infoStatus != ERROR_SUCCESS){

ERROR_CLEANUP(HRESULT_FROM_WIN32(infoStatus), L"error returned while querying a product name");}count++;if (count == MAX_PRODUCTS){

ERROR_CLEANUP(E_FAIL, L"too many products found; couldn't list them all");}enumStatus = MsiEnumProductsEx(

NULL,NULL,MSIINSTALLCONTEXT_USERMANAGED | MSIINSTALLCONTEXT_USERUNMANAGED | MSIINSTALLCONTEXT_MACHINE,count,productCode,&context,NULL,NULL);

Page 24: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

}if (enumStatus != ERROR_NO_MORE_ITEMS){

ERROR_CLEANUP(HRESULT_FROM_WIN32(enumStatus), L"failed to enumerate the next product in the product list");}psa = SafeArrayCreateVector(VT_VARIANT, 0, count);if (psa == NULL){

ERROR_CLEANUP(E_OUTOFMEMORY, L"could not allocate safe array with SafeArrayCreateVector");}VARIANT * v = (VARIANT *)psa->pvData;// not entirely sure if I need this next loop, but lets be safefor (unsigned int i = 0; i < count; i++){

VariantInit(&v[i]);}for (unsigned int i = 0; i < count; i++){

v[i].bstrVal = SysAllocString(productNames[i]);if (v[i].bstrVal == NULL){

ERROR_CLEANUP(E_OUTOFMEMORY, L"could not allocate a BSTR for a product name in the return array");}v[i].vt = VT_BSTR;

}products->vt = VT_ARRAY | VT_VARIANT;products->parray = psa;hr = S_OK;

cleanup:

for (int i = 0; i < allocatedProductNameBuffers; i++){

delete [] productNames[i];}if (FAILED(hr)){

if (psa)

Page 25: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

{SafeArrayDestroy(psa);

}}return hr;

}

And there you have it – a fully-functional ActiveX control, callable from script, that actually does something interesting.

What About The Gadget? – Part IILast but not least, we need to actually call our new function from our gadget, and display the result.

By the way, remember I said that the support for returning arrays from an ActiveX control was less than perfect? Here is where we see why. The array is returned as a SAFEARRAY. The javascript can convert that into something called a VBArray:

var vbArray = new VBArray(safeArray);

And now, we can convert a VBArray into a native javascript array using the toArray() method:

var nativeArray = vbArray.toArray();

To make my life easier, I’ve gone ahead and folded this into a helper function:

function arrayFromSafeArray(safeArray){

var vbArray = new VBArray(safeArray);return vbArray.toArray();

}

So, go ahead and add that helper function to your gadget script, right after where the out() function is defined. Also, replace these lines:

var myControl = new ActiveXObject(“TestActiveX.TestControl”);out(myControl.Echo(“Bruce Williams”));

Page 26: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

With these lines:

var myControl = new ActiveXObject("TestActiveX.TestControl");var productArray = arrayFromSafeArray(myControl.GetProducts());for (var i in productArray){

out(productArray[i]);}

And we’re done! Go ahead and add your newly-updated gadget to your sidebar:

DeploymentThere is one little detail that I have very cleverly not dealt with in this article, and that is deployment. When you build an ActiveX control in Visual Studio, VS will go to the trouble of registering that control for you – that’s how the gadget knows what to load when you say “new ActiveXObject(‘TestActiveX.TestControl’)”. If you want anyone else to use your gadget, though, you’ve got to get the control installed on their machine, as well as getting the gadget installed. I’m not going to go into the details of how to construct a robust control installer program, but I’ll give you the simple manual steps that I use for testing purposes:

1. If VS or the redistributable libraries aren’t already installed on the target machine, you’ll need to install them. This is necessary because our ActiveX control uses the ATL library, so we need to make sure the ATL library is installed. On my machine, the redistributable installer is located at D:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\BootStrapper\Packages\vcredist_x86\vcredist_x86.exe

Page 27: Powering Your Gadgets With ActiveX · Web viewA Pictorial Tutorial By Bruce Williams (Bruce.Williams@microsoft.com) After you’ve written your first couple of sidebar gadgets, you

2. After copying the control (TestActiveX.dll) to the target machine, register it as follows; from an elevated command window run: “regsvr32.exe TestActiveX.dll”

3. Copy your gadget files (gadget.xml, gadget.html) into a compressed ZIP file named “TestActiveX.zip”, then rename the ZIP file to “TestActiveX.gadget”. Copy this gadget file to the target machine, and double-click on it to install the gadget.

ConclusionI hope you enjoyed this tutorial, and found it useful. I welcome any feedback and corrections – send them to [email protected].