Calling C# method from VBA using COM Interop - vba

By refering this, I was able to attach Entities.tlb file to VBA. But I couldn't able to create an instance from the classes in C#. This is my code,
C# class
namespace Entities
{
[Guid("1558C766-44DA-4DA5-BF2F-CBD6804E7E21")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITools
{
ABC GetABC();
}
[Guid("514719AC-E137-4FCA-82AF-73E4025A8625")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Tools : ITools
{
public Tools()
{
}
public methods ...
}
}
From VBA,
Dim objElement As New Entities.Tools
This caused Error : Invalid Use Of New Key Word
And also in VBA object browser,for attached Entities.tlb it shows all the classes under Entities namespace. But It's not showing any method inside those classes.
How can I create a instance of Tools class in VBA and access methods in Tools class from VBA ?

Delete the constructor. VBA can't use them and c# will implement the default constructor transparently..
Then try in the class
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ITools))]
and in the interface
Just
[ComVisible(true)]
Then in VBA
Dim objElement as Tools
set objElement = new Tools
I have a C# class that I access from VBA and this is the only way I've found that gives intellisense in VBA and still allows items such as GetEnumerator to work correctly.
Kvp C# dictionary wrapper for VBA
You should also be aware that if you have an indexer in your class then the .Item[x] syntax will only work in if you choose to not have intellisense. If you have intellisense then then the use of .Item[x]=value will generate a VBA error due toa bug in how the intermediate language is constructed for the indexer.

Related

Translating C# functions into vb.net

I need help converting some of this code. Mainly:
private static void SetProvider(ServiceCollection collection)
=> _service = collection.BuildServiceProvider();
and the line below it. This is being used for a discord bot using Discord.Net with the music library Victoria. Can someone also tell me what this actually is? Just a side question. this uses static classes and there's not anything called static on VB.Net so what would be the best call here? I've seen some other posts from here debating whether to use NonInheritable Class or a Module. What are the differences and when it is better to use either one?
It depends on what you want exactly. VB.NET does not provide static classes. Instead, it offers modules, but those are not completely equal to static classes.
The module version would be:
Public Module ServiceManager
Private _service As IServiceProvider
Public Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
Public Function GetService(Of T As New)() As T
Return _service.GetRequiredService(Of T)()
End Function
End Module
The class version would be:
Public NotInheritable Class ServiceManager
Private Sub New()
End Sub
Private Shared _service As IServiceProvider
Public Shared Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
Public Shared Function GetService(Of T As New)() As T
Return _service.GetRequiredService(Of T)()
End Function
End Class
When using the class implementation, you have to be careful to mark all members as Shared. Additionally, you can consider the following:
Declare the class as NotInheritable, since neither VB.NET modules nor C# static classes can be inherited from. (The corresponding C# keyword is sealed, by the way, but it will never be used in this context, since C# does support static classes.)
Create one private (default) constructor for the class. That will make sure that you cannot instantiate the class. VB.NET modules nor C# static classes cannot be instantiated either.
Using VB.NET modules is somewhat more straightforward, but keep in mind that VB.NET modules have a little quirk. When accessing a member of a module, you are typically not required to prefix it with the module name. Suppose you have some kind of service class called MyService and you have implemented your ServiceManager as a module. Then you do not need to call it like:
Dim svc As MyService = ServiceManager.GetService(Of MyService)()
Instead, you could just call it like:
Dim svc As MyService = GetService(Of MyService)()`.
When using the former method, Visual Studio actually suggests to simplify the name and change it to the latter method. But when you afterwards add another imported namespace that also happens to contain a module that has a GetService(Of T)() method, you will get an error indicating that GetService is ambiguous, in which case you would be forced to prefix it with the module name (like in the former method).
I personally find this checking behavior in Visual Studio regarding VB.NET module member usage to be rather annoying and confusing. I prefer prefixing calls with the module name (for the sake of writing self-documenting code and avoiding ambiguity as mentioned), but I do not want to disable the "simplify name" hint/suggestion in Visual Studio. So I personally prefer a class implementation instead of a module implementation when implementing something in VB.NET that mimics a C# static class.
Or even better: I would avoid a static class design and switch to a "regular" class design when possible. Using class instances has several advantages, like using composition (which is also an important technique used in many popular behavioral design patterns), simplified mocking/unittesting, and less side effects in general.
The equivalent VB.NET is:
Private Shared Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
C# expression bodies are just a single expression body method, MS Docs e.g. the following are equivalent:
void Greet()
{
Console.WriteLine("Hello World");
}
// Same as above
void Greet() => Console.WriteLine("Hello World");

Why no intellisense in VB6?

I wrote a DLL in C# in VS2012:
namespace COMTest
{
public class MyClass
{
public int Fun()
{
return 3;
}
}
}
And then I set "Make Assembly COM Visible=True" and in the Build page, I set "Register COM for intercrop". Then create a new VB6 project, add a reference to the generated dll file but failed……Later tried tlb file succeeded but without intellisense after saying "a." (No "Fun" tip)
Dim a As MyClass
Set a = New MyClass
MsgBox (a.Fun())
So my questions are:
1) Why must I refer tlb file instead of dll file?
2) Why no intellisense?
Try placing a check mark in:
Tools->Options->Editor->Auto List Members
If that does not help, then to resolve this problem, define a public interface by using methods and properties that you want to expose in the TLB, and then implement the interface in the class. Also, add the ClassInterface (ClassInterfaceType.None) attribute to the class. As you develop the component, you can use this approach to avoid using the ComVisible(False) attribute.
You can have more details here

VB.net load dll

I have been attempting to load a DLL using Reflector
Imports System.Reflection
I have a simple DLL file written in c++ using /CLR (this is the entire file)
using namespace System;
namespace ASSEMBLE{
public class REM{
public:
int VALUE(){
return 100;
}
};
};
And inside my VB.net butten click event i have
Dim dllPath As String = "C:\Users\richard\Documents\Visual Studio 2012\Projects\link\link\bin\Release\dlltest.dll"
' load the assembly
Dim assembly1 As System.Reflection.Assembly = Assembly.LoadFrom(dllPath)
' get the type
Dim t As Type = assembly1.GetType("ASSEMBLE.REM")
' create an instance and add it.
Dim c As Object = Activator.CreateInstance(t)
MsgBox(t.InvokeMember("VAULE", BindingFlags.Default Or BindingFlags.InvokeMethod, Nothing, c, {}))
When event triggered (ie. i load the dll) i get the error:
Method 'ASSEMBLE.REM.VALUE' not found
Using:
<DllImport("DLL.dll")> Public Shared Function VALUE() As Integer
End Function
is not an option. I need to load the DLL after runtime.
Your REM class is an unmanaged class, and therefore reflection cannot see its methods. Using the /CLR compile option does not automatically force all classes to be managed. It just allows you to have managed classes in your project.
To allow the call to InvokeMember, you need to make REM a managed class. This can be done by adding ref to the class declaration like so:
public ref class REM{
...
};

Accessing .NET function exposed as COM in Excel

In Convert vba to vb6 and create a .dll - how to - hints, tipps and risks, it was discussed how to convert VBA code into VB.NET code and access the function in VB.NET as COM in Excel.
In Excel, the function has to be accessed this way via VBA:
Public Function getParameterNumberOfMaterial() As Integer
Dim myclass as New ExcelExample.ExcelVB
getParameterNumberOfMaterial = myclass.getParameterNumberOfMaterial()
End Function
This means for every VBA function exposed to the user, I have to write a wrapper as above when converting to VB.NET.
Is there a way to use the function directly without writing the VBA wrapper? That is, in Excel, the user can directly use getParameterNumberOfMaterial(), just like the orignal VBA function, without me writing a VBA wrapper.
You can certainly call .NET functions in a COM-visible .DLL directly from Excel, though you will need to modify them slightly to make then compatible with COM and Excel.
This example will be in C# since that's what I'm familiar with, but the concept should be the same in VB.Net.
Open a new Class project, and add a line to import the InteropServices library:
using System.Runtime.InteropServices;
Declare an interface that will contain the functions you want to call:
public interface ISample
{
object func1(int p1);
object func2(string p1);
}
Only functions listed in the interface will be available in Excel. Note the functions return type is 'object'. This is needed for Excel to properly display the result in the sheet.
Declare the class that will implement the interface, and be exposed via COM.:
[ClassInterface(ClassInterfaceType.None)] // the proper way to declare COM-visible classes
public class Sample : ISample
{
public object func1(int p1)
{
// sample implementation
object[,] retval = new object[1,1]
retval[0,0] = "a";
return retval;
}
public object func2(string p1)
{
// ....
}
Excel expects all return types to be two-dimensional arrays of objects, so you'll need to convert the return values of your functions this way.
You will also need to add a couple functions that will assist with registering and unregistering the DLL:
// helper function to get Registry key of given type
private static string GetSubKeyName(Type type)
{
return "CLSID\\{" + type.GUID.ToString().ToUpper() + "}\\Programmable";
}
// called when class is being registered
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type type)
{
// register the automation DLL function will be visible to Excel
RegistryKey key = Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type));
if (key != null)
{
key.SetValue(string.Empty, Environment.SystemDirectory + #"\mscoree.dll");
}
}
// called when class is being unregistered
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type type)
{
try
{
// remove automation DLL from registry
Registry.ClassesRoot.DeleteSubKeyTree(GetSubKeyName(type));
}
catch (Exception ex)
{
Debug.Print(ex.Message + ex.StackTrace);
}
}
In the Properties screen of your project, in "Application" tab, click "Assembly Information", and check "Make assembly COM-Visible". Click OK.
In "Build" tab, click "Register for COM Interop" near the bottom.
Build the project.
In Excel, click the Developer ribbon, click "Add-ins", "Automation" and scroll down to your project. It will be listed in the form [namespace].[classname]
You should now be able to call the functions directly in an Excel worksheet:
=func1(12)
There is also a post on this from an MSDN blogger, but it is quite old
P.S. If there is any part of this you need assitance with converting to VB.Net, please let me know and I can certainly assist.
There used to be a way up to excel 97 but was removed as a security risk. The call function could call functions from COM dlls. http://www.cpearson.com/excel/Call.htm

Late binding run-time error in VB6 when creating an object from a .NET assembly

i have a vb6 project that has a reference to a vb.net com library.
the project runs well when i use early binding such as:
Dim b as object
Set b = new myComLib.testObject
when i use late binding such as:
Dim b as object
Set b = CreateObject("myComLib.testObject")
i get the following error:
Run-time error '429': ActiveX component can't create object
Any ideas?
thanks
The registry entries for the .NET COM Interop class in this case are:-
HKEY_CLASSES_ROOT\myComLib.testObject
containing a CLSID value and the CLSID entry itself
HKEY_CLASSES_ROOT\CLSID\<<myComLib.testObject\CLSID value>>
They are also replicated in
HKEY_LOCAL_MACHINE\SOFTWARE\Classes
CreateObject uses the HKEY_CLASSES_ROOT entries to retrieve the details of the class name passed in so if they're missing you will receive
Run-time error '429': ActiveX component can't create object
Within the VB6 IDE, adding a reference to the dll (in the case of a .NET assembly, via it's tlb file) bypasses this registry search thereby allowing the early binding to work without the COM registry entries.
The class has to be correctly registered for CreateObject to work. This should occur as part of the Visual Studio build process, otherwise it needs to be registered manually using Regasm.
You can test this behaviour by doing the following:-
1) Create a new VB.NET project myComLib registering for COM Interop in the project Compile properties and add a class testObject
Public Class testObject
Public Property TestProperty As String
Public Function TestFunction() As String
Return "return"
End Function
End Class
2) Build myComLib
3) Create a new VB6 project, add two command buttons to Form1 and the following code
Private Sub Command1_Click()
Dim b As Object
Set b = New myComLib.testObject
b.TestProperty = "Hello"
MsgBox b.TestProperty, vbOKOnly, b.TestFunction()
End Sub
Private Sub Command2_Click()
Dim b As Object
Set b = CreateObject("myComLib.testObject")
b.TestProperty = "Hello"
MsgBox b.TestProperty, vbOKOnly, b.TestFunction()
End Sub
4) Run the VB6 project (without full compile as that will fail)
Command2 will popup a message box, Command1 will fail with
Compile Error: User-defined type not defined.
5) Stop the project and add a reference to myComLib via it's tlb file
6) Run the VB6 project and both buttons should now work
7) Go into the registry and delete the HKEY_CLASSES_ROOT\myComLib.testObject entry (this can be re-created by Rebuilding the .NET component, you'll need to close VB6 to carry out the rebuild)
Command2 will now fail with
Run-time error '429': ActiveX component can't create object
until the registry entry is re-added.
If you're the ClassInterfaceType.None setting, You have to add a ProgId attribute to your class to allow late binding.
For example:
[Guid("B1E17DF6-9578-4D24-B578-9C70979E2F05")]
public interface _Class1
{
[DispId(1)]
string TestingAMethod();
}
[Guid("197A7A57-E59F-41C9-82C8-A2F051ABA53C")]
[ProgId("Rubberduck.SourceControl.Class1")]
[ClassInterface(ClassInterfaceType.None)]
public class Class1 : _Class1
{
public string TestingAMethod()
{
return "Hello World";
}
}