I have been struggling to use Registration Free COM to make .NET calls from Excel 2010. Essentially I wanted to be able to call some .NET API functions in an Excel macro. I created a wrapper API in .NET, and exposed that wrapper as a COM-callable library. The code for that is as follows:
Imports System
Imports System.Collections.Generic
Imports System.Runtime.InteropServices
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Assemblies
Namespace ExcelNXInterface
<Guid("bbe0089d-a732-4743-922b-180b30006fa4"), _
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface _ExcelNXInterface
<DispId(1)> Sub HighlightCompTag(ByRef tag As Long)
<DispId(2)> Sub Echo(ByVal output As String)
End Interface
<ComVisible(True)>
<Guid("1376DE24-CC2D-46cb-8BF0-887A9CAF3014"), _
ClassInterface(ClassInterfaceType.None), _
ProgId("ExcelNXInterface.ExcelNXInterface")> Public Class ExcelNXInterface
Implements _ExcelNXInterface
Public _ExcelNXInterface
Dim theSession As Session = Session.GetSession()
Dim ufs As UFSession = UFSession.GetUFSession()
Public Sub HighlightCompTag(ByRef tag As Long) Implements _ExcelNXInterface.HighlightCompTag
Try
ufs.Disp.SetHighlight(tag, 1)
Echo("Component(s) Highlighted...")
Catch e As NXException
Echo("NX Exception is: {0} " + e.Message)
Catch e As Exception
Echo("Exception is: {0} " & e.Message)
Echo("DONE!" & vbLf)
End Try
End Sub
Sub Echo(ByVal output As String) Implements _ExcelNXInterface.Echo
theSession.ListingWindow.Open()
theSession.ListingWindow.WriteLine(output)
theSession.LogFile.WriteLine(output)
End Sub
End Class
End Namespace
The manifest to use Registration Free COM is as follows:
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly
manifestVersion="1.0"
xmlns="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv1="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assemblyIdentity
type="win32"
version="1.0.0.0"
name="ExcelNXInterface"/>
<clrClass
clsid="{bbe0089d-a732-4743-922b-180b30006fa4}"
progid="ExcelNXInterface.ExcelNXInterface"
threadingModel="Both"
name="ExcelNXInterface.ExcelNXInterface">
</clrClass>
<file name = "ExcelNXInterface.dll"></file>
</asmv1:assembly>
To call the wrapped .NET functions in Excel I used the following VBA code:
Sub ExNX()
Dim actCtx As Object
Set actCtx = CreateObject("Microsoft.Windows.ActCtx")
actCtx.Manifest = ThisWorkbook.Path & "\ExcelNXInterface.dll.manifest"
Dim myNX As Object
Set myNX = actCtx.CreateObject("ExcelNXInterface.ExcelNXInterface")
End Sub
However, the problem is that when the last line of code is called I get a Run time error 2147024894 (80070002) Method 'CreatObject' of object IActCtx' failed. I honestly don't know what it is that I am doing wrong, or even if this is possible. Any help or suggestions are greatly appreciated.
It should be noted that the DLL, manifest and the workbook are all in the same directory. Also it should be noted that VSTO, executable files and/or installations are not options for me. That is why I am trying to stick to using Excel.
Related
I use VBA to automate an external application that recently changed their COM API. The new API loads files asynchronously (used to be synchronous) so I need to wait for the file loaded trigger before I continue when I try to load a file.
I have tried the methods listed on the Microsoft website (EX1, EX2) which were also part of an accepted answer on StackOverflow.
Below is the code I have in a class module named UCExternal to contain the external application object:
Public WithEvents obj As External.Application
Private fileLoaded As Boolean
Private Sub obj_OnFileLoaded(ByVal lLayer As Long, ByVal strUNCPath As String)
Debug.Print lLayer
Debug.Print strUNCPath
fileLoaded = True
End Sub
Public Sub LoadSingleFile(fileStr As String)
fileLoaded = False
obj.LoadFile 0, fileStr
Do
DoEvents
Loop Until fileLoaded
End Sub
And then this is what I had in a normal code module to run using a button on the sheet:
Sub TryLoadFile()
Dim extObj as New UCExternal
set extObj.obj = CreateObject("External.Application")
filePath = "path/to/file"
extObj.LoadSingleFile filePath
End Sub
The event code never seems to fire and instead the Do Loop just runs until Excel crashes. I don't know if there is a way to confirm the application actually sent the event trigger? I have read through the new documentation for the application and that is the event they say to wait for. I have reached out to them for help as well but I wasn't sure if there was something more general I may have been missing. I have not worked with events external to Excel in the past. If I just step through it using the debugger and manually exit the Do Loop eventually the rest of the code that works on the loaded file works as well, so it does load the file.
extObj needs to be declared outside of TryLoadFile, or it will go out of scope and get cleared as soon as TryLoadFile completes
Dim extObj as New UCExternal
Sub TryLoadFile()
Set extObj = New UCExternal
set extObj.obj = CreateObject("External.Application")
filePath = "path/to/file"
extObj.LoadSingleFile filePath
End Sub
I am trying to handle Large XML file thru SAXON library in VBA
Source XML has following tag
<Id><![CDATA[IPMS_TEST_DS_2 & 13]]></Id>
Destination XML while writing into a destination file if am getting following xml tag
<Id>IPMS_TEST_DS_2 & 13</Id> <!--- This should be as same as parent XML -->
by implementing SAXXMLReader60 as reader & MXXMLWriter60 as writer
(Source : FlyLib.com, learn.microsoft.com )
Implements IVBSAXContentHandler
Implements SAXXMLReader60
Implements IVBSAXLexicalHandler
Implements IVBSAXDTDHandler
Private rdr As New SAXXMLReader60
Private wrt As New MXXMLWriter60
Private ch As IVBSAXContentHandler
Private dtdh As IVBSAXDTDHandler
Private lexh As IVBSAXLexicalHandler
Private Sub Class_Initialize()
Set ch = wrt
Set dtdh = wrt
Call rdr.putProperty("http://xml.org/sax/properties/lexical-handler", wrt)
Set lexh = wrt
End Sub
'....Following Event handlers are not getting triggered...
Private Sub IVBSAXLexicalHandler_comment(strChars As String)
End Sub
Private Sub IVBSAXLexicalHandler_endCDATA()
lexh.endCDATA
End Sub
Private Sub IVBSAXLexicalHandler_endDTD()
End Sub
Private Sub IVBSAXLexicalHandler_endEntity(strName As String)
End Sub
Private Sub IVBSAXLexicalHandler_startCDATA()
lexh.startCDATA
End Sub
Please suggest way to detect < ![CDATA[]]> while reading
CDATA is not part of the XDM data model used by XSLT: <![CDATA[IPMS_TEST_DS_2 & 13]]> and IPMS_TEST_DS_2 & 13 are different, but completely synonymous, representations of a text node with string value IPMS_TEST_DS_2 & 13. An XSLT stylesheet cannot distinguish them.
There's a processor called LEXEV from Andrew Welch that allows CDATA to be represented in the XDM data model. I've forgotten the details but it uses something like a processing instruction <?CDATA IPMS_TEST_DS_2 & 13?>. You could use a similar technique in your SAX filter. Of course that wouldn't be distinguishable from an actual processing instruction named "CDATA".
After many iterations over various options, I got to know the correct method of calling SAXReader.putProperty
It catches CDATA for writing into destination XML
Main Code Block
Sub Main()
Dim saxRdr As New SAXXMLReader60
Dim evntHndlr As New SAXEventHandlerClass
Set saxRdr.contentHandler = evntHndlr
Call saxRdr.putProperty("http://xml.org/sax/properties/lexical-handler", evntHndlr.wrt)
'......above line makes event IVBSAXContentHandler_characters(strChars As String) to catch CDATA tag as it is......
saxRdr.parseURL "test.xml"
Set saxRdr = Nothing
End sub
SAXEventHandlerClass: characters event catches CDATA Lexical automatically into strChars
Private Sub IVBSAXContentHandler_characters(strChars As String)
'content writer logic goes here
End Sub
I am trying to call an Access function from Excel and get this error:
Compile Error: Only user-defined types defined in public object
modules can be coerced to or from a variant or passed to late-bound
functions.
I tried to adopt this solution I found, but with no luck. Here is my code:
In the Excel Module ExternalStatistics
Option Explicit
Public Type MyExternalStatistics
esMyInvites As Single
esMyInvitePerTalk As Single
End Type
Public MyExtRecStats As MyExternalStatistics
In the Sheet1(A-Crunched Numbers) object:
Option Explicit
Public appRecruitingAccess As Access.Application
Public Sub Worksheet_Activate()
Dim MyExtRecStats As MyExternalStatistics
Dim RecruitWindow As Integer
Dim test As String
Set appRecruitingAccess = New Access.Application
With appRecruitingAccess
.Visible = False
.OpenCurrentDatabase "C:\Dropbox\RECRUITING\Remote0\Recruiting 0.accdb"
RecruitWindow = DateDiff("d", Format(Date, Worksheets("ActivityAndIncentive").Range("IncentiveStart").Value), Format(Date, Worksheets("ActivityAndIncentive").Range("IncentiveEnd").Value))
RecruitWindow = DateDiff("d", Format(Date, Worksheets("ActivityAndIncentive").Range("IncentiveStart").Value), Format(Date, Worksheets("ActivityAndIncentive").Range("IncentiveEnd").Value))
MyExtRecStats = .Run("ExternalRecruitingStats", RecruitWindow) '*** ERROR HERE ***
.CloseCurrentDatabase
.Quit
End With
Set appRecruitingAccess = Nothing
End Sub
In the Access Module ExternalStatistics
Option Compare Database
Option Explicit
Public Type MyExternalStatistics
esMyInvites As Single
esMyInvitePerTalk As Single
end Type
Public Function ExternalRecruitingStats(StatWindow As Integer) As MyExternalStatistics
Dim MyRecStats As MyExternalStatistics
Dim Invites As Integer, Talks As Integer
Invites = 1
Talks = 2
With MyRecStats
.esMyInvites = CSng(Invites)
.esMyInvitesPerTalk = CSng(Invites/Talks)
End With
ExternalRecruitingStats = MyRecStats 'return a single structure
End Function
It does not like the MyExtRecStats = .Run("ExternalRecruitingStats", RecruitWindow) statement. I would like to eventually assign several set in the Access function and bring them all back with one object. Then I can place those values where they should be in the spreadsheet.
Type definitions in VBA are very local and they don't work well when you try to use them with objects that may not have access to the exact definition of the Type (which is probably the case here).
Sometimes, using a Class may work. You would need to make the class public and instantiate it before passing it around, but I have some doubts that it will actually work (for the same reason that the class definition won't be visible from one app to the other).
Another simple solution would be to use a simple Collection object instead, where you add your values as items to the collection. Of course the exact order of how you add/retrieve items is important.
There are a few interesting answers to a similar issue in User Defined Type (UDT) As Parameter In Public Sub In Class Module. It's about VB6 but it should also apply in great part to VBA.
Having said all this, you may be able to resolve all your issues by importing your Access code into Excel instead.
You can use DAO or ADO from Excel and manipulate Access databases just as if you were in Excel, for instance:
Connecting to Microsoft Access Database from Excel VBA, using DAO Object Model
Using Excel VBA to Export data to Ms.Access Table
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
As a heavy user of dictionaries in vba, I found useful to create a 'super-dictionary' class, that deals with lots of minor issue I don't wanna deal with the main code. Below is a draft of this 'super-dictionary' custom-object.
Is this a good idea? Could this approach affect the performance of my dictionaries in some unforeseen way? (for example is my Get Item method expensive? - I use it a lot)
tks in advance!
Public pDictionary As Object
Private Sub Class_Initialize()
Set pDictionary = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
If Not pDictionary Is Nothing Then Set pDictionary = Nothing
End Sub
Public Property Get GetItem(Key As Variant) As Variant:
If VarType(pDictionary.Items()(1)) = vbObject Then
Set GetItem = pDictionary(Key)
Else
GetItem = pDictionary(Key)
End If
End Property
Public Property Get GetItems() As Variant:
Dim tmpArray() As Variant, i As Integer
If Not pDictionary.Count = 0 Then
ReDim tmpArray(pDictionary.Count - 1)
For i = 0 To pDictionary.Count - 1
If VarType(pDictionary.Items()(i)) = vbObject Then Set tmpArray(i) =pDictionary.Items()(i)
If Not VarType(pDictionary.Items()(i)) = vbObject Then tmpArray(i) =pDictionary.Items()(i)
Next i
Else
ReDim tmpArray(0)
End If
GetItems = tmpArray
End Property
Public Property Get GetKeys() As Variant:
GetKeys = pDictionary.Keys
End Property
Public Property Get Count() As Integer:
Count = pDictionary.Count
End Property
Public Property Get Exists(Key As Variant) As Boolean:
If IsNumeric(Key) Then Exists = pDictionary.Exists(CLng(Key))
If Not IsNumeric(Key) Then Exists = pDictionary.Exists(Key)
End Property
Public Sub Add(Key As Variant, Item As Variant):
If IsNumeric(Key) Then pDictionary.Add CLng(Key), Item
If Not IsNumeric(Key) Then pDictionary.Add Key, Item
End Sub
Public Sub AddorSkip(Key As Variant, Item As Variant):
If IsNumeric(Key) Then
If Not pDictionary.Exists(CLng(Key)) Then pDictionary.Add CLng(Key), Item
Else
If Not pDictionary.Exists(Key) Then pDictionary.Add Key, Item
End If
End Sub
Public Sub AddorError(Key As Variant, Item As Variant):
If IsNumeric(Key) Then
If Not pDictionary.Exists(CLng(Key)) Then
pDictionary.Add CLng(Key), Item
Else
MsgBox ("Double entry in Dictionary: " & Key & " already exists."): End
End If
Else
If Not pDictionary.Exists(Key) Then
pDictionary.Add Key, Item
Else
MsgBox ("Double entry in Dictionary: " & Key & " already exists"): End
End If
End If
End Sub
Public Sub Remove(Key As Variant):
If IsNumeric(Key) Then
pDictionary.Remove (CLng(Key))
Else
pDictionary.Remove (Key)
End If
End Sub
Very good-looking class as far as I can tell (albeit your VBA-class-building skills are clearly more advanced than mine). All that I might suggest is, if you are fluent in .NET, then recreate it in Visual Studio as a portable class library so that you might leverage the robust functionality of the .NET framework.
Create a new "class library" in Visual Studio.
Import System, and System.Runtime.InteropServices into your new class.
Wrap the class in a namespace.
Create an interface for your properties, methods, etc.
Go to the "Compile" settings and click the "Register for COM Interop".
Build the project--this creates a .TLB file in the project's BIN folder.
Add the .TLB file as a reference in the VBA developer environment.
Here is an example of the VB.net code:
Option Strict On
Imports System
Imports System.Runtime.InteropServices
Namespace SuperDictionary
' Interface with members of the Super-Dictionary library exposed in the TLB.
Public Interface ISuperDictionaryInterface
ReadOnly Property MyListOfTypeStringProperty(ByVal index As Integer) As String
Function MyMethod(ByVal someValue as Variant) as Boolean
End Interface
<ClassInterface(ClassInterfaceType.None)>
Public Class SuperDictionary: Implements ISuperDictionaryInterface
Public Function MyMethod(ByVal someValue as Variant) As Boolean Implements ISuperDictionaryInterface.MyMethod
'========================
'Your code here
'========================
End Function
Private _MyListOfTypeStringProperty As List(Of String)
Public ReadOnly Property MyListOfTypeStringProperty(ByVal index as Integer) As String Implements ISuperDictionaryInterface.MyListOfTypeStringProperty
Get
Return _MyListOfTypeString(index)
End Get
End Property
End Class
End Namespace
What can you do with .NET that VBA can't?
That's a good question, I'm glad you asked. Obviously you can do much more than what I demonstrated here. Let's say, for the sake of example, that you would like to integrate some of the fancy new Web Services all the cool kids are using these days. Whether you're communicating with web services using a WSDL file, or perhaps your own custom REST methods, the classes of the .NET framework combined with the plethora of tools found in Visual Studio 2012 developer environment make using .NET much more preferable than VBA. Using the technique outlined above, you could create a wrapper class for these web services that utilize custom methods to perform all the necessary actions, then return the VBA-compatible objects and/or data-types back to VBA. Much better, no?
Not to mention, the library you create would also be compatible with other platforms such as ASP.NET, Windows Phones, Silverlight, Xbox, etc.
Some helpful links I used (I'll add more as I find them):
Use .NET Class Library in Excel VBA
How to enable early binding of VBA object variable...
Early Binding of a C# COM library in VBA
This seems like it should be an easy one but I'm stuck.
I'm running a VBA script in Access that creates a 40+ page report in Excel.
I am creating an Excel Application Object using Early Binding:
Public obj_xl As New Excel.Application
Here is an example of how I am referencing the object:
With obj_xl
.Workbooks.Add
.Visible = True
.Sheets.Add
.blahblahblah
End With
The problem is that the procedure has become too large and I need to break the code up into separate modules.
If I try to reference the Excel Application Object from a different module than it was created in, it throws an error ("Ambiguous Name").
I'm sure I could do something with Win API but that seems like it would be overkill.
Any thoughts? Thanks
this is the type of situation that can cause the error "Ambiguous Name"
Function Split(s As String)
MsgBox s
End Function
Function Split(s As String)
MsgBox s
End Function
I know the example is trivial, but what you are looking for is a function , an object and/or a form control with the same names.
If you convert your declaration to Global, you can reference it in all your modules. For example, in one module, put this at the top:
Global obj_xl As Excel.Application
Then in an another module,
Sub xx()
Set obj_xl = New Excel.Application
Debug.Print obj_xl.Name
End Sub