Automation error on VBA using an imported custom C# class - c#

I'm trying to use a class on Access VBA 7.0 which implements an Interface I made and I'm still getting "Automation error" even after adding the usual headers.
DropBox.cs
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("E2F07CD4-CE73-4102-B35D-119362624C47")]
[ComDefaultInterface(typeof(ICloudFileProvider))]
[ProgId("CloudFiles.dll")]
public class DropBox : ICloudFileProvider
{
public DropBox()
{
ConectaDropbox("TokenLongChicken");
}
public DropBox(string tokenUsuario)
{ //This was the original and good constructor. I know I can't use constructors with arguments on VBA. Just keeping it to compile with Test
ConectaDropbox(tokenUsuario);
}
public void ConectaDropbox(string tokenUsuario)
{
}
// This method and others come implemented from an interface (ICloudFileProvider)
public string SubirArchivo(string rutaLocal, string carpetaCloud, Tipos.TipoSobreescritura tipoSobreescritura)
{
}
This didn't work, so I saw I have to "create" a header for the Interface and I did it on the Interface itself. I'm still getting the error, so on the DropBox class, I added another header "enumerating" the methods I'm using on this class (which has no sense, but I've read another questions on SO which concretes you have to do so).
So I added this at the end of the DropBox class, noting it is as well on the ICloudFileProvider Interface (the real one).
[ComVisible(true)]
[Guid("E2F11CD4-CE73-4102-B35D-119362624C47")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IExposedClass
{
void DropBox();
void DropBox(string tokenUsuario);
void ConectaDropbox(string tokenUsuario);
string SubirArchivo(string rutaLocal, string carpetaCloud, Tipos.TipoSobreescritura tipoSobreescritura);
decimal ConvertBytesToMegabytes(long bytes);
void Descargar(string link, string carpetaLocal);
string GenerarLink(string rutaCloud);
void EliminarArchivo(string link);
}
I guess I'm doing something wrong but I'm kind of lost in this DLL and TLB hell. This project just works fine on C# but I need to integrate it on VBA and I see no examples with projects which uses real interfaces. I'm not really sure if the "InterfaceIsIUnkown" I add at the end of the DropBox class makes any sense, but I saw it on every example I found on the Internet (but none of them used a real Interface).
Could anybody help? Thanks.
P.S: yes, I perform the RegAsm.exe export to .TLB and then I add it to my Access, with no export errors apparently.

Related

Using a C# library in C++/CLI project: compile-time error "undefined class"

I'm trying out using a C# library from a managed C++ project (CLR) for the first time and am experiencing compile-time problems.
The C# library in question is the Optional library from Nils Lück (https://github.com/nlkl/Optional).
I created a simple demo project so I can paste the code here for you to see. Basically, I have a C# class that has a public method which returns an Option<string, MyExceptionType> which is a generic type from the Optional library. Then I call this method from the C++/CLR project.
First, this are the compile-time error messages I get:
And here the code:
C# class
namespace OptionalAndCpp
{
using Optional;
public class MyException
{
public string Message { get; set; }
}
public class Something
{
public Option<string, MyException> DoStuff()
{
return Option.Some<string, MyException>("Hello");
}
}
}
Wrapper header
#pragma once
class TheWrappingClass
{
public:
void DoStuffFromWrapper();
};
Wrapper implementation
#include "stdafx.h"
#include "TheWrapper.h"
#using <OptionalAndCpp.dll>
#using <Optional.dll>
void TheWrappingClass::DoStuffFromWrapper()
{
OptionalAndCpp::Something^ sth = gcnew OptionalAndCpp::Something();
auto result = sth->DoStuff();
}
That's all. And when I hit compile, I get the errors shown in the screenshot above.
I took a look at the source code of the library and saw that it relies extensively on generics and extension methods, so it made me wonder whether it's a similar problem as with exporting C++ function templates from a DLL (which you can't), but I'm sincerely clueless.
Is it possible that some C# libraries are not usable from a C++/CLR project at all for some reason? As I already mentioned this is the first time I'm doing this and I have a lot more experience with C# than with C++.
Has anyone ever encountered a similar problem?
I'm grateful for any help and/or insights you may have.

VBA object browser doesn't show class members in my .NET dll library

I'm trying to make a simple (no dependencies) .NET dll file that is accessible from VBA (MS Office). I'm using VS2015 Express, and make my dll the simplest way possible: Create a class library, add a simple class, and check the options "Make assembly COM-visible" as well as "Register for COM interop".
My C# code:
namespace TestLib {
public class Hello {
public int timestwo(int i) {
return 2 * i;
}
}
}
The library is added to the Windows registry automatically on build. I can access it from Tools --> References within the MS Office VBA editor, and the following VBA code works as expected:
Sub test()
Dim h as TestLib.Hello
MsgBox h.timestwo(2)
End Sub
Now the funny thing is: The object browser shows my class, but with no member functions! And consequently, the autocompletion feature does not work... What is going on here?
Mmm, your description of what you did is missing a few steps, it appears to me. You need GUIDs. You need an Interface, which your class needs to implement. And you need to specify how the class should work with the Interface. It needs to be set to the Type "None" for the Intellisense, etc. to work.
Here's an extract from a .NET COM DLL of mine which does appear in the Object Browser and gives Intellisense when referenced for a VBA project.
[Guid("149F7A5F-7DAC-4426-8AA0-28975A2CE203")]
[ComVisible(true)]
public interface ITest
{
string testLT(string FilePath, object Args);
string RemoveListTemplates(string FilePath, object Args);
void test(string arg);
}
[Guid("D86307C2-3FFA-4518-BABC-DA5F26ABC445")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Test : ITest
In VS, you need to add XML comments to your code:
namespace TestLib {
public class Hello {
/// <summary>
/// A method to return value * 2
/// </summary>
/// <param name="i">An integer to multiply by two</param>
public int timestwo(int i) {
return 2 * i;
}
}
}
Then make sure the tickbox for XML Documentation File is checked and ensure the XML file has the same name as your assembly. This should supply the meta data needed to populate the IntelliSense feature.

COM object written in C# - Get class, but not methods

I have written a simple COM object in C# with only one method, which is called GetMac. I can't get it to work. I am trying to access it from a legacy Borland C++ Builder 4 (BCB4) application, which I know is old, and not used much anymore, but I am able to access other COM objects from it fine.
The Borland development machine is running Windows XP, so I make the C# COM object target the .NET 4.0 framework. I copied the DLL and PDB file over from the C# Visual Studio machine to the XP machine. I registered it via the following command:
"%WINDIR%\Microsoft.NET\Framework\v4.0.30319\regasm.exe" TRSDotNetCOM.dll /tlb /nologo /codebase
I am able to instantiate the COM object (class) fine via the following line of code:
Variant TDN = CreateOleObject("TRSDotNetCOM.TRSCOM_Class");
If I change the name string, it doesn't work, so I know I have this part correct.
However, when I try to call the method as follows:
MacV = TDN.OleFunction(funcNameV,counterV,macKeyV);
... I get a runtime exception (unfortunately, there's an issue with BCB4's exception handling for OLE calls, so the only info the debugger gives me is "Exception Occurred").
Since I am able to call other COM objects from the same BCB4 application in the same manner, I don't think the problem is with my C++ code. I think it is an issue with either the C#-created COM DLL, or the registration thereof.
To explore this, I used Microsoft OLE/COM Object Viewer to browse my system for the OLE object. I was able to find my object as "TRSDotNetCOM.TRSCOM_Class", as expected.
I'm brand new at using the OLE/COM Object Viewer, so I hope I am looking at the right things below:
When I expand the class, I see the following:
I right-clicked on _Object and chose "View", then "View Type Info". Then, the pane on the right shows:
[ uuid(65074F7F-63C0-304E-AF0A-D51741CB4A8D), hidden, dual, nonextensible,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "System.Object")
] dispinterface _Object {
properties:
methods:
[id(00000000), propget,
custom({54FC8F55-38DE-4703-9C4E-250351302B1C}, "1")]
BSTR ToString();
[id(0x60020001)]
VARIANT_BOOL Equals([in] VARIANT obj);
[id(0x60020002)]
long GetHashCode();
[id(0x60020003)]
_Type* GetType(); };
When I expand the tree on the left, this is what I see:
I do not see my method "GetMac" listed anywhere in there. So, I'm thinking that somehow the method is not visible to COM, or that it's not getting registered via regasm.
Here is the source for the COM object:
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace TRSDotNetCOM
{
[Guid("80ef9acd-3a75-4fcd-b841-11199d827e8f")]
public interface TRSCOM_Interface
{
[DispId(1)]
string GetMac(string counter, string macKey);
}
// Events interface Database_COMObjectEvents
[Guid("67bd8422-9641-4675-acda-3dfc3c911a07"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface TRSCOM_Events
{
}
[Guid("854dee72-83a7-4902-ab50-5c7a73a7e17d"),
ClassInterface(ClassInterfaceType.None),
ComVisible(true),
ComSourceInterfaces(typeof(TRSCOM_Events))]
public class TRSCOM_Class : TRSCOM_Interface
{
public TRSCOM_Class()
{
}
[ComVisible(true)]
public string GetMac(string counter, string macKey)
{
// convert counter to bytes
var counterBytes = Encoding.UTF8.GetBytes(counter);
// import AES 128 MAC_KEY
byte[] macKeyBytes = Convert.FromBase64String(macKey);
var hmac = new HMACSHA256(macKeyBytes);
var macBytes = hmac.ComputeHash(counterBytes);
var retval = Convert.ToBase64String(macBytes);
return retval;
}
}
}
I did make sure and go into the project properties and check the "Register for COM interop" checkbox. I also generated a Secure Name file with the "sn" utility, and loaded the file in the Signing section of settings.
So...
1) Am I looking in the correct place in the OLE/COM Object Viewer for my method?
2) If so, why would my method not be visible or not get registered?
3) Any ideas of what else could be wrong?
UPDATE: Here is the updated code with Joe W's and Paulo's suggestions. (It still does not work however)
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace TRSDotNetCOM
{
[Guid("80ef9acd-3a75-4fcd-b841-11199d827e8f"),
ComVisible(true)]
public interface TRSCOM_Interface
{
[DispId(1)]
string GetMac(string counter, string macKey);
}
// Events interface Database_COMObjectEvents
[Guid("67bd8422-9641-4675-acda-3dfc3c911a07"),
ComImport,
ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface TRSCOM_Events
{
}
[Guid("854dee72-83a7-4902-ab50-5c7a73a7e17d"),
ClassInterface(ClassInterfaceType.None),
ComDefaultInterface(typeof(TRSCOM_Interface)),
ComVisible(true),
ComSourceInterfaces(typeof(TRSCOM_Events))]
public class TRSCOM_Class : TRSCOM_Interface
{
public TRSCOM_Class()
{
}
public string GetMac(string counter, string macKey)
{
// convert counter to bytes
var counterBytes = Encoding.UTF8.GetBytes(counter);
// import AES 128 MAC_KEY
byte[] macKeyBytes = Convert.FromBase64String(macKey);
var hmac = new HMACSHA256(macKeyBytes);
var macBytes = hmac.ComputeHash(counterBytes);
var retval = Convert.ToBase64String(macBytes);
return retval;
}
}
}
You're missing just a few bits.
Declare your interfaces as ComVisible:
[ComVisible(true)]
public interface TRSCOM_Interface
If your assembly is already COM visible by default (you can check this in the project's properties or typically in AssemblyInfo.cs), you don't need to do this, but it does no harm and it'll keep the interface available for regasm.exe and tlbexp.exe in case you revert this configuration.
Declare the events interface as ComImport:
[ComImport]
public interface TRSCOM_Events
My guess here is that this interface is defined outside your C# project, probably by the BCB4 application or one of its modules.
If my guess is wrong and your C# project is the one defining this interface, then [ComVisible(true)].
If this interface has event methods, you then implement then as events in the class.
Finally, to avoid having another interface exported for your class, you may want to add the ClassInterface attribute:
[ClassInterface(ClassInterfaceType.None)]
public class TRSCOM_Class : TRSCOM_Interface
This way, you're telling that TRSCOM_Interface is your class default interface, as it is the first one you implement, and regasm.exe /tlb won't generate a class interface.
Depending on the order of implemented interfaces is not reassuring, so you can also use the ComDefaultInterface attribute:
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(TRSCOM_Interface))]
public class TRSCOM_Class : TRSCOM_Interface
Now, you can have any order in the implemented interfaces list without worrying about changing the default class interface.
That is the first time I have ever seen a method declared ComVisible. I would forgo that and instead declare the TRSCOM_Interface interface ComVisible.

TypeLoadException on Assembly.GetType Explicit Interface implementation

So here is my issue. I have a complex archetecture of interfaces and abstract classes that I am trying to load up via Assembly.LoadFrom("x.dll"). When certain types that have an interface implementation where the implementation is explicit in a base class are trying to be loaded, I am getting a TypeLoadException saying:
Method 'MyMethod' in type 'MyPart2DerivedType' from assembly 'MyPart2Assembly, version...' does not have an implementation. I am trying to understand why this is as I have gone through several articles and have even attempted to delete the obj files and dlls manually. Here are the references to what I have done so far:
Solution to TypeLoadException
TypeLoadException says 'no implementation', but it is implemented
Visual Studio Forumns: TypeLoadException
Private accessors and explicit interface implementation
So here is my example code:
//This is in project 1
public interface IFooPart1
{
void DoStuff();
}
//This is in project 2
public interface IFooPart2
{
void DoOtherStuff();
}
//This is in project 3
public interface IFooPart3: IFooPart1, IFooPart2
{
void DoEvenMoreStuff();
}
//This is in project 4
public abstract class MyBaseType: IFooPart1, IFooPart2
{
void IFooPart1.DoStuff()
{
DoStuffInternal();
}
void IFooPart2.DoOtherStuff()
{
DoOtherStuffInternal();
}
}
//This is in project 5
public class MyDerivedType: MyBaseType, IFooPart3
{
public void DoEvenMoreStuff()
{
//Logic here...
}
}
//Only has references to projects 1, 2, & 3 (only interfaces)
public class Program
{
void Main(params string[] args)
{
//Get the path to the actual dll
string assemblyDll = args[0];
//Gets the class name to load (full name, eg: MyNameSpace.MyDerivedType)
string classNameToLoad = args[1];
//This part works...
var fooAssembly = Assembly.LoadFrom(assemblyDll);
//Here we throw a TypeLoadException stating
// Method 'DoStuff' in type 'MyDerivedType' from assembly 'Project 5...' does
// not have an implementation.
Type myDerivedTypeExpected = Assembly.GetType(classNameToLoad);
}
}
Note: If I move the explicit implementation to MyDerivedType instead of MyBaseType it works... but I don't get why I would have to do that. Seems like I should be able to. This code is only an example, the actual code has a factory that returns the loaded class but only via the interface type. (eg: var myDerivedType = GetInstance();)
Okay for everyone that is interested in my stupid fix. Here was my problem:
Project6 (which was the console app) has PROJECT references to the other projects, not references to the dlls in the location that they are supposed to build to. The other projects actually were being built to a specific repository area. So, the console application was using it's own version of the dll's when it was trying to automatically load the dependancies. This evidently made some other type way down there that was being dynamically loaded to not be loaded because it was not in the same folder as the dlls that were there...
So in short, Assembly.LoadFrom might cause you to load an assembly twice, but .NET treats it like a different assembly!!! This may introduce some real odd errors when trying to dynamically load types!!!
Please learn from my frustration/mistake. Fiends don't let freinds DI alone (code review is key to catching this stupid stuff).
I've had a similar problem caused by one of my projects having a reference to an older version of a nuget package dependency. The older version didn't have an implementation for one of the methods.

Create and Register a COM Server Object .NET

I've been following a set of tutorials (like this one) to create a simple COM server object.
Say I've got a simple hello world:
[ComVisible(true)]
[Guid("392930B3-9CD0-4247-8C69-83168D1C8F77")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("nathanr.HellowWorldCom")]
class HelloWorldCom : IHelloWorldCom
{
public int HelloWorld()
{
Console.WriteLine("Hello World!");
return 1;
}
}
With just as simple an interface:
[ComVisible(true)]
[Guid("C08205BE-1393-4070-AE57-FA47F0D653C3")]
interface IHelloWorldCom
{
[DispId(1)]
int HelloWorld();
}
And of course, can't forget the AssemblyInfo.cs file:
...
[assembly: ComVisible(true)]
[assembly: AssemblyKeyFile("HelloWorldCOM.snk")]
...
The problem is when I build the HelloWorldCom.dll and try to register it, regasm just sticks its tongue out at me:
RegAsm : warning RA00000 : No types were registered.
And just to cover my bases I cracked open Regedit and did a search for the ProgID. It wasn't there, which honestly wasn't a surprise.
Am I missing something obvious?
This whole test project is part of a larger (actually useful) set up which is also failing to register.
This will be a very long answer: add public
public class HelloWorldCom : IHelloWorldCom
There might be couple of issues:
You are selecting an incorrect .net framework. In my case, I was trying to register the dll with 2.0 framework, as opposed to 64 bit 4.0 framework
Your DLL is not publicly exposed.
Hope that resolves your issue.
Cheers

Categories

Resources