Excel UDF 'not a valid addin' error - vb.net

I am trying to create a custom vb.net Excel 2007 function (UDF) using VS 2010 and have gotten to this stage (borrowing heavily from Eric Carter's example at http://blogs.msdn.com/b/eric_carter/archive/2004/12/01/273127.aspx):
Namespace AutomationAddin
<Guid("1aeeb1b5-e099-4f7f-aeb0-3e9f19b64f62")>
<ClassInterface(ClassInterfaceType.AutoDual)>
<ComVisible(True)>
Public Class MyFunctions
Public MyFunctions()
Public Function MultiplyNTimes(ByVal number1 As Double, ByVal number2 As Double, ByVal timesToMultiply As Double) As Double
Dim result As Double = number1
For i As Integer = 0 To timesToMultiply - 1
result = result * number2
Next
Return result
End Function
<ComRegisterFunctionAttribute()>
Public Shared Sub RegisterFunction(ByVal type As Type)
Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type, "Programmable"))
Dim key As RegistryKey = Registry.ClassesRoot.OpenSubKey(GetSubKeyName(type, "InprocServer32"), True)
key.SetValue("", (System.Environment.SystemDirectory + "\mscoree.dll"), RegistryValueKind.String)
End Sub
<ComUnregisterFunctionAttribute()>
Public Shared Sub UnregisterFunction(ByVal type As Type)
Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type, "Programmable"), False)
End Sub
Private Shared Function GetSubKeyName(ByVal type As Type, ByVal subKeyName As String) As String
Dim s As System.Text.StringBuilder = New System.Text.StringBuilder
s.Append("CLSID\{")
s.Append(type.GUID.ToString.ToUpper)
s.Append("}\")
s.Append(subKeyName)
Return s.ToString
End Function
End Class
End Namespace
However, when I build it using VS 2010 and try to load it in Excel 2007 using the Addin Manager>Automation I find it listed as AutomationAddin.AutomationAddin.MyFunctions and click OK only to get the error "AutomationAddin.AutomationAddin.MyFunctions is not a valid add-in." I have set the Build settings to Register for COM interop.
I've had a look online and tried following this article How to get COM Server for Excel written in VB.NET installed and registered in Automation Servers list? but to no avail. I checked my registry (after I built my project) and under CLSID/{myGuid}/InprocServer32/Default the data is set to C:\WINDOWS\system32\mscoree.dll and CLSID/{myGuid}/Programmable already exists.
I am not quite sure what I am doing wrong and would appreciate any guidance or suggestions on the topic.
Cheers,
Ben

You might want to check out this article (Build and Deploy a .NET COM Assembly) which might be helpful in your case.

I don't know if this is relevant to the question (especially after all this time) but I originally started with a COM add-in (created using Visual Studio 2010's Excel 2010 add-in project builder). I then added an automation add-in (for UDFs) by hand in the same project using the Eric Carter blog and other examples. The two worked fine independently. It was only after combining the two in the same namespace (for some obsessively tidy reason) that I started getting the "... is not a valid add-in" error. Spent a day tearing my hair out and then separated the namespaces again - problem went away.

Related

Marshalling a .Net function that returns Double() to consume in VBA

Here is my function in .Net:
<Runtime.InteropServices.ComVisibleAttribute(True)>
Public Function Unhex(hex As String) As Double()
Dim GetArr As Double() = HexStringToDoubleArray(hex)
Return GetArr
End Function
Here is how I would like to use it in VBA:
Dim ret() As Double
ret = LinkToComLib.Unhex("EDC531...")
There are hundreds of examples of how to pass arrays into .Net (eg), but the only one I found showing the opposite is this MS page, and it doesn't show it being used on the VBA (or even COM) side. Perhaps I am using the wrong search terms. In any event:
Can I use the MarshalAs to export the Double() from .Net, or will I need to use Marshal.Copy or similar (as I suspect, as it is managed)?
If I do have to Copy, is the proper return type then IntPtr?
Am I correct in thinking that Dim ret() As Double is a pointer to a malloc'ed array or perhaps SAFEARRAY? Is that the proper type to use in VBA in this case?
Would creating the array with the proper size (it's always 492!) in VBA and then passing that to the function help in any way? Deallocing perhaps?
If anyone has a pointer to an example of this - a double (or int) array being passed out of .Net along with the corresponding VBA code, I can likely take it from there. But if someone has answers for the above, VB.Net or C# as they like, I'd appreciate it.
You need to decorate the return with <MarshalAs(UnmanagedType.SafeArray)> attribute.
VB.Net Example:
Imports System.Runtime.InteropServices
<ComClass(ArrayExample.ClassId, ArrayExample.InterfaceId)> _
Public Class ArrayExample
' These GUIDs provide the COM identity for this class and its COM interfaces.
Public Const ClassId As String = "e510d899-dad1-412b-94ea-6c726fe9f9da"
Public Const InterfaceId As String = "ef3498f0-22b4-4c2a-aeb1-22936c9757eb"
Public Function Unhex(hex As String) As <MarshalAs(UnmanagedType.SafeArray)> Double()
Dim GetArr As Double() = {2.0R, 5.0R}
Return GetArr
End Function
End Class
VBA Usage:
Sub t()
Dim c As ExampleComArrayReturn.ArrayExample
Set c = New ExampleComArrayReturn.ArrayExample
Dim arr() As Double
arr = c.Unhex("AABB")
End Sub
Edit: Forgot to mention that this uses the ComClassAttribute Class to have the compiler generate the interfaces for your class.
Edit 2 in response to follow-up question.
To debug your COM library project, go to the Debug tab of project properties. Select "Start External Program" and set it to run Excel. You can also specify the Workbook to open in the "Command line Arguments". Now when you click on the "Start" button, Excel will be launched and break points in your code will be triggered.
Edit 3:
To address the issue of targeting .Net 3.5, you can use a slightly less convenient method of attaching the debugger to the Excel process. If you are using VS2008, the method described above will work. New VS versions will need to attach to the process. There may be a way to specify this info in the vproj.user file, but I have not found the magic property type to allow direct launching using a specific framework version.
Depending on your VS version the "Attach To Process" item will either be under the Tools (VS2013) or the Debug (VS2017) menu or you can use the shortcut cntrl-alt-p.
Obviously start Excel and load your Workbook. Then in VS launch the Attach to Process dialog. Click the "Select" button and then click on the "Debug these type" radiobutton. Select the "Managed (v3.5, v3.0, v2.0) code" type and click the "OK" button. Then select the Excel process and click "Attach".

Excel Automation Addin - functions not working

Edit: The real solution to what I wanted to do can be found on this post here. I just wanted to expose some compiled functions to excel. This proved to be very easy using the Excel DNA nuget package. You just add a class library, add the nuget package, and copy paste the code found in the readme. Click F5 and it launches excel with the add-in already loaded. If you want your functions to be persisted you just need to manually add the add-in to the excel file through the "developer" ribbon section.
Original Post:
I was following the instructions from this microsoft post on how to create an automation add-in. Code compiles fine and I can access the functions from within Excel. However the functions do not work. I almost always get a #value or a #ref error when I try to assign to a cell the result of a function call. To be more specific:
The following function that is provided by Microsoft does not work. It shows me a #value error in the cell where I try to use it. I select using the mouse a random cell range as a parameter for the function.
Public Function NumberOfCells(ByVal range As Object) As Double
Dim r As Excel.Range = TryCast(range, Excel.Range)
Return CDbl(r.get_Cells.get_Count)
End Function
The following function that I created does not work. I get a #ref error. I called it by passing either directly integers ( Add1(1,2) ) or cells that contain numbers.
Public Function Add1(ByVal i1 As Integer, ByVal i2 As Integer) As Integer
return i1+i2
End Function
The following function that I created works(?!?):
Public Function Add1(ByVal i1 As Integer, ByVal i2 As Integer) As Integer
return 222
End Function
I am quite experienced in c# but not at all in vb.net, however for this add-in I need to use vb.net. I suspect that there is something simple that I am missing here but I have no idea what it is. It is also strange that the code provided by Microsoft doesn't work.
Edit: I also copy pasted the function presented here and I get the same #Value error inside excel. I did not follow the tutorial from this post from the beginning but I will during the day.
Edit 2: I figured out that the code from Microsoft doesn't work for some reason whenever you add a number in the function name. If I renamed Add1 on the sample code above to Addqweqew it would work!
MSDN Ref: http://blogs.msdn.com/b/andreww/archive/2008/01/23/managed-automation-add-ins.aspx
It has to do with a locale ID (LCID) issue. This is a known issue when
developing Excel solutions in a mixed culture environment. For more
information, see here: http://support.microsoft.com/kb/246501/.
VSTO solves this problem via its LCID Proxy. Although you can only use
this with VSTO solutions, its worth reading the documentation so you
can understand the problem:
http://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.excellocale1033proxy.aspx
and
http://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.excellocale1033attribute.aspx.
I got the same problem #Value results, I mucked around a bit and got this working (obviously it could be cleared up - but this code definitely works for me while keeping my PC set to my Australian locale ID. I'm not sure which part of the world you live but I am guessing not the United States as that's the locale where it works by default)
Public Function AddNumbers1(ByVal num1 As Double, _
ByVal num2 As Double) As Double
Dim oldCI As CultureInfo = Thread.CurrentThread.CurrentCulture
Dim english As System.Globalization.CultureInfo = System.Globalization.CultureInfo.GetCultureInfo("en-US")
System.Threading.Thread.CurrentThread.CurrentCulture = english
System.Threading.Thread.CurrentThread.CurrentUICulture = english
Dim valresult As Double = num1 + num2
Thread.CurrentThread.CurrentCulture = oldCI
Return valresult
End Function
Related question: https://social.msdn.microsoft.com/Forums/en-US/dafe71c5-d390-44bc-b4d3-b133444a02fe/excel-automation-addin-udf-returns-error-on-different-regional-settings?forum=vsto

Difficulty Registering/using VB .Net DLL for use in VBA/Com Interop

I have created a library I want to be able to share with colleagues, containing functions they can access from the VBIDE within MSOffice applications. I am using Visual Studio Express 2010, so I haven't been able to get the IDE to automatically register the DLL - instead, I have used RegAsm.exe. After several hours of desperate Googling, I finally managed to get the DLL (TLB) to appear in the VBIDE references menu. Whilst I have lots of classes in the DLL, I only exposed one as a ComClass:
<ComClass(RandomDemographicData.ClassId, RandomDemographicData.InterfaceId, RandomDemographicData.EventsId)> Public Class RandomDemographicData
Public Const ClassId As String = "05DC232D-D584-4739-8DDE-6FCA997EDC0C"
Public Const InterfaceId As String = "8FBD540B-3884-4585-9C90-47E42666FB63"
Public Const EventsId As String = "736CDFDC-D240-4DC7-9BE6-9167054221ED"
Public Function aName() As String
Return "Hello World"
End Function
End Class
Then I called it in VBA with:
Sub fgjhfh()
Dim f As New RandomDemographicData
Debug.Print f.aName
End Sub
But I get error "'-2147024894 (80070002)': Automation Error: system cannot find the file specified". I seem tantalizingly close to my goal, but can't quite make it - is anyone able to help?
EDIT:
The RegAsm command I used was "RegAsm.exe MyVBALib.dll /tlb:MyVBALib.tlb /codebase following advice found in the last post here. I have also made a copy of RegAsm.exe in the folder containing the DLL

"Set" and explicit "Variant" statement Deprecation?

I am relatively new to programming in VB, so please be patient with my frustration.
I am trying to declare a class using VB in Visual Studio 2010 Ultimate, and I'm following along with a tutorial, but Intellisense keeps messing with my code. Here's something along the lines of what I'm trying to write:
Import HYSYS
Dim myVar As Variant
...
Public Class MyClassVB1
Dim hyContainer As Object
Public Function Initialize(ByVal Container As Object, ByVal IsRecalling As Boolean) As Long
Set hyContainer = Container
End Function
However, Visual Studio turns my code into this:
Import HYSYS
Dim myVar As VariantType
...
Public Class MyClassVB1
Dim hyContainer As Object
Public Function Initialize(ByVal Container As Object, ByVal IsRecalling As Boolean) As Long
SetAttr() hyContainer = Container
End Function
After arguing with Visual Studio for a while, I finally have the message Let and Set assignment statements are no longer supported.
Is there a replacement that I'm supposed to use, or does Visual Studio just assume that I mean Variant if I don't explicitly specify a type?
Likewise, do I need to use a keyword or function for variable assignment, or can I treat it like I would in c++ and just write hyContainer = Container?
Set, as you're using it, is left over from the vb6/vbscript days.
It no longer works like that in VB.Net. You never ever use it for assignment.
You may be following an obsolete tutorial. In VB.Net, Set is only used as part of a LINQ query.
Variant is similar. It is no longer part of the VB language at all, and has not been for more than 10 years.

How to get hardware print?

Can someone tell me how to get the hardware print of my computer, on VB 2010 express, or where and how is that stored? Thanks in advance.
The best way to figure out what hardware is attached to your computer is to use WMI to get the information. Microsoft has created a tool that will create C#, VB.Net and VBScript sample code which you can run with the program and see what the values are, you can then add it to your program. This tool is called the WMI Code Creator. I would start out by exploring the Classes starting with Win32_
Now that I know what you are trying to do I can be a little more specific. The WMI NameSpace you are needing is root\CIMV2 the Class is Win32_DiskDrive or Win32_PhysicalMedia and the Property is SerialNumber. I made a small console test app in Vb.net. It will print out the drive serialnumbers on your PC, if you need it in c# I can modify. There are also numerous other SO Questions about the same subject.
Imports System
Imports System.Management
Module Module1
Sub Main()
For Each sn As String In GetDriveSerialNumber()
Console.WriteLine(sn.Trim)
Next
Console.ReadLine()
End Sub
Function GetDriveSerialNumber() As List(Of String)
Dim snList As List(Of String) = New List(Of String)
Try
Dim searcher As New ManagementObjectSearcher("root\CIMV2", "SELECT * FROM Win32_DiskDrive")
For Each queryObj As ManagementObject In searcher.Get()
snList.Add(queryObj("SerialNumber").ToString())
Next
Catch err As ManagementException
Throw
End Try
Return snList
End Function
End Module