How can I use the WinFax Pro COM objects from a .NET app?

923 views Asked by At

I know, WinFax Pro is so 1998.

(note: this is not WinFax.dll, which is apparently part of Windows. This is WinFax Pro, a separate commercial add-on app originally from Delrina, and later acquired by Symantec).

I'm working in an office that still uses WinFax Pro as an operational system. They have customer fax numbers stored in the WinFax Pro "PhoneBook", and use it to notify customers of service visits. The way it works now is, someone looks at the (printed) schedule, generated from a Mac Calendar, then clicks on all the appropriate entries in the WinFax phonebook, to send a notification fax.

This is like something We used to call "swivel chair" integration, but that referred to 2 screens. This isn't even 2 screens - it's one sheet of paper and one screen.

Anyway I'm trying to automate it and having trouble.

The good news:
- WinFax Pro exposes its functions as COM objects: WinFax.SDKSend for the fax sxending engine; WinFax.SDKPhoneBook for the address book, and so on.
- WinFax Pro ships a type library, wfxctl32.tlb, that describes these various COM objects. - I am able to successfully use the WinFax.SDKSend object from .NET (C#), via wrappers generated from tlbimport. (I'm using .NET 3.5, can't do .NET 4.0) .

The bad news:
I haven't been able to invoke any of the methods on WinFax COM objects other than WinFax.SDKSend. The signatures don't look any more complicated than those in WinFax.SDKSend, but I keep getting exceptions.

The C# code:

public void Run()
{
    var pb = new wfxctl32.CSDKPhoneBook();
    string id = pb.GetFolderListFirst(1, "");
}

The exception:

Exception: System.InvalidCastException: Unable to cast COM object of type 'wfxctl32.CSDKPhoneBookClass' to interface type 'wfxctl32.ISDKPhoneBook'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{A67FCC81-9949-11D0-961E-444553540000}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

at System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters)
at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)
at wfxctl32.CSDKPhoneBookClass.GetFolderListFirst(Int16 standardFolder, String folderID)

3

There are 3 answers

3
Cheeso On BEST ANSWER

I opened up the OleView tool, part of the Windows SDK, and I can see that the COM interface for the WinFax.SDKPhoneBook is not present on the object. Hmm, that is a surprise. The interface is described by the type library, and tlbimport.exe generates a wrapper for it. Also the interface is documented in the WinFax Pro SDK PDF doc. But I could not find a single example of anyone successfully using the early-bound interface for WinFax.SDKPhoneBook.

When I tried this with javascript calling into the COM object, it worked just fine.

function say(x){ WScript.Echo(x); }

var Folder = function(id) {
    this.Id = id;
    this.DisplayName = null;
    this.Parent = null;
};

Folder.prototype.GetFolderName = function() {
    if (this.DisplayName === null) {
        this.DisplayName = comObject.GetFolderDisplayName(this.Id);
    }
    return (this.Parent === null) ? this.DisplayName
        : this.Parent.GetFolderName() + "/" + this.DisplayName;
};

var comObject = new ActiveXObject("WinFax.SDKPhoneBook");

var GetPbFoldersForId = function(firstId) {
    // stage 1 - do searches for folders
    var list = [];
    var id = firstId;
    do {
        list.push(new Folder(id));
        id = comObject.GetFolderListNext();
    } while (id != "");

    // stage 2 - get subfolders, if any, for each folder
    var subs =[];
    for (var i=0; i<list.length; i++) {
        id = comObject.GetFolderListFirst(0,list[i].Id);
        if (id != "") {
            var a = GetPbFoldersForId(id);  // recurse
            for (var j=0; j < a.length; j++) {
                if (a[j].Parent === null) {a[j].Parent = list[i];}
                subs.push(a[j]);
            }
        }
    }

    for (var k=0; k<subs.length; k++) {
        list.push(subs[k]);
    }

    return list; // a list of folders
};

var id = comObject.GetFolderListFirst(1, "");
Folders = GetPbFoldersForId(id);

for (var k=0; k<Folders.length; k++) {
    say(Folders[k].GetFolderName());
}

That led me to conclude that the WinFax Pro COM interfaces are not dual interfaces - they are IDispatch (late bound) only, and are accessible naturally from VB6, VBScript, Javascript, Perl, Python, and other late-bound languages, but not directly from .NET languages like C# or VB.NET.


Does C# .NET support IDispatch late binding? tells me how to connect with IDispatch interfaces from C#. Using that, I can do something like this:

public sealed class PhoneBook // a singleton
{
    Object comObject;
    Type type;

    private readonly static PhoneBook _instance = new PhoneBook();
    public static PhoneBook Instance  { get { return _instance; } }

    private PhoneBook()
    {
        var t = Type.GetTypeFromProgID("WinFax.SDKPhoneBook");
        if (t == null)
            throw new ArgumentException("WinFax Pro is not installed.");
        comObject = Activator.CreateInstance(t);
        type = comObject.GetType();
    }

    public string GetUserGroupFirst(int flavor, string id)
    {
        var parameters = new Object[2];
        parameters[0] = flavor;
        parameters[1] = id;
        string s = type.InvokeMember("GetUserGroupFirst",
                                     BindingFlags.InvokeMethod,
                                     null,
                                     comObject,
                                     parameters) as String;
        return s;
    }
....

The PhoneBook class is a wrapper on the IDispatch interface. I wrote one wrapper method for each method and property in the typelib. I think this is kinda what .NET 4.0 would do automagically for me. In any case, this worked well for me.

I'm posting this Q&A here only so that other people who deal with WinFax Pro might have the information. I searched all over the intertubes and couldn't find any good information, so I'm putting this out there.


EDIT - this .NET code is now running in an ASPNET MVC app, allowing users to lookup entries and send faxes from a web page or a REST client.

0
Erik Funkenbusch On

I can't help you with your particular problem, but be aware that COM interop only works with com dll's of the same "bitness", and .NET will default genearte an application that is native to the computer it's running on (ie. it will generate a 64 bit app on a 64 bit computer).

Almost all COM dll's are 32 bit, especially commercial ones, so you will have to force the app to always build 32 bit, by setting the CPU type in the project settings from "Any" to "32 bit".

0
Hans Olsson On

I'd see it as a worst case solution, but you could always write your own wrapper for the Winfax COM objects in C++ (or possibly even VB6) and have that code do the actual work and then call those wrappers from your .Net code. That way you don't have to worry too much about figuring out how to translate the COM interface types to .Net compatible types etc, so it might make it quite a bit easier.