Accessing .NET function exposed as COM in Excel - vb.net

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

Related

Calling C# method from VBA using COM Interop

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.

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{
...
};

Calling a function from a header in Visual C++ 2010

I'm programming in Visual C++ 2010. I've got a example.h example.cpp and Form1.h.
Basically, I've pasted bits of code. I'm not able to include Form1.h in the example.h file, not sure why. But The main question is how do I call Test (which is in form1.h) from example.cpp? What would be the syntax? Is it possible to do this?
My Form1.h
#include "example.h"
public ref class Form1 : public System::Windows::Forms::Form
{
public: void Test(void)
{
// Does something
}
}
My example.cpp
#include "example.h"
#include "Form1.h"
Test(); // would like to call Test from here.
You have two problems here:
You must call functions from inside of another function. The code you currently have in your example.cpp file is invalid, because you are trying to call the Test() function at global scope.
Make it look like this instead:
int main()
{
Test();
return 0;
}
This also solves the problem that you don't have a main function, which is the entry point to any C++ application.
More generally, I would strongly recommend using one of the project templates that comes with Visual Studio to get started (rather than copying and pasting random bits of code, like you said). That ensures that you have all of the things you need to get started, like an entry point. Once you have a solid foundation, you can start building up from there.
You might also find it useful to obtain either a book on C++/CLI or an online tutorial (such as this one: Hello C++/CLI, Part 1 and Hello C++/CLI, Part 2).
Your Test function is a member function of the Form1 class, which means that you need an object of that class in order to call it. Thus, the code should actually look like this:
int main()
{
Form1^ frm = gcnew Form1();
frm.Test();
return 0;
}
Alternatively, you could work around this by making the Test() function a static function. This would allow you to call it without having an instance of the class:
public ref class Form1 : public System::Windows::Forms::Form
{
public: static void Test(void)
{
// Does something
}
}
// ...
int main()
{
Form1::Test();
return 0;
}
However, beware that this means you cannot access any other members of the Form1 class inside of the Test() function (because there is no this pointer).
This should all be explained in whatever book/tutorial you decide to use to learn C++/CLI—search for a chapter about "Classes" or "Object-Oriented Design".

Cannot use managed event/objects in unmanaged code error c3265, c2811

Native C++ library that I am using in C++/CLI project raises events giving me results,
If I try to handle the event by extending the unmanaged event, it says the ref class can only extend ref class.
I then tried to create a native event but have manged object inside it to collect the results, but I get the error cannot declare managed object in unmanaged class.
Is there anyway to get it done in one of the ways I am trying, or should I declare unmanaged result objects fill them in unmanaged event and then Marshall it ?
Edit:
class MyNativeListener: public NativeEventListener
{
private:
ManagedResultsObject ^_results;
public:
void onEndProcessing(ProcessingEvent *event)
{
_results.Value = event->value;
//Many more properties to capture
}
};
This is what I am trying, I have extended the native event listener to capture the event, but not sure how to capture the results to a managed object.
Edit2
Found this while searching on the same line as suggested by #mcdave auto_gcroot
Your native class needs to store a handle to the managed object instead of a reference to it. You can do this using the gcroot template. If you dig into the gcroot template you will find it uses the GCHandle Structure, which with appropriate static casting can be stored as a void* pointer and so provides a means of storing managed references in native code.
Try expanding your code along the following lines:
#include <vcclr.h>
class MyNativeListener: public NativeEventListener
{
private:
gcroot<ManagedResultsObject^> _results;
public:
void onEndProcessing(ProcessingEvent *event)
{
_results->Value = event->value;
//Many more properties to capture
}
};