DLL for VBA.Collection - c#

I need to use a VBA.Collection class and I need to register a COM component in order to do that. I found msvbvm60.dll file on my computer and successfully registered it in my system. I am creating an instance of a class VBA.Collection, but I get an exception in that line of code:
VBA.Collection collection = new VBA.Collection();
The exception has the following description:
System.Runtime.InteropServices.COMException was unhandled
HResult=-2147221164 Message=Retrieving the COM class factory for
component with CLSID {A4C4671C-499F-101B-BB78-00AA00383CBB} failed due
to the following error: 80040154 Class not registered (Exception from
HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)). Source=mscorlib
ErrorCode=-2147221164
I used regedit.exe tool to search for a component with CLSID of {A4C4671C-499F-101B-BB78-00AA00383CBB}, but haven't been able to find one. I suspect that there should be another .dll file that I should register, even though I can see with Object Browser in Visual Studio that reference to msvbvm60.dll gives me access to VBA namespace and Collection class within it.
It says on this web-page http://codeverge.com/asp.net.migrating-from-asp/using-vba.collection-object-in-net/577432 that the problem will go away if I instantiate VBA.CollectionClass . However, if I do so, I receive the following error and my code doesn't compile:
Interop type 'VBA.CollectionClass' cannot be embedded. Use the
applicable interface instead
Have I registered the wrong COM component? Or is there anything I need to change in my C# code?
I also read on How to create VBA.Collection() object in c# that I need to create my own class based on VBA._Collection interface and not instantiate VBA.Collection class. I created the following class:
class MyCollection : VBA._Collection
{
private Dictionary<object, object> _items = new Dictionary<object, object>();
public void Add(ref object Item, [System.Runtime.InteropServices.OptionalAttribute]ref object Key, [System.Runtime.InteropServices.OptionalAttribute]ref object Before, [System.Runtime.InteropServices.OptionalAttribute]ref object After)
{
// Ignoring the Before and After params for simplicity
_items.Add(Key, Item);
}
public int Count()
{
return _items.Count;
}
public System.Collections.IEnumerator GetEnumerator()
{
return _items.Values.GetEnumerator();
}
public dynamic Item(ref object Index)
{
return _items[Index];
}
public void Remove(ref object Index)
{
_items.Remove(Index);
}
}
I now instantiate MyCollection class with MyCollection collection = new MyCollection();
I do not get an exception there anymore. However, I need to call a method GetIncidentCodes() of another COM component that returns an object of type VBA.Collection. If I use collection = class1.GetIncidentCodes();, then my program won't compile because an explicit cast is required. If I use collection = (MyCollection) class1.GetIncidentCodes();, then I get the following exception:
Unable to cast COM object of type 'System.__ComObject' to class type
'PoliceDispatcherClient.MyCollection'. Instances of types that
represent COM components cannot be cast to types that do not represent
COM components; however they can be cast to interfaces as long as the
underlying COM component supports QueryInterface calls for the IID of
the interface.
Should I cast the return value of GetIncidentCodes() into interface VBA._Collection? I am new to .NET, so I am not sure how to do it.

Related

Why usage of dynamic throws exception in runtime?

I have an external dll I'm loading into my appdomain during runtime.
I'm creating an instance of a class from that assembly into a local dynamic variable.
As far as I understood the usage of dynamic in C#, I can simply call a method of it, which will be resolved at run time...
Using this approach, the following code gets me a runtime "'object' does not contain a definition for 'Get'" exception.
I'll try to illustrate the structure as I can't expose the actual code.
External dll name: a.b.c
namespace Ext
{
public static class FactoryCreator
{
public static ProxyFactory CreateFactory()
{
return new ProxyFactory();
}
}
public interface FactoryIfc
{
Proxy Get();
}
internal class ProxyFactory: FactoryIfc
{
private Proxy proxy;
public Proxy Get()
{
if (this.proxy == null)
this.proxy = <a method to create a proxy>
return this.proxy;
}
}
}
I'm using the following code
var assembly = "a.b.c, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<key>,processorArchitecture=MSIL";
var instName = "Ext.FactoryCreator";
dynamic factoryCreator = AppDomain.CurrentDomain.Load(assembly).GetType(instName).GetMethod("CreateFactory").Invoke(null, new object[0]);
dynamic proxy = factoryCreator.Get();
I understand that for FactoryCreator dynamic variable, I need to get the Type and invoke the static method of it, but.. as I said, it is throwing an exception "'object' does not contains a definition for 'Get'" - at the factory.Get() statement - while I would expect dynamic factory to be resolve automatically to the object and service the Get() call.
Observing the situation under a debug session, I can clearly see the Get method using factory.GetType().GetMethods() in the quickwatch window.
Can you explain what is happening?
Must I use factory.GetType().GetMethod("Get") followed by an Invoke? I thought the power of dynamic should work this out automatically in runtime...

C# Reflection on UnitOfWork / Generic Repositories

This seems like an age-old question, but simply can't find what I'm looking for. Here's my current code with things I've tried.
private async Task<T> HandleFileCreate<T>(Guid tableId, IFormFile file, DocumentType documentType)
where T : DocumentLibrary
{
// This works fine and gets the correct type
Type repoType = _unitOfWork.GetType().GetProperty(typeof(T).Name + "Repository").PropertyType;
// This works fine and creates an instance of my document
T document = (T)Activator.CreateInstance(typeof(T));
// Throws up error: "No parameterless constructor defined for this object."
object instance = Activator.CreateInstance(repoType);
// Throws up error: "Object does not match target type."
repoType.GetMethod("Create").Invoke(repoType, new object[] { document });
}
It seems a bit chicken and egg because I can't invoice "Create" it seems without the CreateInstance, in which I can't do because of IoC.
This is really annoying because I've already got an instantiated property in _unitOfWork which relates directly to my relevant GenericRepository, I just can't figure out how to access it? I shouldn't even have to re-instantiate it.
Rader then using reflection(Always a bad idea) I would recommend doing something like this
public static T Create<T>(IDocumentLibraryCreator<T> repo)// You can add you params here
where T: DocumentLibrary, new()
{
var newItem = new T();
repo.Create(newItem);
//Do your stuff here
return newItem;
}
public interface IDocumentLibraryCreator<T> where T : DocumentLibrary
{
Task Create(T document);
}
public abstract class DocumentLibrary
{
}
Because all you will get with reflection is runtime exceptions and not compile time exceptions. No need for reflection. Your repo will need to implement IDocumentLibraryCreator<T> interface but if you did IRepository in a good way you should have something like this already.
You will need to pass the instance created in the uof class if you need the work to be done in a single Unite of work.

Returning Managed C# List to Unmanaged C++ code

I have a C# dll ( for which register for COM interop option is set).
This C# dll has the below interface and class
interface IMyInterface
{
bool IsNameExists(string name);
List<string> GetNameList();
}
public class MyClass : IMyInterface
{
public bool IsNameExists(string name)
{
//DO Something
}
public List<string> GetNameList()
{
// DO something
}
}
I need to call the methods IsNameExists and GetNameList from unmanaged C++.
#import "..\..\ProdCon\bin\ProdCon.tlb" raw_interfaces_only
void main()
{
HRESULT hr =::CoInitialize(NULL);
IMyInterface pIMyInterface(__uuidof(MyClass));
VARIANT_BOOL bRet = FALSE;
BSTR bstrName = ::SysAllocString(_T("RAJESH"));
hr = pIMyInterface->IsNameExists(bstrName,&bRet);
}
So I created COM object as above and called the IsNameExists mehtod without any issue.
Since GetNameList method returns list, I am getting the below warning
'MyDll.IMyInterface.GetNameList(#0), MyDll'. Warning: Type library
exporter encountered a generic type instance in a signature. Generic
code may not be exported to COM.
Please help me how to return C# list to unmanaged C++ code. So that unmanaged C++ code can use this list.
'Generic code may not be exported to COM.' => hence its the type parameter string in public List GetNameList(). Thus essentially you need access to a non-generic c# method to get the data.
If you have control of the MyClass codebase you could (for example) add a:
public string[] GetNameArray()
{
return GetNameList.ToArray();
}
If not then you'll need to write a proxy/wrapper class to do something similar to the above and expose that via COM, either as a one off or a 'general' methodology using reflection say.
See for example http://weblog.west-wind.com/posts/2007/Jul/10/Generics-and-COM-Interop-dont-mix

instantiate a class implementing a generic interface using reflection

I have the following in a business logic assembly:
public class BusinessEntity
{
...
}
public class Customer : BusinessEntity
{
...
}
public interface IEntityManager <T> where T : BusinessEntity
{
T SaveData(T oData);
}
public CustomerEntityManager : IEntityManager <Customer>
{
Customer SaveData(Customer o)
{
...
}
}
I am forced to load the above assembly (due to a couple of obvious reasons) in my current project through reflection and instantiate CustomersEntityManager. Imagine that I am writing a method as follows:
public class EntityFactory
{
public static IEntityManager<BusinessEntity> GetManagerInstance(string EntityName)
{
var asm = GetAssembly(); // imagine that I loaded the assembly somehow
EntityName = "Customer"; // just imagine
object o;
// DO NOT KNOW WHAT TO WRITE HERE.
return o as IEntityManager<BusinessEntity>; // this needs to be an instance of CustomerEntityManager.
}
}
I have the option to modify business assembly. But the instance creation needs to be in my current project and I have to load the business assembly using reflection. All the data types will be known only at run-time.
I may be missing some fundamental things or probably doing wrong coding. Please help me out on this.
UPDATE:
Followed "driss" suggestion, like following:
string fullTypeName = "Your.Namespace." + EntityName + "EntityManager";
object o = Activator.CreateInstance(asm.FullName, fullTypeName);
Looks like it created the instance. However, it ended up with an error:
Cannot cast 'o' (which has an actual type of 'CustomerEntityManager') to 'IEntityManager'
when the following statement is getting executed:
return o as IEntityManager<BusinessEntity>
Thanks
You need to construct the full type name somehow, so that you can get the Type instance representing the type. You might decide that the type name relies on a convention, such that you could find the full type name as:
string fullTypeName = "Your.Namespace." + EntityName + "EntityManager";
object o = Activator.CreateInstance(asm.FullName, fullTypeName);
Then it is just a matter of calling Activator.CreateInstance, as you see.
However, I would highly recommend you looking into using an IoC framework for solving this problem.
Re: your comment:
You can't cast CustomerEntityManager to IEntityManager, because that is not what it implements - it only implements IEntityManager. If the cast was allowed, type safety would be broken (you could pass in a BusinessEntity, when the implementation clearly expects a Customer, or, that is at least what the contract says. (Co/contra variance can't save you here because T goes both in and out of IEntityManager).
Forget about using low-level reflection at your own, a lot of not very convenient work. Use an IoC framework if you can, i.e. StructureMap. With StructureMap you'll just need to create a Registry that knows all the dependencies (such as CustomersEntityManager is our implementation for IEntityManager<Customer>). It looks more less like that:
For<IEntityManager<Customer>>().Use<CustomersEntityManager>()
And now if you ask your StructureMap container for an implementation of IEntityManager<Customer>, you'll get CustomersEntityManager:
ObjectFactory.GetInstance<IEntityManager<Customer>>(); // will return instance of CustomersEntityManager
If you don't know the requested type at compile time, you can ask for the entity manager using plain Type instance:
string entityName = "Customer";
Type entityType = Type.GetType(entityType);
Type requestedType = typeof(IEntityManager<>).MakeGenericType(new[] { entityType });
ObjectFactory.GetInstance(requestedType); // will also return CustomersEntityManager instance
Registry can be defined in your assembly, without touching the business assembly.
Checkout Activator.CreateInstance()
Object o = Activator.CreateInstance (asm.FullName, EntityName );
will give you an instance of the Customer. I'm not sure how you would go from Customer to CustomerEntity but I'm sure you can work that part out.

Using C++ COM interface in C# for both client and server

I need to make a piece of C# code interact through COM with all kinds of implementations.
To make it easeier for users of that integration, I included the interacted interfaces in IDL (as part of a relevant existing DLL, but without coclass or implementation), then got that into my C# code by running Tlbimp to create the types definition.
I implemented my C#, creating COM objects based on Windows registry info and casting the object into the interface I need.
I then created a C# implementation of the interface in a seperate project and registered it.
The main program creates the testing COM object correctly but fails to cast it into the interface (gets a null object when using C# 'as', gets an InvalidCastException of explicit cast).
Can someone suggest why the interface is not identified as implemented by the testing object?
This is the interface defition in IDL (compiled in C++ in VS 2005):
[
object,
uuid(B60C546F-EE91-48a2-A352-CFC36E613CB7),
dual,
nonextensible,
helpstring("IScriptGenerator Interface"),
pointer_default(unique)
]
interface IScriptGenerator : IDispatch{
[helpstring("Init the Script generator")]
HRESULT Init();
[helpstring("General purpose error reporting")]
HRESULT GetLastError([out] BSTR *Error);
};
This is the stub created for C# by Tlbimp:
[TypeLibType(4288)]
[Guid("B60C546F-EE91-48A2-A352-CFC36E613CB7")]
public interface IScriptGenerator
{
[DispId(1610743813)]
void GetLastError(out string Error);
[DispId(1610743808)]
void Init();
}
This is part of the main C# code, creating a COM object by its ProgID and casting it to the IScriptGenerator interface:
public ScriptGenerator(string GUID)
{
Type comType = Type.GetTypeFromProgID(GUID);
object comObj = null;
if (comType != null)
{
try
{
comObj = Activator.CreateInstance(comType);
}
catch (Exception ex)
{
Debug.Fail("Cannot create the script generator COM object due to the following exception: " + ex, ex.Message + "\n" + ex.StackTrace);
throw ex;
}
}
else
throw new ArgumentException("The GUID does not match a registetred COM object", "GUID");
m_internalGenerator = comObj as IScriptGenerator;
if (m_internalGenerator == null)
{
Debug.Fail("The script generator doesn't support the required interface - IScriptGenerator");
throw new InvalidCastException("The script generator with the GUID " + GUID + " doesn't support the required interface - IScriptGenerator");
}
}
And this is the implementing C# code, to test it's working (and it's not):
[Guid("EB46E31F-0961-4179-8A56-3895DDF2884E"),
ProgId("ScriptGeneratorExample.ScriptGenerator"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(SOAAPIOLELib.IScriptGeneratorCallback))]
public class ScriptGenerator : IScriptGenerator
{
public void GetLastError(out string Error)
{
throw new NotImplementedException();
}
public void Init()
{
// nothing to do
}
}
Again - thanks for the suggestions.
I was able to finally resolve the issue on my own. I tried the above suggestions and didn't made any progress. Then I changed the namespace of the interop in the 'testing' code - it varied from the one in the main code because of different argument use when using Tlbimp. This solved the problem.
Here's my guess to why: .Net creates the COM object, but when it detects this is actually a .Net object, it bypass the COM layer and communicates directly. In which case, queryInterface (with the interface GUID) is not used and the interface do differ because of different C# namespaces.
This means that in order to supprot integration with .Net code, I will need to publish my original interop assembly aside the IDL.
Thanks,
Inbar
I think you need this on the interface
[InterfaceType(ComInterfaceType.InterfaceIsDual)]

Categories

Resources