How do I make a TypeConverter known to an assembly

2019-08-28 17:30发布

问题:

I have a c# project that consists of a COM typelib (ComLib), a C# tools classlibrary (ComTools) and a main C# project (ComMaster)that is used as a testbed for the COM typelib.

See the code at the bottom.

When calling the COM object from the ComMaster (not as a com object, but as a 'normal' C# object), everything works fine: The call from ComLib to ComTools works and ComTools finds the TypeConverter located in ComLib. The DoIt function pops up a nice Messagebox saying:

Original: Hello
Converted: Hello

Now I publish the com lib using RegAsm ComLib.dll /codebase /tlb:ComLib.tlb.

My problem is: When I call the COM object from, say, Excel-Vba where I set a reference on the typelib generated with regasm and have this code:

Sub TestComLib()
    Dim c As New ComLib.ComLib
    c.DoIt "My Test String"
End Sub

Now I get a runtime error saying "InvalidCastException: 'System.String' can't be converted into 'ComLib.MyClass'". Obviously, this comes from the nasty (T)(object)aString in ComTools.ToolFunc that gets called when the TypConverter is unable to convert the string to a MyClass.

Now, my question is: How can I hand over the TypeConverter attached to MyClass to the ComTools assembly without setting a reference in ComTools to ComLib (EDIT: this would cause a circular reference!)?

I have found the following issue that seems to be the same (but has not been answered): TypeConverter from VB6

EDIT: I have just tried to be more explicit in the attribute of MyClass declaring the type converter: [System.ComponentModel.TypeConverter("ComLib.MyClassConverter, ComLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]. But it does not help :-(


EDIT: Solution

Thanks to Simon Mourier, I added the following code to my ComLib class:

static ComLib()
{//static constructor, gets called before anything in here gets executed
    AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
    {
        if (args.Name == Assembly.GetAssembly(typeof(ComLib)).FullName)
            return Assembly.GetAssembly(typeof(ComLib));
        return null;
    };
}


Sample Code:
ComLib (project created as c# classlibrary explicitly exposing an interface):

namespace ComLib
{
    [ComVisible(true), Guid("abcdef00-23aa-46d0-8ba8-c7548fa4d820")]//faked GUID
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IComLib
    {
        void DoIt(string aMsg);
    }

    [ComVisible(true), Guid("abcdef01-23aa-46d0-8ba8-c7548fa4d820")]//faked GUID
    [ProgId("ComLib.ComLib")]
    [ClassInterface(ClassInterfaceType.None)]
    public class ComLib : IComLib
    {
        public void DoIt(string aMsg)
        { 
            try {
                MyClass c = new MyClass();
                //call tool func in ComTools
                c = ComTools.ComTools.ToolFunc<MyClass>(aMsg);
                MessageBox.Show("Original: " + aMsg + "\n" + c.Text);
            }
            catch (Exception e) {
                MessageBox.Show("Error: " + e.ToString());
            }
        }
    }

    [System.ComponentModel.TypeConverter(typeof(MyClassConverter))]
    public class MyClass
    {//dummy class wrapping a string
        public string Text {get; set; }
    }

    public class MyClassConverter : System.ComponentModel.TypeConverter
    {//converter for MyClass allowing conversions from string
        public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
        { return sourceType == typeof(string); }

        public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            var s = value as string;
            if (s != null)
                return new MyClass() {Text = "Converted: " + s};
            return base.ConvertFrom(context, culture, value);
        }
    }
}

The ComLib project references the ComTools project which contains the following code:

namespace ComTools
{
    public class ComTools
    {
        public static T ToolFunc<T>(string aString)
        {//create an object of the given T type converting it from goiven string value
            System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(T));
            if (typeConverter.CanConvertFrom(typeof(string)))
            {//called from com, the correct type converter is not found
                return (T) typeConverter.ConvertFrom(aString);//convert using the typeconverter found
            }
            else
            {//last resort: try a cast
               // ******* this throws an error when called from COM,
               // ******* because the correct type converter is not found
               return (T)(object)aString;//will not let me cast directly to T
            }
        }
    }
}

The ComMaster project references only the ComLib project and contains the following code:

static void Main()
    {
        ComLib.ComLib lib = new ComLib.ComLib();//create an instance of the lib
        lib.DoIt("Hello");//call the exposed function
    }

回答1:

What happens is, for some reason, when you're running a non .NET application, some types are not resolved as automatically as they are in a pure .NET context, but there is no evident error or exception raised, all fails gracefully and silently (which is a shame BTW...).

What you need to do is help the system a bit, and attach to the AssemblyResolve event, something like this:

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
    {
        if (args.Name == Assembly.GetAssembly(typeof(ComLib)).FullName) // adapt to your needs
            return Assembly.GetAssembly(typeof(ComLib));

        return null;
    };