Search code examples
c#.netvbscriptcom

I need to create a C# based COM Server for consumption in VBScript


I am creating a COM server .dll in C# (.NET 3.0 - external requirement). Some clients work well with it but others do not. Specifically, I'm having problems getting VBScript to like it.

  1. There are two primary components:

    a. an interface
    b. an implementation object

    they both have their own GUID's. The implementation class EXPLICITLY implements the interface.

  2. The 'Assembly Information' dialog check box 'Make assembly COM-Visible' is checked.

  3. The 'Build' dialog check box 'Register for COM interop' is also checked.

  4. I also signed the assembly, created a Strong Name Key.

In desperation, I added the System.EnterpriseServices.ServicedComponent assembly:

[Guid( "0135bc5c-b248-444c-94b9-b0b4577f5a1b" )]
public class TwoPays : ServicedComponent, ITwoPays
{
    void ITwoPays.TestConnect()
    {
...
[Guid( "0135bc5c-b248-444c-94b9-b0b4577f5a1a" )]
public interface ITwoPays
{

I also ran regsvcs.exe.

RegSvcs.exe LandCORC.dll Microsoft (R) .NET Framework Services Installation Utility Version 2.0.50727.5483 Copyright (c) Microsoft Corporation. All rights reserved.

Which failed with:

WARNING: The assembly does not declare an ApplicationAccessControl Attribute. Application security will be enabled by default.

Installed Assembly:
Assembly: C:\Source\LandCORC\bin\Release\LandCORC.dll
Application: LandcORC
TypeLib: C:\Source\LandCORC\bin\Release\LandCORC.tlb

Since I also don't know anything about Application security, I'm not sure if the warning message from RegSvcs.exe is important.

So I've been using VbsEdit to test it. Before I added the ServicedComponent and ran RegSvcs.exe, it would die on the Set objXL = CreateObject("LandCORC.TwoPays") statement.

Now that completes but when I try to call TestConnect it says that objXL is an invalid pointer, for example:

Dim objXL
Set objXL = CreateObject("LandCORC.TwoPays")
objXL.TestConnect

Adding the reference for VbsEdit:

Prog ID: LandCORC.TowPays
TypeLib path: C:\Source\LandCORC\bin\Release\LandCORC.tlb
GUID: {0135BC5C-B248-444C-94B9-B0B4577F5A1B}

But if I change the VbsEdit reference to use the interface GUID instead, then the auto complete for objXL will show the correct list of methods but then when it executes that I get 'Object doesn't support this property or method: TestConnect' instead.

I don't know what I need to complete the puzzle.

NEW INFORMATION Based on Kev's answer, I have corrected the interface and class definitions.

[Guid( "0135bc5c-b248-444c-94b9-b0b4577f5a1a" )]
[InterfaceType( ComInterfaceType.InterfaceIsDual )]
[ComVisible( true )]
public interface ITwoPays
{
    [DispId( 1 )]
    void TestConnect();

    [DispId( 2 )]
    void Terminate();



[Guid( "0135bc5c-b248-444c-94b9-b0b4577f5a1b" )]
[ClassInterface( ClassInterfaceType.None )]
[ComVisible( true )]
public class TwoPays : ITwoPays
{
    [ComVisible( true )]
    public void TestConnect()
    {
        LogInformationMessage( "TestConnect." );
    }

   [ComVisible( true )]
   public void Terminate()
   {

And running RegAsm /codebase /register /tlb comes back clean as does running the gacutil -i which states Assembly successfully added to the cache


Solution

  • This is from a project where I had a similar requirement, an InProc COM wrapped .NET component visible to VBScript and Classic ASP (as in IDispatch friendly in COM parlance).

    I've renamed the interface, class and methods but you should get the idea.

    Declare our interface:

    [Guid("0B201484-E388-45eb-9AB8-A6AE1E197AAB")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    [ComVisible(true)]
    public interface IThingy
    {
        [DispId(1)]
        int DoThing1(string name, int age);
    
        [DispId(2)]
        string DoThing2(int id);
    
        [DispId(3)]
        bool DoThing3(string address);
    }
    

    Implementation:

    [Guid("68FAB6AC-9923-425a-85F2-59B50552A5C1")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    public class Thingy: IThingy
    {
        [ComVisible(true)]
        public int DoThing1(string name, int age)
        {
            // ....
        }
    
        [ComVisible(true)]
        public string DoThing2(int id)
        {
            // ....
        }
    
        [ComVisible(true)]
        public bool DoThing3(string address)
        {
            // ....
        }
    }
    

    I also had to generate an assembly key file, this is because COM wrapped .NET assemblies need to be "strongly named" (I vaguely remember - it's been ten years since I wrote this code).

    Next I regasm'd the assembly which does all the fun COM wiring in the registry:

    regasm.exe COMExample.Fun.dll /register /codebase /tlb
    

    And then chucked it in the GAC:

    gacutil.exe -i COMExample.Fun.dll
    

    To uninstall:

    gacutil.exe -u COMExample.Fun.dll,Version=1.0.0.0
    regasm.exe COMExample.Fun.dll /unregister /tlb
    

    Get rid of the System.EnterpriseServices and ServicedComponent stuff. You only need that if you're going run up an out of process server in COM+ and need the COM+ transactional infrastructure etc.