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
}
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: