Home :: DOM :: ASV Wrapper

  1. Introduction
  2. Implementation of ASV3Sharp
    1. System.Type Class
    2. Invoking a Method
    3. Getting a Property Value
    4. Setting a Property Value
    5. ASVObject
    6. ASV3Sharp
  3. Setup
    1. DLLs
    2. Adding the ASV Control to Your Windows Form Toolbox
    3. ActiveX Wrapper DLLs
  4. Use
    1. Method Calls and Properties
    2. Event Handlers
    3. Calling ECMAScript Functions from C#
    4. Calling C# Methods from ECMAScript
  5. Known Problems
  6. Revision History
    1. Version 1.2
    2. Version 1.1
    3. Version 1.0
  7. Download
    1. Libraries
    2. Source
    3. Demo Project

Introduction

Adobe has built their SVG Viewer as ActiveX component. This gives Windows developers the wonderful opportunity of hosting the SVG Viewer control within their own applications. This section of this site discusses how to use the SVG control within a C# application.

Unfortunately, the most recent release of Adobe's Viewer (aka. ASV), version 3.0, only exposes the SVGWindow's interface. If you need to manipulate the actual SVG DOM itself, you will need to use C#'s reflection capabilities. Stefan Goessner has an excellent introduction, along with code, at his site. Note that the technique used on his site can be used with the final 3.0 version. In fact, his technique is being used in the library discussed on this page. Also, Jim Longson has written a very demonstrative example (requires membership to Yahoo's SVG Developers Group) which was quite helpful to me. Additional information about hosting the SVG control can be found on the SVG Wiki.

I have written a fair amount of ECMAScript with ASV, so I'm quite used to the SVG DOM API available through ECMAScript. It was a bit disappointing not to have these APIs in C# by default. Fortunately, I have spent a little time working on an IDL parser. Although the parser has yet to be completed, it is far enough along that I have been able to generate useful SVG libraries with it. I used this parser to generate a collection of wrapper classes which allow the C# developer to use the standard SVG DOM API with the SVG control. The sections that follow discuss how these classes are implemented and how to make use of them in your own C# projects.

DISLAIMER: I am quite new to C# and .NET development. It is very likely that there are better ways to implement this library. If you see something blatantly wrong or even if you just have suggestions, please feel free to contact me so I can improve the code.


Implementation of ASV3Sharp

This section has been provided to describe the lower levels of the ASV3Sharp library. Readers will gain a better understanding of how the SVG DOM API calls are being made. This knowledge may be useful in cases where you may want to bypass the ASV3Sharp calls.

The ASV control exposes the SVGWindow interface as described in Adobe's Current Support document. This interface provides enough functionality to set the current SVG file that is to be viewed in the control. Also, you get access to postURL, getURL, the svgDocument, and more. The svgDocument is of most use to DOM programmers, but, as mentioned earlier, the interface has not been exposed. Fortunately, reflection allows us to make method calls to this object with the caveat that we must know what the method name is and what parameters to pass to the method.

System.Type Class

If you look through the Visual Studio .NET documentation, you will find a class called System.Type. This class allows you to invoke methods, get properties, and set properties at runtime. NOTE: You can do much more with a Type class, but these are the only functions we are interested in here. You supply the method or property name as a string and pass additional parameters as needed. This facility is known as reflection. We will use only one method, InvokeMember, to call methods, get properties, and set properties. A parameter of type BindingFlags is used to specify which function we would like to perform.

Invoking a Method

Let's look at a sample of how to call a method. We will assume that the SVG control has already been created, initialized, assigned to ASV3Control and is displaying an SVG document.

AxSVGACTIVEXLib.AxSVGCtl svgWindow = ASV3Control.getWindow();
object svgDocument = svgWindow.getDocument();
Type   type        = svgDocument.GetType();

object svgRoot = type.InvokeMember(
    "getRootElement",
    BindingFlags.InvokeMethod,
    null,
    svgDocument,
    null);

The first thing to notice is that we use the most generic type, object, for all objects acquired through the control. Next, we grab the Type object that is associated with the object; the SVGDocument in this example. We use the InvokeMember method of the Type object to call methods, get properties and set properties of our control object. The first parameter supplies the name of the method or property in which you are interested. The second parameter is what determines the function we want to perform on the object. For our purposes, the third parameter will always be null. The fourth parameter identifies the object to use for the specified function. The last parameter is an object array and is used to pass parameters when needed.

The example calls the getRootElement() method of the svgDocument and returns the topmost svg element in the current SVG document displayed in the control.

Getting a Property Value

This next example shows how to retrieve the SVG root element by accessing a property.

AxSVGACTIVEXLib.AxSVGCtl svgWindow = ASV3Control.getWindow();
object svgDocument = svgWindow.getDocument();
Type   type        = svgDocument.GetType();

object svgRoot = type.InvokeMember(
    "rootElement",
    BindingFlags.GetProperty,
    null,
    svgDocument,
    null);

Everything looks the same as our last example. The only difference is that we specify a different BindingFlag, GetProperty, to specify that we want to retrieve the named property. Once again, the return value is the topmost svg element in the current SVG document displayed in the control.

Setting a Property Value

The next example builds on this code to set the currentScale, aka. zoom level, of the current SVG document being viewed in the control.

type = svgRoot.GetType();

type.InvokeMember(
    "currentScale",
    BindingFlags.SetProperty,
    null,
    svgRoot,
    object[] {2});

This time we use a different BindingFlag, SetProperty, to indicate that we want to set a property value. The final parameter demonstrates how parameters are passed when calling methods and setting properites. This parameter must be an object array so, we build an array even though only one value is needed when setting a property.

ASVObject

The calls to InvokeMember are fairly simple, but they are a bit too wordy for me. I created a new class called ASVObject to simplify method calls, retrieving properties and setting properites. Here's a rewrite of our previous example using the ASVObject class.

AxSVGACTIVEXLib.AxSVGCtl svgWindow = ASV3Control.getWindow();
ASVObject svgDocument = new ASVObject(svgWindow.getDocument());
ASVObject svgRoot     = new ASVObject(svgDocument.GetProperty("rootElement"));

svgRoot.SetProperty("currentScale", 2);

This adds a little bit of maintenance for the coder since you have to wrap all objects returned by the control into an ASVObject; however, once you have an ASVObject, coding gets a bit easier. You make method calls with InvokeMethod(method-name, parameter1, parameter2, ...). You get a property with GetProperty(property-name) and you set a property with SetProperty(property-name, property-value).

When you invoke a method with more than one paramter, simply add all needed parameters after the method name. Here's an example.

svgRoot.InvokeMethod(
    "insertBefore", someSVGNode, svgRoot.GetProperty("firstChild"));

ASVObject will wrap all parameters into an object array before calling the Type's InvokeMember method.

ASV3Sharp

Even though ASVObject cleans up our code a bit, it doesn't prevent us from passing incorrect parameters. For example, there would be nothing to stop you from setting the currentScale to your Application object. Of course, that's meaningless and ASV will (or should) generate an error, but it would be nicer if we found out about our errors at compile time instead of runtime. That's where the ASV3Sharp library comes in.

I have re-created SVG's IDL interface hierarchy as both C# interfaces and C# classes. All C# classes inherit from ASVObject. All methods and properties associated with an IDL interface are included within the C# class. Please note that due to multiple inheritance in the IDL interfaces, I have included all methods and properties of all ancestors within each class. Most likely this would not be the ideal way to design the SVG class hierarchy, but since we only need wrappers for ASV, I decided that the duplication would not cause any harm.

Let's look at our previous scaling example as it would be implemented with ASV3Sharp.

using KevLinDev.ASV3;

ASVWindow window = new ASVWindow( ASV3Control );
SVGDocument svgDocument = window.svgDocument;
SVGSVGElement svgRoot = svgDocument.rootElement;

svgRoot.currentScale = 2;

We start off including the ASV3 namespace to shorten type references. We wrap up the control inside of an ASVWindow object. And from there, we make calls as if we're using ASV's API directly.

Ultimately, the ASV3Sharp classes are calling the Type object's InvokeMember(), but the details have been hidden from the developer. This gives the developer the illusion that they are working directly with the SVG DOM API. The wrapper classes cause the compiler to type check at compile time thus avoiding potential runtime errors. Also, return values, for the most part, are type cast to useful types (as opposed to the object type).


Setup

Before you can start using the ASV3Sharp library, you'll need to setup a few things in your Visual Studio 7 project.

DLLs

Firstly, you need to include the ASV3Sharp.dll in your Visual Studio project. To do this right-click References in your project Solution Explorer, select Add Reference... from the popup menu, click Browse..., open ASV3Sharp.dll, and finally, click OK to accept your selection. You should see ASV3Sharp listed under the References folder.

Adding the ASV Control to Your Windows Form Toolbox

If you have never used the ASV control in a C# project, then you may need to add it to your Windows Forms Toolbox. Open a form in Design mode. Right-click anywhere inside of the Window Forms Toolbox, select Customize Toolbox... from the popup menu, scroll down through the list and check SVG Document, and click OK. You should see a new item in your Toolbox labeled SVGCtl. If you do not see SVG Document in the Customize Toolbox Dialog, then you may need to install Adobe SVG Viewer 3.0.

ActiveX Wrapper DLLs

The ASV3Sharp library uses a couple of ActiveX wrapper DLLs which allow C# to interact with ASV3. Visual Studio automatically generates these wrapper DLLs when you add an ASV control to any form in your project. You can always delete the ASV control if it is not needed; the wrapper DLL's will remain in your References folder.

For those of you who are building C# projects through the command-line, you will need to generate the ASV ActiveX wrapper DLLs manually. This can be done using the following command:

aximp "C:\WINNT\system32\Adobe\SVG Viewer 3.0\NPSVG3.dll"

This will generate the two wrapper DLLs, AxInterop.SVGACTIVEXLib.dll and Interop.SVGACTIVEXLib.dll, in the directory in which you executed the command. Make sure to include these two DLLs and the ASV3Sharp.dll in your command line when you build your project.

OK. So now we're ready to start using the ASV3Sharp Library.


Use

Method Calls and Properties

These topics with examples were discussed in the previous section, so I will be brief here. If all is working as expected, then you should be able to grab the SVG IDL (and all related IDLs), look up a method call or a property, and then use it like any other C# method or property. The following example creates the ASVWindow object, then navigates to the svg root element, and then changes the current scale.

using KevLinDev.ASV3;

ASVWindow window = new ASVWindow( ASV3Control );
SVGDocument svgDocument = window.svgDocument;
SVGSVGElement svgRoot = svgDocument.rootElement;

svgRoot.currentScale = 2;

Event Handlers

In my mind, this is where things start to get interesting. It is possible to use a C# object as an event listener. For example, this code finds an element whose id is "clickMe", attaches an event listener (which is the current C# object). When the mouse clicks on the clickMe object, the object's handleEvent method will be invoked. A message to that effect is displayed to the user.

using KevLinDev.ASV3;

public void setEventHandler() {
    ASVWindow window = new ASVWindow( ASV3Control );
    SVGDocument svgDocument = window.svgDocument;
    SVGRectElement rect = new SVGRectElement(
        svgDocument.getElementById("clickMe").Object);

    rect.addEventListener("mousedown", this, false);
}

public void handleEvent(object evt) {
    MouseEvent e = new MouseEvent(evt);

    MessageBox.Show( "clicked at (" + e.clientX + "," + e.clientY + ")" );
}

This example shows limitations and an oddity in the library. The first thing notice is the definition of the rect variable. You will notice that the call to getElementById() is wrapped insde a constructor for an SVGRectElement. The challenge here is that, according to the IDL, getElementById() returns an Element object, but addEventListener is not in the Element interface. We, possibly incorrectly, assume that the return Element is a rectangle, so we create a new SVGRectElement object.

Hidden in the details of the ASVObject class is a property called Object. This property refers to the object returned by the ASV control. We need this object for all ASVObject-derived classes when calling the constructor, hence the reason we call it here.

Most likely, this is one place that I'm showing my lack of C# knowledge. Initially I created a separate constructor that took an ASVObject as a parameter. Unfortunately, only the original constructor would be called (it uses an object for its parameter). I was unable to determine how to get around this. That aside, it seems to me that it would be much better to use a cast instead of a constructor anyway, but I couldn't think of an easy way to define the explicit cast handlers and avoid the combinatorial explosion of all possible combinations of casts. Again, this is a limitation in my current knowledge.

The next problem is not visible. It has to do with the call to addEventListener. The second parameter expects an object that implements the IEventListener C# interface. Well, in order to handle some of the hidden magic of an ASVObject, all interfaces inherit from IASVObject. All IASVObject-derived classes have to define an Object property. Since we are using the current object (this) in our addEventListener, we need to make sure that 1) our current class states that it implements IEventListener by including IEventListener in the class definition and that 2) we define an Object property (required by IEventListener since it inherits IASVObject). In this case, the Object property should simply return itself.

The last strange area is in handleEvent. In an ideal situation, handleEvent should receive an Event object, or really some sub-class of Event. However, ASV3Sharp's Event object is really a wrapper object; whereas, ASV sends handleEvent a true event object as used within ASV. We have to create a new wrapper that passes in the event object in the constructor. Once we've wrapped the event object, we can make the standard API calls (clientX and clientY in the example).

NOTE: Using a C# object as an event handler appears to work with SVG documents that use ASV's scripting engine or Microsoft's scripting engine.

Calling ECMAScript Functions from C#

Calling ECMAScript functions from C# involves using the helper methods in ASVObject. Top-level functions in ECMAScript are actually properties of the SVGWindow. The following code calls the sayHello() function defined in the SVG file passing the string "Billy" as its only parameter. As mentioned before, you can pass as many parameters as needed when invoking a method call.

using KevLinDev.ASV3;

ASVWindow svgWindow = new ASVWindow( ASV3Control );
string result = (string) svgWindow.InvokeMethod("sayHello", "Billy");
MessageBox.Show("C#: result = " + result);

The "result" variable will contain the return value from the ECMAScript sayHello() function. Just as a reminder, we are using methods from the Type class which means that InvokeMethod returns an generic object. For sake of example, we assume that sayHello() returns a string, so we cast the result to a string.

NOTE: I was unable to get this technique to work with SVG documents that use ASV's scripting engine.

Calling C# Methods from ECMAScript

This technique is not perfect, but it does allow you to call C# methods from ECMAScript. Everything in C# is an object, so if we want to call a particular method, then we need to have an instance of an object. We need to give ECMAScript access to the object we want to call, so we'll set a window property to that object.

using KevLinDev.ASV3;

ASVWindow svgWindow = new ASVWindow( ASV3Control );
svgWindow.SetProperty("csDocument", this);

Here's the catch. In order for this to work, you need to define a window property named "csDocument" in your ECMAScript in the SVG document. I have not found a way to create new properties on ECMAScript objects from C#.

To invoke, for instance, the sayHello() method on the C# object, we could use something like the following ECMAScript.

var csDocument;

function callCSharp() {
    if ( window.csDocument != null )
        csDocument.sayHello("Johnny");
}

I've run into another limitation of my knowledge here. I have successully invoked C# methods on the Form object from ECMAScript; however, I have been unable to invoke methods on any ASVObject-derived object. I get an "automation not implemented" error. If this problem can be remedied, then it would be possible to extend ASV's functionality in a way that would be almost transparent to ECMAScript.

NOTE: I was unable to get this technique to work with SVG documents that use ASV's scripting engine.

Checking for Null Return Values

All return values are wrapped within an ASVObject. So, in order to determine if a return value is null, you will need to test the return values Object property. For example, the following code will traverse all child nodes of a given node. The while expression tests if the child node is null before processing it.

node = parent.firstChild;

while ( node.Object != null ) {
    // do something with the current node
    node = node.nextSibling;
}

Known Problems

  1. Unable to get an ASVObject constructor to fire when constructing an object with an ASVObject. Apparently, the constructor that uses an Object as its parameter takes precedence over the ASVObject-based constructor.
  2. I would prefer to use casting as a means from converting more general types to more specific types. If this can be done without having to create every possible combination of type cast, then problem #1 would no longer be an issue.
  3. It would be very nice if methods that return generic nodes and elements would return a more specific type. For example, getElementById() returns an Element. I would prefer that it return, say, an SVGRectElement or whatever is appropriate for the object being returned.
  4. I am unable to create new properties on ECMAScript objects.
  5. Event handlers have to create their own ASVObject-based object. There's no way around this one since ASV is providing the object to the handler.
  6. I am unable to call the methods of an ASVObject-derived object from ECMAScript. I get an "automation not implemented" error. What's strange to me is 1) I can invoke a Form method and 2) I can use any object as an event handler. From what I've read this problem is related to COM. It seems that I have to set something up to allow this type of interaction, but it seems to me that it is already setup since I can call some methods. Any suggestions here would be greatly appreciated.
  7. I am unable to call methods from C# to ECMAScript and vice versa when using ASV's scripting engine. It seems that this should be possible since I have been able to get EventHandlers to fire in C# when using ASV's scripting engine.

I'm sure other problems will be discovered as the library gets used more. So, if you do run into a problem, please let me know and I'll add it to the list (or even better, fix it). As mentioned above, I am still learning C#, so if you find anything out of the ordinary or even if you just have suggestions, please feel free to drop me an email.


Revision History

Version 1.2

  1. Everything returned by the SVG control is contained by an ASVObject. I problem occurred when the SVG control returned a null which caused the ASVObject constructor to fire an exception. I'm now testing for nulls in the ASVObject constructor. As a side note, you will have to test if a method or property has returned null by checking the returned object's Object property. For example, if (svgNode.firstChild.Object == null) { ... }.

Version 1.1

  1. Return types which are stated to be unsigned in the IDL cannot be cast to their correct unsigned type. All types that appear as unsigned in the IDL are now signed in ASVSharp. COM appears to support unsigned types, so I think this problem may be due to the SVG control, but I'm not positive.

Version 1.0

  1. Initial release of DLLs, source, and demo project

Download

Download the library and the associated DLLs (ASV3Sharp, AxInterop.SVGACTIVEXLib.dll and Interop.SVGACTIVEXLib.dll)

DLLs.zip

Download the ASV3Sharp source

ASVSharp_1_2.zip

Download a simple browser. Supports XSLT and demonstrates techniques presented on this page

DemoProject.zip